github.com/vishvananda/netlink@v1.3.0/conntrack_test.go (about) 1 //go:build linux 2 // +build linux 3 4 package netlink 5 6 import ( 7 "encoding/binary" 8 "fmt" 9 "net" 10 "os" 11 "runtime" 12 "testing" 13 "time" 14 15 "github.com/vishvananda/netlink/nl" 16 "github.com/vishvananda/netns" 17 "golang.org/x/sys/unix" 18 ) 19 20 func CheckErrorFail(t *testing.T, err error) { 21 if err != nil { 22 t.Fatalf("Fatal Error: %s", err) 23 } 24 } 25 func CheckError(t *testing.T, err error) { 26 if err != nil { 27 t.Errorf("Error: %s", err) 28 } 29 } 30 31 func udpFlowCreateProg(t *testing.T, flows, srcPort int, dstIP string, dstPort int) { 32 for i := 0; i < flows; i++ { 33 ServerAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", dstIP, dstPort)) 34 CheckError(t, err) 35 36 LocalAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", srcPort+i)) 37 CheckError(t, err) 38 39 Conn, err := net.DialUDP("udp", LocalAddr, ServerAddr) 40 CheckError(t, err) 41 42 Conn.Write([]byte("Hello World")) 43 Conn.Close() 44 } 45 } 46 47 func nsCreateAndEnter(t *testing.T) (*netns.NsHandle, *netns.NsHandle, *Handle) { 48 // Lock the OS Thread so we don't accidentally switch namespaces 49 runtime.LockOSThread() 50 51 // Save the current network namespace 52 origns, _ := netns.Get() 53 54 ns, err := netns.New() 55 CheckErrorFail(t, err) 56 57 h, err := NewHandleAt(ns) 58 CheckErrorFail(t, err) 59 60 // Enter the new namespace 61 netns.Set(ns) 62 63 // Bing up loopback 64 link, _ := h.LinkByName("lo") 65 h.LinkSetUp(link) 66 67 return &origns, &ns, h 68 } 69 70 func applyFilter(flowList []ConntrackFlow, ipv4Filter *ConntrackFilter, ipv6Filter *ConntrackFilter) (ipv4Match, ipv6Match uint) { 71 for _, flow := range flowList { 72 if ipv4Filter.MatchConntrackFlow(&flow) == true { 73 ipv4Match++ 74 } 75 if ipv6Filter.MatchConntrackFlow(&flow) == true { 76 ipv6Match++ 77 } 78 } 79 return ipv4Match, ipv6Match 80 } 81 82 // TestConntrackSocket test the opening of a NETFILTER family socket 83 func TestConntrackSocket(t *testing.T) { 84 skipUnlessRoot(t) 85 setUpNetlinkTestWithKModule(t, "nf_conntrack") 86 setUpNetlinkTestWithKModule(t, "nf_conntrack_netlink") 87 88 h, err := NewHandle(unix.NETLINK_NETFILTER) 89 CheckErrorFail(t, err) 90 91 if h.SupportsNetlinkFamily(unix.NETLINK_NETFILTER) != true { 92 t.Fatal("ERROR not supporting the NETFILTER family") 93 } 94 } 95 96 // TestConntrackTableList test the conntrack table list 97 // Creates some flows and checks that they are correctly fetched from the conntrack table 98 func TestConntrackTableList(t *testing.T) { 99 if os.Getenv("CI") == "true" { 100 t.Skipf("Fails in CI: Flow creation fails") 101 } 102 skipUnlessRoot(t) 103 k, m, err := KernelVersion() 104 if err != nil { 105 t.Fatal(err) 106 } 107 // conntrack l3proto was unified since 4.19 108 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f 109 if k < 4 || k == 4 && m < 19 { 110 setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4") 111 setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv6") 112 } 113 setUpNetlinkTestWithKModule(t, "nf_conntrack") 114 setUpNetlinkTestWithKModule(t, "nf_conntrack_netlink") 115 116 // Creates a new namespace and bring up the loopback interface 117 origns, ns, h := nsCreateAndEnter(t) 118 defer netns.Set(*origns) 119 defer origns.Close() 120 defer ns.Close() 121 defer runtime.UnlockOSThread() 122 123 setUpF(t, "/proc/sys/net/netfilter/nf_conntrack_acct", "1") 124 setUpF(t, "/proc/sys/net/netfilter/nf_conntrack_timestamp", "1") 125 setUpF(t, "/proc/sys/net/netfilter/nf_conntrack_udp_timeout", "45") 126 127 // Flush the table to start fresh 128 err = h.ConntrackTableFlush(ConntrackTable) 129 CheckErrorFail(t, err) 130 131 // Create 5 udp 132 startTime := time.Now() 133 udpFlowCreateProg(t, 5, 2000, "127.0.0.10", 3000) 134 135 // Fetch the conntrack table 136 flows, err := h.ConntrackTableList(ConntrackTable, unix.AF_INET) 137 CheckErrorFail(t, err) 138 139 // Check that it is able to find the 5 flows created 140 var found int 141 for _, flow := range flows { 142 if flow.Forward.Protocol == 17 && 143 flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) && 144 flow.Forward.DstPort == 3000 && 145 (flow.Forward.SrcPort >= 2000 && flow.Forward.SrcPort <= 2005) { 146 found++ 147 flowStart := time.Unix(0, int64(flow.TimeStart)) 148 if flowStart.Before(startTime) || flowStart.Sub(startTime) > time.Second { 149 t.Error("Invalid conntrack entry start timestamp") 150 } 151 if flow.TimeStop != 0 { 152 t.Error("Invalid conntrack entry stop timestamp") 153 } 154 // Expect at most one second to have already passed from the configured UDP timeout of 45secs. 155 if flow.TimeOut < 44 || flow.TimeOut > 45 { 156 t.Error("Invalid conntrack entry timeout") 157 } 158 } 159 160 if flow.Forward.Bytes == 0 && flow.Forward.Packets == 0 && flow.Reverse.Bytes == 0 && flow.Reverse.Packets == 0 { 161 t.Error("No traffic statistics are collected") 162 } 163 } 164 if found != 5 { 165 t.Fatalf("Found only %d flows over 5", found) 166 } 167 168 // Give a try also to the IPv6 version 169 _, err = h.ConntrackTableList(ConntrackTable, unix.AF_INET6) 170 CheckErrorFail(t, err) 171 172 // Switch back to the original namespace 173 netns.Set(*origns) 174 } 175 176 // TestConntrackTableFlush test the conntrack table flushing 177 // Creates some flows and then call the table flush 178 func TestConntrackTableFlush(t *testing.T) { 179 if os.Getenv("CI") == "true" { 180 t.Skipf("Fails in CI: Flow creation fails") 181 } 182 skipUnlessRoot(t) 183 setUpNetlinkTestWithKModule(t, "nf_conntrack") 184 setUpNetlinkTestWithKModule(t, "nf_conntrack_netlink") 185 k, m, err := KernelVersion() 186 if err != nil { 187 t.Fatal(err) 188 } 189 // conntrack l3proto was unified since 4.19 190 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f 191 if k < 4 || k == 4 && m < 19 { 192 setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4") 193 } 194 setUpNetlinkTestWithKModule(t, "nf_conntrack") 195 // Creates a new namespace and bring up the loopback interface 196 origns, ns, h := nsCreateAndEnter(t) 197 defer netns.Set(*origns) 198 defer origns.Close() 199 defer ns.Close() 200 defer runtime.UnlockOSThread() 201 202 // Create 5 udp flows using netcat 203 udpFlowCreateProg(t, 5, 3000, "127.0.0.10", 4000) 204 205 // Fetch the conntrack table 206 flows, err := h.ConntrackTableList(ConntrackTable, unix.AF_INET) 207 CheckErrorFail(t, err) 208 209 // Check that it is able to find the 5 flows created 210 var found int 211 for _, flow := range flows { 212 if flow.Forward.Protocol == 17 && 213 flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) && 214 flow.Forward.DstPort == 4000 && 215 (flow.Forward.SrcPort >= 3000 && flow.Forward.SrcPort <= 3005) { 216 found++ 217 } 218 } 219 if found != 5 { 220 t.Fatalf("Found only %d flows over 5", found) 221 } 222 223 // Flush the table 224 err = h.ConntrackTableFlush(ConntrackTable) 225 CheckErrorFail(t, err) 226 227 // Fetch again the flows to validate the flush 228 flows, err = h.ConntrackTableList(ConntrackTable, unix.AF_INET) 229 CheckErrorFail(t, err) 230 231 // Check if it is still able to find the 5 flows created 232 found = 0 233 for _, flow := range flows { 234 if flow.Forward.Protocol == 17 && 235 flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) && 236 flow.Forward.DstPort == 4000 && 237 (flow.Forward.SrcPort >= 3000 && flow.Forward.SrcPort <= 3005) { 238 found++ 239 } 240 } 241 if found > 0 { 242 t.Fatalf("Found %d flows, they should had been flushed", found) 243 } 244 245 // Switch back to the original namespace 246 netns.Set(*origns) 247 } 248 249 // TestConntrackTableDelete tests the deletion with filter 250 // Creates 2 group of flows then deletes only one group and validates the result 251 func TestConntrackTableDelete(t *testing.T) { 252 if os.Getenv("CI") == "true" { 253 t.Skipf("Fails in CI: Flow creation fails") 254 } 255 skipUnlessRoot(t) 256 257 requiredModules := []string{"nf_conntrack", "nf_conntrack_netlink"} 258 k, m, err := KernelVersion() 259 if err != nil { 260 t.Fatal(err) 261 } 262 // conntrack l3proto was unified since 4.19 263 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f 264 if k < 4 || k == 4 && m < 19 { 265 requiredModules = append(requiredModules, "nf_conntrack_ipv4") 266 } 267 268 setUpNetlinkTestWithKModule(t, requiredModules...) 269 270 // Creates a new namespace and bring up the loopback interface 271 origns, ns, h := nsCreateAndEnter(t) 272 defer netns.Set(*origns) 273 defer origns.Close() 274 defer ns.Close() 275 defer runtime.UnlockOSThread() 276 277 // Create 10 udp flows 278 udpFlowCreateProg(t, 5, 5000, "127.0.0.10", 6000) 279 udpFlowCreateProg(t, 5, 7000, "127.0.0.20", 8000) 280 281 // Fetch the conntrack table 282 flows, err := h.ConntrackTableList(ConntrackTable, unix.AF_INET) 283 CheckErrorFail(t, err) 284 285 // Check that it is able to find the 5 flows created for each group 286 var groupA int 287 var groupB int 288 for _, flow := range flows { 289 if flow.Forward.Protocol == 17 && 290 flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) && 291 flow.Forward.DstPort == 6000 && 292 (flow.Forward.SrcPort >= 5000 && flow.Forward.SrcPort <= 5005) { 293 groupA++ 294 } 295 if flow.Forward.Protocol == 17 && 296 flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.20")) && 297 flow.Forward.DstPort == 8000 && 298 (flow.Forward.SrcPort >= 7000 && flow.Forward.SrcPort <= 7005) { 299 groupB++ 300 } 301 } 302 if groupA != 5 || groupB != 5 { 303 t.Fatalf("Flow creation issue groupA:%d, groupB:%d", groupA, groupB) 304 } 305 306 // Create a filter to erase groupB flows 307 filter := &ConntrackFilter{} 308 filter.AddIP(ConntrackOrigDstIP, net.ParseIP("127.0.0.20")) 309 filter.AddProtocol(17) 310 filter.AddPort(ConntrackOrigDstPort, 8000) 311 312 // Flush entries of groupB 313 var deleted uint 314 if deleted, err = h.ConntrackDeleteFilters(ConntrackTable, unix.AF_INET, filter); err != nil { 315 t.Fatalf("Error during the erase: %s", err) 316 } 317 if deleted != 5 { 318 t.Fatalf("Error deleted a wrong number of flows:%d instead of 5", deleted) 319 } 320 321 // Check again the table to verify that are gone 322 flows, err = h.ConntrackTableList(ConntrackTable, unix.AF_INET) 323 CheckErrorFail(t, err) 324 325 // Check if it is able to find the 5 flows of groupA but none of groupB 326 groupA = 0 327 groupB = 0 328 for _, flow := range flows { 329 if flow.Forward.Protocol == 17 && 330 flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.10")) && 331 flow.Forward.DstPort == 6000 && 332 (flow.Forward.SrcPort >= 5000 && flow.Forward.SrcPort <= 5005) { 333 groupA++ 334 } 335 if flow.Forward.Protocol == 17 && 336 flow.Forward.DstIP.Equal(net.ParseIP("127.0.0.20")) && 337 flow.Forward.DstPort == 8000 && 338 (flow.Forward.SrcPort >= 7000 && flow.Forward.SrcPort <= 7005) { 339 groupB++ 340 } 341 } 342 if groupA != 5 || groupB > 0 { 343 t.Fatalf("Error during the erase groupA:%d, groupB:%d", groupA, groupB) 344 } 345 346 // Switch back to the original namespace 347 netns.Set(*origns) 348 } 349 350 func TestConntrackFilter(t *testing.T) { 351 var flowList []ConntrackFlow 352 flowList = append(flowList, ConntrackFlow{ 353 FamilyType: unix.AF_INET, 354 Forward: IPTuple{ 355 SrcIP: net.ParseIP("10.0.0.1"), 356 DstIP: net.ParseIP("20.0.0.1"), 357 SrcPort: 1000, 358 DstPort: 2000, 359 Protocol: 17, 360 }, 361 Reverse: IPTuple{ 362 SrcIP: net.ParseIP("20.0.0.1"), 363 DstIP: net.ParseIP("192.168.1.1"), 364 SrcPort: 2000, 365 DstPort: 1000, 366 Protocol: 17, 367 }, 368 }, 369 ConntrackFlow{ 370 FamilyType: unix.AF_INET, 371 Forward: IPTuple{ 372 SrcIP: net.ParseIP("10.0.0.2"), 373 DstIP: net.ParseIP("20.0.0.2"), 374 SrcPort: 5000, 375 DstPort: 6000, 376 Protocol: 6, 377 }, 378 Reverse: IPTuple{ 379 SrcIP: net.ParseIP("20.0.0.2"), 380 DstIP: net.ParseIP("192.168.1.1"), 381 SrcPort: 6000, 382 DstPort: 5000, 383 Protocol: 6, 384 }, 385 Labels: []byte{0, 0, 0, 0, 3, 4, 61, 141, 207, 170, 2, 0, 0, 0, 0, 0}, 386 Zone: 200, 387 }, 388 ConntrackFlow{ 389 FamilyType: unix.AF_INET6, 390 Forward: IPTuple{ 391 SrcIP: net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee"), 392 DstIP: net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd"), 393 SrcPort: 1000, 394 DstPort: 2000, 395 Protocol: 132, 396 }, 397 Reverse: IPTuple{ 398 SrcIP: net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd"), 399 DstIP: net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee"), 400 SrcPort: 2000, 401 DstPort: 1000, 402 Protocol: 132, 403 }, 404 Zone: 200, 405 }) 406 407 // Empty filter 408 v4Match, v6Match := applyFilter(flowList, &ConntrackFilter{}, &ConntrackFilter{}) 409 if v4Match > 0 || v6Match > 0 { 410 t.Fatalf("Error, empty filter cannot match, v4:%d, v6:%d", v4Match, v6Match) 411 } 412 413 // Filter errors 414 415 // Adding same attribute should fail 416 filter := &ConntrackFilter{} 417 err := filter.AddIP(ConntrackOrigSrcIP, net.ParseIP("10.0.0.1")) 418 if err != nil { 419 t.Fatalf("Unexpected error: %v", err) 420 } 421 if err := filter.AddIP(ConntrackOrigSrcIP, net.ParseIP("10.0.0.1")); err == nil { 422 t.Fatalf("Error, it should fail adding same attribute to the filter") 423 } 424 err = filter.AddProtocol(6) 425 if err != nil { 426 t.Fatalf("Unexpected error: %v", err) 427 } 428 if err := filter.AddProtocol(17); err == nil { 429 t.Fatalf("Error, it should fail adding same attribute to the filter") 430 } 431 filter.AddPort(ConntrackOrigSrcPort, 80) 432 if err := filter.AddPort(ConntrackOrigSrcPort, 80); err == nil { 433 t.Fatalf("Error, it should fail adding same attribute to the filter") 434 } 435 436 // Can not add a Port filter without Layer 4 protocol 437 filter = &ConntrackFilter{} 438 if err := filter.AddPort(ConntrackOrigSrcPort, 80); err == nil { 439 t.Fatalf("Error, it should fail adding a port filter without a protocol") 440 } 441 442 // Can not add a Port filter if the Layer 4 protocol does not support it 443 filter = &ConntrackFilter{} 444 err = filter.AddProtocol(47) 445 if err != nil { 446 t.Fatalf("Unexpected error: %v", err) 447 } 448 if err := filter.AddPort(ConntrackOrigSrcPort, 80); err == nil { 449 t.Fatalf("Error, it should fail adding a port filter with a wrong protocol") 450 } 451 452 // Proto filter 453 filterV4 := &ConntrackFilter{} 454 err = filterV4.AddProtocol(6) 455 if err != nil { 456 t.Fatalf("Unexpected error: %v", err) 457 } 458 459 filterV6 := &ConntrackFilter{} 460 err = filterV6.AddProtocol(132) 461 if err != nil { 462 t.Fatalf("Unexpected error: %v", err) 463 } 464 465 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 466 if v4Match != 1 || v6Match != 1 { 467 t.Fatalf("Error, there should be only 1 match for TCP:%d, UDP:%d", v4Match, v6Match) 468 } 469 470 // SrcIP filter 471 filterV4 = &ConntrackFilter{} 472 err = filterV4.AddIP(ConntrackOrigSrcIP, net.ParseIP("10.0.0.1")) 473 if err != nil { 474 t.Fatalf("Unexpected error: %v", err) 475 } 476 477 filterV6 = &ConntrackFilter{} 478 err = filterV6.AddIP(ConntrackOrigSrcIP, net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee")) 479 if err != nil { 480 t.Fatalf("Unexpected error: %v", err) 481 } 482 483 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 484 if v4Match != 1 || v6Match != 1 { 485 t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) 486 } 487 488 // DstIp filter 489 filterV4 = &ConntrackFilter{} 490 err = filterV4.AddIP(ConntrackOrigDstIP, net.ParseIP("20.0.0.1")) 491 if err != nil { 492 t.Fatalf("Unexpected error: %v", err) 493 } 494 495 filterV6 = &ConntrackFilter{} 496 err = filterV6.AddIP(ConntrackOrigDstIP, net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")) 497 if err != nil { 498 t.Fatalf("Unexpected error: %v", err) 499 } 500 501 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 502 if v4Match != 1 || v6Match != 1 { 503 t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) 504 } 505 506 // SrcIP for NAT 507 filterV4 = &ConntrackFilter{} 508 err = filterV4.AddIP(ConntrackReplySrcIP, net.ParseIP("20.0.0.1")) 509 if err != nil { 510 t.Fatalf("Unexpected error: %v", err) 511 } 512 513 filterV6 = &ConntrackFilter{} 514 err = filterV6.AddIP(ConntrackReplySrcIP, net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")) 515 if err != nil { 516 t.Fatalf("Unexpected error: %v", err) 517 } 518 519 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 520 if v4Match != 1 || v6Match != 1 { 521 t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) 522 } 523 524 // DstIP for NAT 525 filterV4 = &ConntrackFilter{} 526 err = filterV4.AddIP(ConntrackReplyDstIP, net.ParseIP("192.168.1.1")) 527 if err != nil { 528 t.Fatalf("Unexpected error: %v", err) 529 } 530 531 filterV6 = &ConntrackFilter{} 532 err = filterV6.AddIP(ConntrackReplyDstIP, net.ParseIP("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")) 533 if err != nil { 534 t.Fatalf("Unexpected error: %v", err) 535 } 536 537 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 538 if v4Match != 2 || v6Match != 0 { 539 t.Fatalf("Error, there should be an exact match, v4:%d, v6:%d", v4Match, v6Match) 540 } 541 542 // AnyIp for Nat 543 filterV4 = &ConntrackFilter{} 544 err = filterV4.AddIP(ConntrackReplyAnyIP, net.ParseIP("192.168.1.1")) 545 if err != nil { 546 t.Fatalf("Unexpected error: %v", err) 547 } 548 549 filterV6 = &ConntrackFilter{} 550 err = filterV6.AddIP(ConntrackReplyAnyIP, net.ParseIP("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee")) 551 if err != nil { 552 t.Fatalf("Unexpected error: %v", err) 553 } 554 555 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 556 if v4Match != 2 || v6Match != 1 { 557 t.Fatalf("Error, there should be an exact match, v4:%d, v6:%d", v4Match, v6Match) 558 } 559 560 // SrcIPNet filter 561 filterV4 = &ConntrackFilter{} 562 ipNet, err := ParseIPNet("10.0.0.0/12") 563 if err != nil { 564 t.Fatalf("Unexpected error: %v", err) 565 } 566 err = filterV4.AddIPNet(ConntrackOrigSrcIP, ipNet) 567 if err != nil { 568 t.Fatalf("Unexpected error: %v", err) 569 } 570 571 filterV6 = &ConntrackFilter{} 572 ipNet, err = ParseIPNet("eeee:eeee:eeee:eeee::/64") 573 if err != nil { 574 t.Fatalf("Unexpected error: %v", err) 575 } 576 err = filterV6.AddIPNet(ConntrackOrigSrcIP, ipNet) 577 if err != nil { 578 t.Fatalf("Unexpected error: %v", err) 579 } 580 581 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 582 if v4Match != 2 || v6Match != 1 { 583 t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) 584 } 585 586 // DstIpNet filter 587 filterV4 = &ConntrackFilter{} 588 ipNet, err = ParseIPNet("20.0.0.0/12") 589 if err != nil { 590 t.Fatalf("Unexpected error: %v", err) 591 } 592 err = filterV4.AddIPNet(ConntrackOrigDstIP, ipNet) 593 if err != nil { 594 t.Fatalf("Unexpected error: %v", err) 595 } 596 597 filterV6 = &ConntrackFilter{} 598 ipNet, err = ParseIPNet("dddd:dddd:dddd:dddd::/64") 599 if err != nil { 600 t.Fatalf("Unexpected error: %v", err) 601 } 602 err = filterV6.AddIPNet(ConntrackOrigDstIP, ipNet) 603 if err != nil { 604 t.Fatalf("Unexpected error: %v", err) 605 } 606 607 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 608 if v4Match != 2 || v6Match != 1 { 609 t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) 610 } 611 612 // SrcIPNet for NAT 613 filterV4 = &ConntrackFilter{} 614 ipNet, err = ParseIPNet("20.0.0.0/12") 615 if err != nil { 616 t.Fatalf("Unexpected error: %v", err) 617 } 618 err = filterV4.AddIPNet(ConntrackReplySrcIP, ipNet) 619 if err != nil { 620 t.Fatalf("Unexpected error: %v", err) 621 } 622 623 filterV6 = &ConntrackFilter{} 624 ipNet, err = ParseIPNet("dddd:dddd:dddd:dddd::/64") 625 if err != nil { 626 t.Fatalf("Unexpected error: %v", err) 627 } 628 err = filterV6.AddIPNet(ConntrackReplySrcIP, ipNet) 629 if err != nil { 630 t.Fatalf("Unexpected error: %v", err) 631 } 632 633 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 634 if v4Match != 2 || v6Match != 1 { 635 t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) 636 } 637 638 // DstIPNet for NAT 639 filterV4 = &ConntrackFilter{} 640 ipNet, err = ParseIPNet("192.168.0.0/12") 641 if err != nil { 642 t.Fatalf("Unexpected error: %v", err) 643 } 644 err = filterV4.AddIPNet(ConntrackReplyDstIP, ipNet) 645 if err != nil { 646 t.Fatalf("Unexpected error: %v", err) 647 } 648 649 filterV6 = &ConntrackFilter{} 650 ipNet, err = ParseIPNet("dddd:dddd:dddd:dddd::/64") 651 if err != nil { 652 t.Fatalf("Unexpected error: %v", err) 653 } 654 err = filterV6.AddIPNet(ConntrackReplyDstIP, ipNet) 655 if err != nil { 656 t.Fatalf("Unexpected error: %v", err) 657 } 658 659 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 660 if v4Match != 2 || v6Match != 0 { 661 t.Fatalf("Error, there should be an exact match, v4:%d, v6:%d", v4Match, v6Match) 662 } 663 664 // AnyIpNet for Nat 665 filterV4 = &ConntrackFilter{} 666 ipNet, err = ParseIPNet("192.168.0.0/12") 667 if err != nil { 668 t.Fatalf("Unexpected error: %v", err) 669 } 670 err = filterV4.AddIPNet(ConntrackReplyAnyIP, ipNet) 671 if err != nil { 672 t.Fatalf("Unexpected error: %v", err) 673 } 674 675 filterV6 = &ConntrackFilter{} 676 ipNet, err = ParseIPNet("eeee:eeee:eeee:eeee::/64") 677 if err != nil { 678 t.Fatalf("Unexpected error: %v", err) 679 } 680 err = filterV6.AddIPNet(ConntrackReplyAnyIP, ipNet) 681 if err != nil { 682 t.Fatalf("Unexpected error: %v", err) 683 } 684 685 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 686 if v4Match != 2 || v6Match != 1 { 687 t.Fatalf("Error, there should be an exact match, v4:%d, v6:%d", v4Match, v6Match) 688 } 689 // SrcPort filter 690 filterV4 = &ConntrackFilter{} 691 err = filterV4.AddProtocol(6) 692 if err != nil { 693 t.Fatalf("Unexpected error: %v", err) 694 } 695 err = filterV4.AddPort(ConntrackOrigSrcPort, 5000) 696 if err != nil { 697 t.Fatalf("Unexpected error: %v", err) 698 } 699 700 filterV6 = &ConntrackFilter{} 701 err = filterV6.AddProtocol(132) 702 if err != nil { 703 t.Fatalf("Unexpected error: %v", err) 704 } 705 err = filterV6.AddPort(ConntrackOrigSrcPort, 1000) 706 if err != nil { 707 t.Fatalf("Unexpected error: %v", err) 708 } 709 710 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 711 if v4Match != 1 || v6Match != 1 { 712 t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) 713 } 714 715 // DstPort filter 716 filterV4 = &ConntrackFilter{} 717 err = filterV4.AddProtocol(6) 718 if err != nil { 719 t.Fatalf("Unexpected error: %v", err) 720 } 721 err = filterV4.AddPort(ConntrackOrigDstPort, 6000) 722 if err != nil { 723 t.Fatalf("Unexpected error: %v", err) 724 } 725 726 filterV6 = &ConntrackFilter{} 727 err = filterV6.AddProtocol(132) 728 if err != nil { 729 t.Fatalf("Unexpected error: %v", err) 730 } 731 err = filterV6.AddPort(ConntrackOrigDstPort, 2000) 732 if err != nil { 733 t.Fatalf("Unexpected error: %v", err) 734 } 735 736 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 737 if v4Match != 1 || v6Match != 1 { 738 t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) 739 } 740 741 // Labels filter 742 filterV4 = &ConntrackFilter{} 743 var labels [][]byte 744 labels = append(labels, []byte{3, 4, 61, 141, 207, 170}) 745 labels = append(labels, []byte{0x2}) 746 err = filterV4.AddLabels(ConntrackMatchLabels, labels) 747 if err != nil { 748 t.Fatalf("Unexpected error: %v", err) 749 } 750 751 filterV6 = &ConntrackFilter{} 752 err = filterV6.AddLabels(ConntrackUnmatchLabels, labels) 753 if err != nil { 754 t.Fatalf("Unexpected error: %v", err) 755 } 756 757 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 758 if v4Match != 1 || v6Match != 0 { 759 t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) 760 } 761 762 filterV4 = &ConntrackFilter{} 763 err = filterV4.AddZone(200) 764 if err != nil { 765 t.Fatalf("Unexpected error: %v", err) 766 } 767 filterV6 = &ConntrackFilter{} 768 v4Match, v6Match = applyFilter(flowList, filterV4, filterV6) 769 if v4Match != 2 || v6Match != 0 { 770 t.Fatalf("Error, there should be only 1 match, v4:%d, v6:%d", v4Match, v6Match) 771 } 772 } 773 774 func TestParseRawData(t *testing.T) { 775 if nl.NativeEndian() == binary.BigEndian { 776 t.Skip("testdata expect little-endian test executor") 777 } 778 os.Setenv("TZ", "") // print timestamps in UTC 779 tests := []struct { 780 testname string 781 rawData []byte 782 expConntrackFlow string 783 }{ 784 { 785 testname: "UDP conntrack", 786 rawData: []byte{ 787 /* Nfgenmsg header */ 788 2, 0, 0, 0, 789 /* >> nested CTA_TUPLE_ORIG */ 790 52, 0, 1, 128, 791 /* >>>> nested CTA_TUPLE_IP */ 792 20, 0, 1, 128, 793 /* >>>>>> CTA_IP_V4_SRC */ 794 8, 0, 1, 0, 795 192, 168, 0, 10, 796 /* >>>>>> CTA_IP_V4_DST */ 797 8, 0, 2, 0, 798 192, 168, 0, 3, 799 /* >>>>>> nested proto info */ 800 28, 0, 2, 128, 801 /* >>>>>>>> CTA_PROTO_NUM */ 802 5, 0, 1, 0, 803 17, 0, 0, 0, 804 /* >>>>>>>> CTA_PROTO_SRC_PORT */ 805 6, 0, 2, 0, 806 189, 1, 0, 0, 807 /* >>>>>>>> CTA_PROTO_DST_PORT */ 808 6, 0, 3, 0, 809 0, 53, 0, 0, 810 /* >> CTA_TUPLE_REPLY */ 811 52, 0, 2, 128, 812 /* >>>> nested CTA_TUPLE_IP */ 813 20, 0, 1, 128, 814 /* >>>>>> CTA_IP_V4_SRC */ 815 8, 0, 1, 0, 816 192, 168, 0, 3, 817 /* >>>>>> CTA_IP_V4_DST */ 818 8, 0, 2, 0, 819 192, 168, 0, 10, 820 /* >>>>>> nested proto info */ 821 28, 0, 2, 128, 822 /* >>>>>>>> CTA_PROTO_NUM */ 823 5, 0, 1, 0, 824 17, 0, 0, 0, 825 /* >>>>>>>> CTA_PROTO_SRC_PORT */ 826 6, 0, 2, 0, 827 0, 53, 0, 0, 828 /* >>>>>>>> CTA_PROTO_DST_PORT */ 829 6, 0, 3, 0, 830 189, 1, 0, 0, 831 /* >> CTA_STATUS */ 832 8, 0, 3, 0, 833 0, 0, 1, 138, 834 /* >> CTA_MARK */ 835 8, 0, 8, 0, 836 0, 0, 0, 5, 837 /* >> CTA_ID */ 838 8, 0, 12, 0, 839 81, 172, 253, 151, 840 /* >> CTA_USE */ 841 8, 0, 11, 0, 842 0, 0, 0, 1, 843 /* >> CTA_TIMEOUT */ 844 8, 0, 7, 0, 845 0, 0, 0, 32, 846 /* >> nested CTA_COUNTERS_ORIG */ 847 28, 0, 9, 128, 848 /* >>>> CTA_COUNTERS_PACKETS */ 849 12, 0, 1, 0, 850 0, 0, 0, 0, 0, 0, 0, 1, 851 /* >>>> CTA_COUNTERS_BYTES */ 852 12, 0, 2, 0, 853 0, 0, 0, 0, 0, 0, 0, 55, 854 /* >> nested CTA_COUNTERS_REPLY */ 855 28, 0, 10, 128, 856 /* >>>> CTA_COUNTERS_PACKETS */ 857 12, 0, 1, 0, 858 0, 0, 0, 0, 0, 0, 0, 1, 859 /* >>>> CTA_COUNTERS_BYTES */ 860 12, 0, 2, 0, 861 0, 0, 0, 0, 0, 0, 0, 71, 862 /* >> nested CTA_TIMESTAMP */ 863 16, 0, 20, 128, 864 /* >>>> CTA_TIMESTAMP_START */ 865 12, 0, 1, 0, 866 22, 134, 80, 142, 230, 127, 74, 166, 867 /* >> CTA_LABELS */ 868 20, 0, 22, 0, 869 0, 0, 0, 0, 5, 0, 18, 172, 66, 2, 1, 0, 0, 0, 0, 0}, 870 expConntrackFlow: "udp\t17 src=192.168.0.10 dst=192.168.0.3 sport=48385 dport=53 packets=1 bytes=55\t" + 871 "src=192.168.0.3 dst=192.168.0.10 sport=53 dport=48385 packets=1 bytes=71 mark=0x5 labels=0x00000000050012ac4202010000000000 " + 872 "start=2021-06-07 13:41:30.39632247 +0000 UTC stop=1970-01-01 00:00:00 +0000 UTC timeout=32(sec)", 873 }, 874 { 875 testname: "TCP conntrack", 876 rawData: []byte{ 877 /* Nfgenmsg header */ 878 2, 0, 0, 0, 879 /* >> nested CTA_TUPLE_ORIG */ 880 52, 0, 1, 128, 881 /* >>>> nested CTA_TUPLE_IP */ 882 20, 0, 1, 128, 883 /* >>>>>> CTA_IP_V4_SRC */ 884 8, 0, 1, 0, 885 192, 168, 0, 10, 886 /* >>>>>> CTA_IP_V4_DST */ 887 8, 0, 2, 0, 888 192, 168, 77, 73, 889 /* >>>>>> nested proto info */ 890 28, 0, 2, 128, 891 /* >>>>>>>> CTA_PROTO_NUM */ 892 5, 0, 1, 0, 893 6, 0, 0, 0, 894 /* >>>>>>>> CTA_PROTO_SRC_PORT */ 895 6, 0, 2, 0, 896 166, 129, 0, 0, 897 /* >>>>>>>> CTA_PROTO_DST_PORT */ 898 6, 0, 3, 0, 899 13, 5, 0, 0, 900 /* >> CTA_TUPLE_REPLY */ 901 52, 0, 2, 128, 902 /* >>>> nested CTA_TUPLE_IP */ 903 20, 0, 1, 128, 904 /* >>>>>> CTA_IP_V4_SRC */ 905 8, 0, 1, 0, 906 192, 168, 77, 73, 907 /* >>>>>> CTA_IP_V4_DST */ 908 8, 0, 2, 0, 909 192, 168, 0, 10, 910 /* >>>>>> nested proto info */ 911 28, 0, 2, 128, 912 /* >>>>>>>> CTA_PROTO_NUM */ 913 5, 0, 1, 0, 914 6, 0, 0, 0, 915 /* >>>>>>>> CTA_PROTO_SRC_PORT */ 916 6, 0, 2, 0, 917 13, 5, 0, 0, 918 /* >>>>>>>> CTA_PROTO_DST_PORT */ 919 6, 0, 3, 0, 920 166, 129, 0, 0, 921 /* >> CTA_STATUS */ 922 8, 0, 3, 0, 923 0, 0, 1, 142, 924 /* >> CTA_MARK */ 925 8, 0, 8, 0, 926 0, 0, 0, 5, 927 /* >> CTA_ID */ 928 8, 0, 12, 0, 929 177, 65, 179, 133, 930 /* >> CTA_USE */ 931 8, 0, 11, 0, 932 0, 0, 0, 1, 933 /* >> CTA_TIMEOUT */ 934 8, 0, 7, 0, 935 0, 0, 0, 152, 936 /* >> CTA_PROTOINFO */ 937 48, 0, 4, 128, 938 44, 0, 1, 128, 939 5, 0, 1, 0, 8, 0, 0, 0, 940 5, 0, 2, 0, 0, 0, 0, 0, 941 5, 0, 3, 0, 0, 0, 0, 0, 942 6, 0, 4, 0, 39, 0, 0, 0, 943 6, 0, 5, 0, 32, 0, 0, 0, 944 /* >> nested CTA_COUNTERS_ORIG */ 945 28, 0, 9, 128, 946 /* >>>> CTA_COUNTERS_PACKETS */ 947 12, 0, 1, 0, 948 0, 0, 0, 0, 0, 0, 0, 11, 949 /* >>>> CTA_COUNTERS_BYTES */ 950 12, 0, 2, 0, 951 0, 0, 0, 0, 0, 0, 7, 122, 952 /* >> nested CTA_COUNTERS_REPLY */ 953 28, 0, 10, 128, 954 /* >>>> CTA_COUNTERS_PACKETS */ 955 12, 0, 1, 0, 956 0, 0, 0, 0, 0, 0, 0, 10, 957 /* >>>> CTA_COUNTERS_BYTES */ 958 12, 0, 2, 0, 959 0, 0, 0, 0, 0, 0, 7, 66, 960 /* >> CTA_ZONE */ 961 8, 0, 18, 0, 962 0, 100, 0, 0, 963 /* >> nested CTA_TIMESTAMP */ 964 16, 0, 20, 128, 965 /* >>>> CTA_TIMESTAMP_START */ 966 12, 0, 1, 0, 967 22, 134, 80, 175, 134, 10, 182, 221}, 968 expConntrackFlow: "tcp\t6 src=192.168.0.10 dst=192.168.77.73 sport=42625 dport=3333 packets=11 bytes=1914\t" + 969 "src=192.168.77.73 dst=192.168.0.10 sport=3333 dport=42625 packets=10 bytes=1858 mark=0x5 zone=100 " + 970 "start=2021-06-07 13:43:50.511990493 +0000 UTC stop=1970-01-01 00:00:00 +0000 UTC timeout=152(sec)", 971 }, 972 } 973 974 for _, test := range tests { 975 t.Run(test.testname, func(t *testing.T) { 976 conntrackFlow := parseRawData(test.rawData) 977 if conntrackFlow.String() != test.expConntrackFlow { 978 t.Errorf("expected conntrack flow:\n\t%q\ngot conntrack flow:\n\t%q", 979 test.expConntrackFlow, conntrackFlow) 980 } 981 }) 982 } 983 } 984 985 // TestConntrackUpdateV4 first tries to update a non-existant IPv4 conntrack and asserts that an error occurs. 986 // It then creates a conntrack entry using and adjacent API method (ConntrackCreate), and attempts to update the value of the created conntrack. 987 func TestConntrackUpdateV4(t *testing.T) { 988 // Print timestamps in UTC 989 os.Setenv("TZ", "") 990 991 requiredModules := []string{"nf_conntrack", "nf_conntrack_netlink"} 992 k, m, err := KernelVersion() 993 if err != nil { 994 t.Fatal(err) 995 } 996 // Conntrack l3proto was unified since 4.19 997 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f 998 if k < 4 || k == 4 && m < 19 { 999 requiredModules = append(requiredModules, "nf_conntrack_ipv4") 1000 } 1001 // Implicitly skips test if not root: 1002 nsStr, teardown := setUpNamedNetlinkTestWithKModule(t, requiredModules...) 1003 defer teardown() 1004 1005 ns, err := netns.GetFromName(nsStr) 1006 if err != nil { 1007 t.Fatalf("couldn't get handle to generated namespace: %s", err) 1008 } 1009 1010 h, err := NewHandleAt(ns, nl.FAMILY_V4) 1011 if err != nil { 1012 t.Fatalf("failed to create netlink handle: %s", err) 1013 } 1014 1015 flow := ConntrackFlow{ 1016 FamilyType: FAMILY_V4, 1017 Forward: IPTuple{ 1018 SrcIP: net.IP{234,234,234,234}, 1019 DstIP: net.IP{123,123,123,123}, 1020 SrcPort: 48385, 1021 DstPort: 53, 1022 Protocol: unix.IPPROTO_TCP, 1023 }, 1024 Reverse: IPTuple{ 1025 SrcIP: net.IP{123,123,123,123}, 1026 DstIP: net.IP{234,234,234,234}, 1027 SrcPort: 53, 1028 DstPort: 48385, 1029 Protocol: unix.IPPROTO_TCP, 1030 }, 1031 // No point checking equivalence of timeout, but value must 1032 // be reasonable to allow for a potentially slow subsequent read. 1033 TimeOut: 100, 1034 Mark: 12, 1035 ProtoInfo: &ProtoInfoTCP{ 1036 State: nl.TCP_CONNTRACK_SYN_SENT2, 1037 }, 1038 } 1039 1040 err = h.ConntrackUpdate(ConntrackTable, nl.FAMILY_V4, &flow) 1041 if err == nil { 1042 t.Fatalf("expected an error to occur when trying to update a non-existant conntrack: %+v", flow) 1043 } 1044 1045 err = h.ConntrackCreate(ConntrackTable, nl.FAMILY_V4, &flow) 1046 if err != nil { 1047 t.Fatalf("failed to insert conntrack: %s", err) 1048 } 1049 1050 flows, err := h.ConntrackTableList(ConntrackTable, nl.FAMILY_V4) 1051 if err != nil { 1052 t.Fatalf("failed to list conntracks following successful insert: %s", err) 1053 } 1054 1055 filter := ConntrackFilter{ 1056 ipNetFilter: map[ConntrackFilterType]*net.IPNet{ 1057 ConntrackOrigSrcIP: NewIPNet(flow.Forward.SrcIP), 1058 ConntrackOrigDstIP: NewIPNet(flow.Forward.DstIP), 1059 ConntrackReplySrcIP: NewIPNet(flow.Reverse.SrcIP), 1060 ConntrackReplyDstIP: NewIPNet(flow.Reverse.DstIP), 1061 }, 1062 portFilter: map[ConntrackFilterType]uint16{ 1063 ConntrackOrigSrcPort: flow.Forward.SrcPort, 1064 ConntrackOrigDstPort: flow.Forward.DstPort, 1065 }, 1066 protoFilter:unix.IPPROTO_TCP, 1067 } 1068 1069 var match *ConntrackFlow 1070 for _, f := range flows { 1071 if filter.MatchConntrackFlow(f) { 1072 match = f 1073 break 1074 } 1075 } 1076 1077 if match == nil { 1078 t.Fatalf("Didn't find any matching conntrack entries for original flow: %+v\n Filter used: %+v", flow, filter) 1079 } else { 1080 t.Logf("Found entry in conntrack table matching original flow: %+v labels=%+v", match, match.Labels) 1081 } 1082 checkFlowsEqual(t, &flow, match) 1083 checkProtoInfosEqual(t, flow.ProtoInfo, match.ProtoInfo) 1084 1085 // Change the conntrack and update the kernel entry. 1086 flow.Mark = 10 1087 flow.ProtoInfo = &ProtoInfoTCP{ 1088 State: nl.TCP_CONNTRACK_ESTABLISHED, 1089 } 1090 err = h.ConntrackUpdate(ConntrackTable, nl.FAMILY_V4, &flow) 1091 if err != nil { 1092 t.Fatalf("failed to update conntrack with new mark: %s", err) 1093 } 1094 1095 // Look for updated conntrack. 1096 flows, err = h.ConntrackTableList(ConntrackTable, nl.FAMILY_V4) 1097 if err != nil { 1098 t.Fatalf("failed to list conntracks following successful update: %s", err) 1099 } 1100 1101 var updatedMatch *ConntrackFlow 1102 for _, f := range flows { 1103 if filter.MatchConntrackFlow(f) { 1104 updatedMatch = f 1105 break 1106 } 1107 } 1108 if updatedMatch == nil { 1109 t.Fatalf("Didn't find any matching conntrack entries for updated flow: %+v\n Filter used: %+v", flow, filter) 1110 } else { 1111 t.Logf("Found entry in conntrack table matching updated flow: %+v labels=%+v", updatedMatch, updatedMatch.Labels) 1112 } 1113 1114 checkFlowsEqual(t, &flow, updatedMatch) 1115 checkProtoInfosEqual(t, flow.ProtoInfo, updatedMatch.ProtoInfo) 1116 } 1117 1118 // TestConntrackUpdateV6 first tries to update a non-existant IPv6 conntrack and asserts that an error occurs. 1119 // It then creates a conntrack entry using and adjacent API method (ConntrackCreate), and attempts to update the value of the created conntrack. 1120 func TestConntrackUpdateV6(t *testing.T) { 1121 // Print timestamps in UTC 1122 os.Setenv("TZ", "") 1123 1124 requiredModules := []string{"nf_conntrack", "nf_conntrack_netlink"} 1125 k, m, err := KernelVersion() 1126 if err != nil { 1127 t.Fatal(err) 1128 } 1129 // Conntrack l3proto was unified since 4.19 1130 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f 1131 if k < 4 || k == 4 && m < 19 { 1132 requiredModules = append(requiredModules, "nf_conntrack_ipv4") 1133 } 1134 // Implicitly skips test if not root: 1135 nsStr, teardown := setUpNamedNetlinkTestWithKModule(t, requiredModules...) 1136 defer teardown() 1137 1138 ns, err := netns.GetFromName(nsStr) 1139 if err != nil { 1140 t.Fatalf("couldn't get handle to generated namespace: %s", err) 1141 } 1142 1143 h, err := NewHandleAt(ns, nl.FAMILY_V6) 1144 if err != nil { 1145 t.Fatalf("failed to create netlink handle: %s", err) 1146 } 1147 1148 flow := ConntrackFlow{ 1149 FamilyType: FAMILY_V6, 1150 Forward: IPTuple{ 1151 SrcIP: net.ParseIP("2001:db8::68"), 1152 DstIP: net.ParseIP("2001:db9::32"), 1153 SrcPort: 48385, 1154 DstPort: 53, 1155 Protocol: unix.IPPROTO_TCP, 1156 }, 1157 Reverse: IPTuple{ 1158 SrcIP: net.ParseIP("2001:db9::32"), 1159 DstIP: net.ParseIP("2001:db8::68"), 1160 SrcPort: 53, 1161 DstPort: 48385, 1162 Protocol: unix.IPPROTO_TCP, 1163 }, 1164 // No point checking equivalence of timeout, but value must 1165 // be reasonable to allow for a potentially slow subsequent read. 1166 TimeOut: 100, 1167 Mark: 12, 1168 ProtoInfo: &ProtoInfoTCP{ 1169 State: nl.TCP_CONNTRACK_SYN_SENT2, 1170 }, 1171 } 1172 1173 err = h.ConntrackUpdate(ConntrackTable, nl.FAMILY_V6, &flow) 1174 if err == nil { 1175 t.Fatalf("expected an error to occur when trying to update a non-existant conntrack: %+v", flow) 1176 } 1177 1178 err = h.ConntrackCreate(ConntrackTable, nl.FAMILY_V6, &flow) 1179 if err != nil { 1180 t.Fatalf("failed to insert conntrack: %s", err) 1181 } 1182 1183 flows, err := h.ConntrackTableList(ConntrackTable, nl.FAMILY_V6) 1184 if err != nil { 1185 t.Fatalf("failed to list conntracks following successful insert: %s", err) 1186 } 1187 1188 filter := ConntrackFilter{ 1189 ipNetFilter: map[ConntrackFilterType]*net.IPNet{ 1190 ConntrackOrigSrcIP: NewIPNet(flow.Forward.SrcIP), 1191 ConntrackOrigDstIP: NewIPNet(flow.Forward.DstIP), 1192 ConntrackReplySrcIP: NewIPNet(flow.Reverse.SrcIP), 1193 ConntrackReplyDstIP: NewIPNet(flow.Reverse.DstIP), 1194 }, 1195 portFilter: map[ConntrackFilterType]uint16{ 1196 ConntrackOrigSrcPort: flow.Forward.SrcPort, 1197 ConntrackOrigDstPort: flow.Forward.DstPort, 1198 }, 1199 protoFilter:unix.IPPROTO_TCP, 1200 } 1201 1202 var match *ConntrackFlow 1203 for _, f := range flows { 1204 if filter.MatchConntrackFlow(f) { 1205 match = f 1206 break 1207 } 1208 } 1209 1210 if match == nil { 1211 t.Fatalf("didn't find any matching conntrack entries for original flow: %+v\n Filter used: %+v", flow, filter) 1212 } else { 1213 t.Logf("found entry in conntrack table matching original flow: %+v labels=%+v", match, match.Labels) 1214 } 1215 checkFlowsEqual(t, &flow, match) 1216 checkProtoInfosEqual(t, flow.ProtoInfo, match.ProtoInfo) 1217 1218 // Change the conntrack and update the kernel entry. 1219 flow.Mark = 10 1220 flow.ProtoInfo = &ProtoInfoTCP{ 1221 State: nl.TCP_CONNTRACK_ESTABLISHED, 1222 } 1223 err = h.ConntrackUpdate(ConntrackTable, nl.FAMILY_V6, &flow) 1224 if err != nil { 1225 t.Fatalf("failed to update conntrack with new mark: %s", err) 1226 } 1227 1228 // Look for updated conntrack. 1229 flows, err = h.ConntrackTableList(ConntrackTable, nl.FAMILY_V6) 1230 if err != nil { 1231 t.Fatalf("failed to list conntracks following successful update: %s", err) 1232 } 1233 1234 var updatedMatch *ConntrackFlow 1235 for _, f := range flows { 1236 if filter.MatchConntrackFlow(f) { 1237 updatedMatch = f 1238 break 1239 } 1240 } 1241 if updatedMatch == nil { 1242 t.Fatalf("didn't find any matching conntrack entries for updated flow: %+v\n Filter used: %+v", flow, filter) 1243 } else { 1244 t.Logf("found entry in conntrack table matching updated flow: %+v labels=%+v", updatedMatch, updatedMatch.Labels) 1245 } 1246 1247 checkFlowsEqual(t, &flow, updatedMatch) 1248 checkProtoInfosEqual(t, flow.ProtoInfo, updatedMatch.ProtoInfo) 1249 } 1250 1251 func TestConntrackCreateV4(t *testing.T) { 1252 // Print timestamps in UTC 1253 os.Setenv("TZ", "") 1254 1255 requiredModules := []string{"nf_conntrack", "nf_conntrack_netlink"} 1256 k, m, err := KernelVersion() 1257 if err != nil { 1258 t.Fatal(err) 1259 } 1260 // Conntrack l3proto was unified since 4.19 1261 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f 1262 if k < 4 || k == 4 && m < 19 { 1263 requiredModules = append(requiredModules, "nf_conntrack_ipv4") 1264 } 1265 // Implicitly skips test if not root: 1266 nsStr, teardown := setUpNamedNetlinkTestWithKModule(t, requiredModules...) 1267 defer teardown() 1268 1269 ns, err := netns.GetFromName(nsStr) 1270 if err != nil { 1271 t.Fatalf("couldn't get handle to generated namespace: %s", err) 1272 } 1273 1274 h, err := NewHandleAt(ns, nl.FAMILY_V4) 1275 if err != nil { 1276 t.Fatalf("failed to create netlink handle: %s", err) 1277 } 1278 1279 flow := ConntrackFlow{ 1280 FamilyType: FAMILY_V4, 1281 Forward: IPTuple{ 1282 SrcIP: net.IP{234,234,234,234}, 1283 DstIP: net.IP{123,123,123,123}, 1284 SrcPort: 48385, 1285 DstPort: 53, 1286 Protocol: unix.IPPROTO_TCP, 1287 }, 1288 Reverse: IPTuple{ 1289 SrcIP: net.IP{123,123,123,123}, 1290 DstIP: net.IP{234,234,234,234}, 1291 SrcPort: 53, 1292 DstPort: 48385, 1293 Protocol: unix.IPPROTO_TCP, 1294 }, 1295 // No point checking equivalence of timeout, but value must 1296 // be reasonable to allow for a potentially slow subsequent read. 1297 TimeOut: 100, 1298 Mark: 12, 1299 ProtoInfo: &ProtoInfoTCP{ 1300 State: nl.TCP_CONNTRACK_ESTABLISHED, 1301 }, 1302 } 1303 1304 err = h.ConntrackCreate(ConntrackTable, nl.FAMILY_V4, &flow) 1305 if err != nil { 1306 t.Fatalf("failed to insert conntrack: %s", err) 1307 } 1308 1309 flows, err := h.ConntrackTableList(ConntrackTable, nl.FAMILY_V4) 1310 if err != nil { 1311 t.Fatalf("failed to list conntracks following successful insert: %s", err) 1312 } 1313 1314 filter := ConntrackFilter{ 1315 ipNetFilter: map[ConntrackFilterType]*net.IPNet{ 1316 ConntrackOrigSrcIP: NewIPNet(flow.Forward.SrcIP), 1317 ConntrackOrigDstIP: NewIPNet(flow.Forward.DstIP), 1318 ConntrackReplySrcIP: NewIPNet(flow.Reverse.SrcIP), 1319 ConntrackReplyDstIP: NewIPNet(flow.Reverse.DstIP), 1320 }, 1321 portFilter: map[ConntrackFilterType]uint16{ 1322 ConntrackOrigSrcPort: flow.Forward.SrcPort, 1323 ConntrackOrigDstPort: flow.Forward.DstPort, 1324 }, 1325 protoFilter:unix.IPPROTO_TCP, 1326 } 1327 1328 var match *ConntrackFlow 1329 for _, f := range flows { 1330 if filter.MatchConntrackFlow(f) { 1331 match = f 1332 break 1333 } 1334 } 1335 1336 if match == nil { 1337 t.Fatalf("didn't find any matching conntrack entries for original flow: %+v\n Filter used: %+v", flow, filter) 1338 } else { 1339 t.Logf("Found entry in conntrack table matching original flow: %+v labels=%+v", match, match.Labels) 1340 } 1341 1342 checkFlowsEqual(t, &flow, match) 1343 checkProtoInfosEqual(t, flow.ProtoInfo, match.ProtoInfo) 1344 } 1345 1346 func TestConntrackCreateV6(t *testing.T) { 1347 // Print timestamps in UTC 1348 os.Setenv("TZ", "") 1349 1350 requiredModules := []string{"nf_conntrack", "nf_conntrack_netlink"} 1351 k, m, err := KernelVersion() 1352 if err != nil { 1353 t.Fatal(err) 1354 } 1355 // Conntrack l3proto was unified since 4.19 1356 // https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f 1357 if k < 4 || k == 4 && m < 19 { 1358 requiredModules = append(requiredModules, "nf_conntrack_ipv4") 1359 } 1360 // Implicitly skips test if not root: 1361 nsStr, teardown := setUpNamedNetlinkTestWithKModule(t, requiredModules...) 1362 defer teardown() 1363 1364 ns, err := netns.GetFromName(nsStr) 1365 if err != nil { 1366 t.Fatalf("couldn't get handle to generated namespace: %s", err) 1367 } 1368 1369 h, err := NewHandleAt(ns, nl.FAMILY_V6) 1370 if err != nil { 1371 t.Fatalf("failed to create netlink handle: %s", err) 1372 } 1373 1374 flow := ConntrackFlow{ 1375 FamilyType: FAMILY_V6, 1376 Forward: IPTuple{ 1377 SrcIP: net.ParseIP("2001:db8::68"), 1378 DstIP: net.ParseIP("2001:db9::32"), 1379 SrcPort: 48385, 1380 DstPort: 53, 1381 Protocol: unix.IPPROTO_TCP, 1382 }, 1383 Reverse: IPTuple{ 1384 SrcIP: net.ParseIP("2001:db9::32"), 1385 DstIP: net.ParseIP("2001:db8::68"), 1386 SrcPort: 53, 1387 DstPort: 48385, 1388 Protocol: unix.IPPROTO_TCP, 1389 }, 1390 // No point checking equivalence of timeout, but value must 1391 // be reasonable to allow for a potentially slow subsequent read. 1392 TimeOut: 100, 1393 Mark: 12, 1394 ProtoInfo: &ProtoInfoTCP{ 1395 State: nl.TCP_CONNTRACK_ESTABLISHED, 1396 }, 1397 } 1398 1399 err = h.ConntrackCreate(ConntrackTable, nl.FAMILY_V6, &flow) 1400 if err != nil { 1401 t.Fatalf("failed to insert conntrack: %s", err) 1402 } 1403 1404 flows, err := h.ConntrackTableList(ConntrackTable, nl.FAMILY_V6) 1405 if err != nil { 1406 t.Fatalf("failed to list conntracks following successful insert: %s", err) 1407 } 1408 1409 filter := ConntrackFilter{ 1410 ipNetFilter: map[ConntrackFilterType]*net.IPNet{ 1411 ConntrackOrigSrcIP: NewIPNet(flow.Forward.SrcIP), 1412 ConntrackOrigDstIP: NewIPNet(flow.Forward.DstIP), 1413 ConntrackReplySrcIP: NewIPNet(flow.Reverse.SrcIP), 1414 ConntrackReplyDstIP: NewIPNet(flow.Reverse.DstIP), 1415 }, 1416 portFilter: map[ConntrackFilterType]uint16{ 1417 ConntrackOrigSrcPort: flow.Forward.SrcPort, 1418 ConntrackOrigDstPort: flow.Forward.DstPort, 1419 }, 1420 protoFilter:unix.IPPROTO_TCP, 1421 } 1422 1423 var match *ConntrackFlow 1424 for _, f := range flows { 1425 if filter.MatchConntrackFlow(f) { 1426 match = f 1427 break 1428 } 1429 } 1430 1431 if match == nil { 1432 t.Fatalf("Didn't find any matching conntrack entries for original flow: %+v\n Filter used: %+v", flow, filter) 1433 } else { 1434 t.Logf("Found entry in conntrack table matching original flow: %+v labels=%+v", match, match.Labels) 1435 } 1436 1437 // Other fields are implicitly correct due to the filter/match logic. 1438 if match.Mark != flow.Mark { 1439 t.Logf("Matched kernel entry did not have correct mark. Kernel: %d, Expected: %d", flow.Mark, match.Mark) 1440 t.Fail() 1441 } 1442 checkProtoInfosEqual(t, flow.ProtoInfo, match.ProtoInfo) 1443 } 1444 1445 // TestConntrackFlowToNlData generates a serialized representation of a 1446 // ConntrackFlow and runs the resulting bytes back through `parseRawData` to validate. 1447 func TestConntrackFlowToNlData(t *testing.T) { 1448 flowV4 := ConntrackFlow{ 1449 FamilyType: FAMILY_V4, 1450 Forward: IPTuple{ 1451 SrcIP: net.IP{234,234,234,234}, 1452 DstIP: net.IP{123,123,123,123}, 1453 SrcPort: 48385, 1454 DstPort: 53, 1455 Protocol: unix.IPPROTO_TCP, 1456 }, 1457 Reverse: IPTuple{ 1458 SrcIP: net.IP{123,123,123,123}, 1459 DstIP: net.IP{234,234,234,234}, 1460 SrcPort: 53, 1461 DstPort: 48385, 1462 Protocol: unix.IPPROTO_TCP, 1463 }, 1464 Mark: 5, 1465 TimeOut: 10, 1466 ProtoInfo: &ProtoInfoTCP{ 1467 State: nl.TCP_CONNTRACK_ESTABLISHED, 1468 }, 1469 } 1470 flowV6 := ConntrackFlow { 1471 FamilyType: FAMILY_V6, 1472 Forward: IPTuple{ 1473 SrcIP: net.ParseIP("2001:db8::68"), 1474 DstIP: net.ParseIP("2001:db9::32"), 1475 SrcPort: 48385, 1476 DstPort: 53, 1477 Protocol: unix.IPPROTO_TCP, 1478 }, 1479 Reverse: IPTuple{ 1480 SrcIP: net.ParseIP("2001:db9::32"), 1481 DstIP: net.ParseIP("2001:db8::68"), 1482 SrcPort: 53, 1483 DstPort: 48385, 1484 Protocol: unix.IPPROTO_TCP, 1485 }, 1486 Mark: 5, 1487 TimeOut: 10, 1488 ProtoInfo: &ProtoInfoTCP{ 1489 State: nl.TCP_CONNTRACK_ESTABLISHED, 1490 }, 1491 } 1492 1493 var bytesV4, bytesV6 []byte 1494 1495 attrsV4, err := flowV4.toNlData() 1496 if err != nil { 1497 t.Fatalf("Error converting ConntrackFlow to netlink messages: %s", err) 1498 } 1499 // Mock nfgenmsg header 1500 bytesV4 = append(bytesV4, flowV4.FamilyType,0,0,0) 1501 for _, a := range attrsV4 { 1502 bytesV4 = append(bytesV4, a.Serialize()...) 1503 } 1504 1505 attrsV6, err := flowV6.toNlData() 1506 if err != nil { 1507 t.Fatalf("Error converting ConntrackFlow to netlink messages: %s", err) 1508 } 1509 // Mock nfgenmsg header 1510 bytesV6 = append(bytesV6, flowV6.FamilyType,0,0,0) 1511 for _, a := range attrsV6 { 1512 bytesV6 = append(bytesV6, a.Serialize()...) 1513 } 1514 1515 parsedFlowV4 := parseRawData(bytesV4) 1516 checkFlowsEqual(t, &flowV4, parsedFlowV4) 1517 checkProtoInfosEqual(t, flowV4.ProtoInfo, parsedFlowV4.ProtoInfo) 1518 1519 parsedFlowV6 := parseRawData(bytesV6) 1520 checkFlowsEqual(t, &flowV6, parsedFlowV6) 1521 checkProtoInfosEqual(t, flowV6.ProtoInfo, parsedFlowV6.ProtoInfo) 1522 } 1523 1524 func checkFlowsEqual(t *testing.T, f1, f2 *ConntrackFlow) { 1525 // No point checking timeout as it will differ between reads. 1526 // Timestart and timestop may also differ. 1527 if f1.FamilyType != f2.FamilyType { 1528 t.Logf("Conntrack flow FamilyTypes differ. Tuple1: %d, Tuple2: %d.\n", f1.FamilyType, f2.FamilyType) 1529 t.Fail() 1530 } 1531 if f1.Mark != f2.Mark { 1532 t.Logf("Conntrack flow Marks differ. Tuple1: %d, Tuple2: %d.\n", f1.Mark, f2.Mark) 1533 t.Fail() 1534 } 1535 if !tuplesEqual(f1.Forward, f2.Forward) { 1536 t.Logf("Forward tuples mismatch. Tuple1 forward flow: %+v, Tuple2 forward flow: %+v.\n", f1.Forward, f2.Forward) 1537 t.Fail() 1538 } 1539 if !tuplesEqual(f1.Reverse, f2.Reverse) { 1540 t.Logf("Reverse tuples mismatch. Tuple1 reverse flow: %+v, Tuple2 reverse flow: %+v.\n", f1.Reverse, f2.Reverse) 1541 t.Fail() 1542 } 1543 } 1544 1545 func checkProtoInfosEqual(t *testing.T, p1, p2 ProtoInfo) { 1546 t.Logf("Checking protoinfo fields equal:\n\t p1: %+v\n\t p2: %+v", p1, p2) 1547 if !protoInfosEqual(p1, p2) { 1548 t.Logf("Protoinfo structs differ: P1: %+v, P2: %+v", p1, p2) 1549 t.Fail() 1550 } 1551 } 1552 1553 func protoInfosEqual(p1, p2 ProtoInfo) bool { 1554 if p1 == nil { 1555 return p2 == nil 1556 } else if p2 != nil { 1557 return p1.Protocol() == p2.Protocol() 1558 } 1559 1560 return false 1561 } 1562 1563 func tuplesEqual(t1, t2 IPTuple) bool { 1564 if t1.Bytes != t2.Bytes { 1565 return false 1566 } 1567 1568 if !t1.DstIP.Equal(t2.DstIP) { 1569 return false 1570 } 1571 1572 if !t1.SrcIP.Equal(t2.SrcIP) { 1573 return false 1574 } 1575 1576 if t1.DstPort != t2.DstPort { 1577 return false 1578 } 1579 1580 if t1.SrcPort != t2.SrcPort { 1581 return false 1582 } 1583 1584 if t1.Packets != t2.Packets { 1585 return false 1586 } 1587 1588 if t1.Protocol != t2.Protocol { 1589 return false 1590 } 1591 1592 return true 1593 }