gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/iptables/nat.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package iptables 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "net" 22 "strconv" 23 24 "golang.org/x/sys/unix" 25 "gvisor.dev/gvisor/pkg/binary" 26 "gvisor.dev/gvisor/pkg/hostarch" 27 ) 28 29 const redirectPort = 42 30 31 func init() { 32 RegisterTestCase(&NATPreRedirectUDPPort{}) 33 RegisterTestCase(&NATPreRedirectTCPPort{}) 34 RegisterTestCase(&NATPreRedirectTCPOutgoing{}) 35 RegisterTestCase(&NATOutRedirectTCPIncoming{}) 36 RegisterTestCase(&NATOutRedirectUDPPort{}) 37 RegisterTestCase(&NATOutRedirectTCPPort{}) 38 RegisterTestCase(&NATDropUDP{}) 39 RegisterTestCase(&NATAcceptAll{}) 40 RegisterTestCase(&NATPreRedirectIP{}) 41 RegisterTestCase(&NATPreDontRedirectIP{}) 42 RegisterTestCase(&NATPreRedirectInvert{}) 43 RegisterTestCase(&NATOutRedirectIP{}) 44 RegisterTestCase(&NATOutDontRedirectIP{}) 45 RegisterTestCase(&NATOutRedirectInvert{}) 46 RegisterTestCase(&NATRedirectRequiresProtocol{}) 47 RegisterTestCase(&NATLoopbackSkipsPrerouting{}) 48 RegisterTestCase(&NATPreOriginalDst{}) 49 RegisterTestCase(&NATOutOriginalDst{}) 50 RegisterTestCase(&NATPreRECVORIGDSTADDR{}) 51 RegisterTestCase(&NATOutRECVORIGDSTADDR{}) 52 RegisterTestCase(&NATPostSNATUDP{}) 53 RegisterTestCase(&NATPostSNATTCP{}) 54 RegisterTestCase(&NATOutDNAT{}) 55 RegisterTestCase(&NATOutDNATAddrOnly{}) 56 RegisterTestCase(&NATOutDNATPortOnly{}) 57 } 58 59 // NATPreRedirectUDPPort tests that packets are redirected to different port. 60 type NATPreRedirectUDPPort struct{ containerCase } 61 62 var _ TestCase = (*NATPreRedirectUDPPort)(nil) 63 64 // Name implements TestCase.Name. 65 func (*NATPreRedirectUDPPort) Name() string { 66 return "NATPreRedirectUDPPort" 67 } 68 69 // ContainerAction implements TestCase.ContainerAction. 70 func (*NATPreRedirectUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 71 if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil { 72 return err 73 } 74 75 if err := listenUDP(ctx, redirectPort, ipv6); err != nil { 76 return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %v", redirectPort, err) 77 } 78 79 return nil 80 } 81 82 // LocalAction implements TestCase.LocalAction. 83 func (*NATPreRedirectUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 84 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 85 } 86 87 // NATPreRedirectTCPPort tests that connections are redirected on specified ports. 88 type NATPreRedirectTCPPort struct{ baseCase } 89 90 var _ TestCase = (*NATPreRedirectTCPPort)(nil) 91 92 // Name implements TestCase.Name. 93 func (*NATPreRedirectTCPPort) Name() string { 94 return "NATPreRedirectTCPPort" 95 } 96 97 // ContainerAction implements TestCase.ContainerAction. 98 func (*NATPreRedirectTCPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 99 if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil { 100 return err 101 } 102 103 // Listen for TCP packets on redirect port. 104 return listenTCP(ctx, acceptPort, ipv6) 105 } 106 107 // LocalAction implements TestCase.LocalAction. 108 func (*NATPreRedirectTCPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 109 return connectTCP(ctx, ip, dropPort, ipv6) 110 } 111 112 // NATPreRedirectTCPOutgoing verifies that outgoing TCP connections aren't 113 // affected by PREROUTING connection tracking. 114 type NATPreRedirectTCPOutgoing struct{ baseCase } 115 116 var _ TestCase = (*NATPreRedirectTCPOutgoing)(nil) 117 118 // Name implements TestCase.Name. 119 func (*NATPreRedirectTCPOutgoing) Name() string { 120 return "NATPreRedirectTCPOutgoing" 121 } 122 123 // ContainerAction implements TestCase.ContainerAction. 124 func (*NATPreRedirectTCPOutgoing) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 125 // Redirect all incoming TCP traffic to a closed port. 126 if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil { 127 return err 128 } 129 130 // Establish a connection to the host process. 131 return connectTCP(ctx, ip, acceptPort, ipv6) 132 } 133 134 // LocalAction implements TestCase.LocalAction. 135 func (*NATPreRedirectTCPOutgoing) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 136 return listenTCP(ctx, acceptPort, ipv6) 137 } 138 139 // NATOutRedirectTCPIncoming verifies that incoming TCP connections aren't 140 // affected by OUTPUT connection tracking. 141 type NATOutRedirectTCPIncoming struct{ baseCase } 142 143 var _ TestCase = (*NATOutRedirectTCPIncoming)(nil) 144 145 // Name implements TestCase.Name. 146 func (*NATOutRedirectTCPIncoming) Name() string { 147 return "NATOutRedirectTCPIncoming" 148 } 149 150 // ContainerAction implements TestCase.ContainerAction. 151 func (*NATOutRedirectTCPIncoming) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 152 // Redirect all outgoing TCP traffic to a closed port. 153 if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil { 154 return err 155 } 156 157 // Establish a connection to the host process. 158 return listenTCP(ctx, acceptPort, ipv6) 159 } 160 161 // LocalAction implements TestCase.LocalAction. 162 func (*NATOutRedirectTCPIncoming) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 163 return connectTCP(ctx, ip, acceptPort, ipv6) 164 } 165 166 // NATOutRedirectUDPPort tests that packets are redirected to different port. 167 type NATOutRedirectUDPPort struct{ containerCase } 168 169 var _ TestCase = (*NATOutRedirectUDPPort)(nil) 170 171 // Name implements TestCase.Name. 172 func (*NATOutRedirectUDPPort) Name() string { 173 return "NATOutRedirectUDPPort" 174 } 175 176 // ContainerAction implements TestCase.ContainerAction. 177 func (*NATOutRedirectUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 178 return loopbackTest(ctx, ipv6, net.ParseIP(nowhereIP(ipv6)), "-A", "OUTPUT", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)) 179 } 180 181 // LocalAction implements TestCase.LocalAction. 182 func (*NATOutRedirectUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 183 // No-op. 184 return nil 185 } 186 187 // NATDropUDP tests that packets are not received in ports other than redirect 188 // port. 189 type NATDropUDP struct{ containerCase } 190 191 var _ TestCase = (*NATDropUDP)(nil) 192 193 // Name implements TestCase.Name. 194 func (*NATDropUDP) Name() string { 195 return "NATDropUDP" 196 } 197 198 // ContainerAction implements TestCase.ContainerAction. 199 func (*NATDropUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 200 if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil { 201 return err 202 } 203 204 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 205 defer cancel() 206 if err := listenUDP(timedCtx, acceptPort, ipv6); err == nil { 207 return fmt.Errorf("packets on port %d should have been redirected to port %d", acceptPort, redirectPort) 208 } else if !errors.Is(err, context.DeadlineExceeded) { 209 return fmt.Errorf("error reading: %v", err) 210 } 211 212 return nil 213 } 214 215 // LocalAction implements TestCase.LocalAction. 216 func (*NATDropUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 217 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 218 } 219 220 // NATAcceptAll tests that all UDP packets are accepted. 221 type NATAcceptAll struct{ containerCase } 222 223 var _ TestCase = (*NATAcceptAll)(nil) 224 225 // Name implements TestCase.Name. 226 func (*NATAcceptAll) Name() string { 227 return "NATAcceptAll" 228 } 229 230 // ContainerAction implements TestCase.ContainerAction. 231 func (*NATAcceptAll) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 232 if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "ACCEPT"); err != nil { 233 return err 234 } 235 236 if err := listenUDP(ctx, acceptPort, ipv6); err != nil { 237 return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %v", acceptPort, err) 238 } 239 240 return nil 241 } 242 243 // LocalAction implements TestCase.LocalAction. 244 func (*NATAcceptAll) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 245 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 246 } 247 248 // NATOutRedirectIP uses iptables to select packets based on destination IP and 249 // redirects them. 250 type NATOutRedirectIP struct{ baseCase } 251 252 var _ TestCase = (*NATOutRedirectIP)(nil) 253 254 // Name implements TestCase.Name. 255 func (*NATOutRedirectIP) Name() string { 256 return "NATOutRedirectIP" 257 } 258 259 // ContainerAction implements TestCase.ContainerAction. 260 func (*NATOutRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 261 // Redirect OUTPUT packets to a listening localhost port. 262 return loopbackTest(ctx, ipv6, net.ParseIP(nowhereIP(ipv6)), 263 "-A", "OUTPUT", 264 "-d", nowhereIP(ipv6), 265 "-p", "udp", 266 "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)) 267 } 268 269 // LocalAction implements TestCase.LocalAction. 270 func (*NATOutRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 271 // No-op. 272 return nil 273 } 274 275 // NATOutDontRedirectIP tests that iptables matching with "-d" does not match 276 // packets it shouldn't. 277 type NATOutDontRedirectIP struct{ localCase } 278 279 var _ TestCase = (*NATOutDontRedirectIP)(nil) 280 281 // Name implements TestCase.Name. 282 func (*NATOutDontRedirectIP) Name() string { 283 return "NATOutDontRedirectIP" 284 } 285 286 // ContainerAction implements TestCase.ContainerAction. 287 func (*NATOutDontRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 288 if err := natTable(ipv6, "-A", "OUTPUT", "-d", localIP(ipv6), "-p", "udp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", dropPort)); err != nil { 289 return err 290 } 291 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 292 } 293 294 // LocalAction implements TestCase.LocalAction. 295 func (*NATOutDontRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 296 return listenUDP(ctx, acceptPort, ipv6) 297 } 298 299 // NATOutRedirectInvert tests that iptables can match with "! -d". 300 type NATOutRedirectInvert struct{ baseCase } 301 302 var _ TestCase = (*NATOutRedirectInvert)(nil) 303 304 // Name implements TestCase.Name. 305 func (*NATOutRedirectInvert) Name() string { 306 return "NATOutRedirectInvert" 307 } 308 309 // ContainerAction implements TestCase.ContainerAction. 310 func (*NATOutRedirectInvert) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 311 // Redirect OUTPUT packets to a listening localhost port. 312 dest := "192.0.2.2" 313 if ipv6 { 314 dest = "2001:db8::2" 315 } 316 return loopbackTest(ctx, ipv6, net.ParseIP(nowhereIP(ipv6)), 317 "-A", "OUTPUT", 318 "!", "-d", dest, 319 "-p", "udp", 320 "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)) 321 } 322 323 // LocalAction implements TestCase.LocalAction. 324 func (*NATOutRedirectInvert) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 325 // No-op. 326 return nil 327 } 328 329 // NATPreRedirectIP tests that we can use iptables to select packets based on 330 // destination IP and redirect them. 331 type NATPreRedirectIP struct{ containerCase } 332 333 var _ TestCase = (*NATPreRedirectIP)(nil) 334 335 // Name implements TestCase.Name. 336 func (*NATPreRedirectIP) Name() string { 337 return "NATPreRedirectIP" 338 } 339 340 // ContainerAction implements TestCase.ContainerAction. 341 func (*NATPreRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 342 addrs, err := localAddrs(ipv6) 343 if err != nil { 344 return err 345 } 346 347 var rules [][]string 348 for _, addr := range addrs { 349 rules = append(rules, []string{"-A", "PREROUTING", "-p", "udp", "-d", addr, "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)}) 350 } 351 if err := natTableRules(ipv6, rules); err != nil { 352 return err 353 } 354 return listenUDP(ctx, acceptPort, ipv6) 355 } 356 357 // LocalAction implements TestCase.LocalAction. 358 func (*NATPreRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 359 return sendUDPLoop(ctx, ip, dropPort, ipv6) 360 } 361 362 // NATPreDontRedirectIP tests that iptables matching with "-d" does not match 363 // packets it shouldn't. 364 type NATPreDontRedirectIP struct{ containerCase } 365 366 var _ TestCase = (*NATPreDontRedirectIP)(nil) 367 368 // Name implements TestCase.Name. 369 func (*NATPreDontRedirectIP) Name() string { 370 return "NATPreDontRedirectIP" 371 } 372 373 // ContainerAction implements TestCase.ContainerAction. 374 func (*NATPreDontRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 375 if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil { 376 return err 377 } 378 return listenUDP(ctx, acceptPort, ipv6) 379 } 380 381 // LocalAction implements TestCase.LocalAction. 382 func (*NATPreDontRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 383 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 384 } 385 386 // NATPreRedirectInvert tests that iptables can match with "! -d". 387 type NATPreRedirectInvert struct{ containerCase } 388 389 var _ TestCase = (*NATPreRedirectInvert)(nil) 390 391 // Name implements TestCase.Name. 392 func (*NATPreRedirectInvert) Name() string { 393 return "NATPreRedirectInvert" 394 } 395 396 // ContainerAction implements TestCase.ContainerAction. 397 func (*NATPreRedirectInvert) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 398 if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "!", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil { 399 return err 400 } 401 return listenUDP(ctx, acceptPort, ipv6) 402 } 403 404 // LocalAction implements TestCase.LocalAction. 405 func (*NATPreRedirectInvert) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 406 return sendUDPLoop(ctx, ip, dropPort, ipv6) 407 } 408 409 // NATRedirectRequiresProtocol tests that use of the --to-ports flag requires a 410 // protocol to be specified with -p. 411 type NATRedirectRequiresProtocol struct{ baseCase } 412 413 var _ TestCase = (*NATRedirectRequiresProtocol)(nil) 414 415 // Name implements TestCase.Name. 416 func (*NATRedirectRequiresProtocol) Name() string { 417 return "NATRedirectRequiresProtocol" 418 } 419 420 // ContainerAction implements TestCase.ContainerAction. 421 func (*NATRedirectRequiresProtocol) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 422 if err := natTable(ipv6, "-A", "PREROUTING", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err == nil { 423 return errors.New("expected an error using REDIRECT --to-ports without a protocol") 424 } 425 return nil 426 } 427 428 // LocalAction implements TestCase.LocalAction. 429 func (*NATRedirectRequiresProtocol) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 430 // No-op. 431 return nil 432 } 433 434 // NATOutRedirectTCPPort tests that connections are redirected on specified ports. 435 type NATOutRedirectTCPPort struct{ baseCase } 436 437 var _ TestCase = (*NATOutRedirectTCPPort)(nil) 438 439 // Name implements TestCase.Name. 440 func (*NATOutRedirectTCPPort) Name() string { 441 return "NATOutRedirectTCPPort" 442 } 443 444 // ContainerAction implements TestCase.ContainerAction. 445 func (*NATOutRedirectTCPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 446 if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil { 447 return err 448 } 449 450 localAddr := net.TCPAddr{ 451 IP: net.ParseIP(localIP(ipv6)), 452 Port: acceptPort, 453 } 454 455 // Starts listening on port. 456 lConn, err := net.ListenTCP("tcp", &localAddr) 457 if err != nil { 458 return err 459 } 460 defer lConn.Close() 461 462 // Accept connections on port. 463 if err := connectTCP(ctx, ip, dropPort, ipv6); err != nil { 464 return err 465 } 466 467 conn, err := lConn.AcceptTCP() 468 if err != nil { 469 return err 470 } 471 conn.Close() 472 473 return nil 474 } 475 476 // LocalAction implements TestCase.LocalAction. 477 func (*NATOutRedirectTCPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 478 return nil 479 } 480 481 // NATLoopbackSkipsPrerouting tests that packets sent via loopback aren't 482 // affected by PREROUTING rules. 483 type NATLoopbackSkipsPrerouting struct{ baseCase } 484 485 var _ TestCase = (*NATLoopbackSkipsPrerouting)(nil) 486 487 // Name implements TestCase.Name. 488 func (*NATLoopbackSkipsPrerouting) Name() string { 489 return "NATLoopbackSkipsPrerouting" 490 } 491 492 // ContainerAction implements TestCase.ContainerAction. 493 func (*NATLoopbackSkipsPrerouting) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 494 // Redirect anything sent to localhost to an unused port. 495 var dest net.IP 496 if ipv6 { 497 dest = net.IPv6loopback 498 } else { 499 dest = net.IPv4(127, 0, 0, 1) 500 } 501 if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", dropPort)); err != nil { 502 return err 503 } 504 505 // Establish a connection via localhost. If the PREROUTING rule did apply to 506 // loopback traffic, the connection would fail. 507 sendCh := make(chan error) 508 go func() { 509 sendCh <- connectTCP(ctx, dest, acceptPort, ipv6) 510 }() 511 512 if err := listenTCP(ctx, acceptPort, ipv6); err != nil { 513 return err 514 } 515 return <-sendCh 516 } 517 518 // LocalAction implements TestCase.LocalAction. 519 func (*NATLoopbackSkipsPrerouting) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 520 // No-op. 521 return nil 522 } 523 524 // NATPreOriginalDst tests that SO_ORIGINAL_DST returns the pre-NAT destination 525 // of PREROUTING NATted packets. 526 type NATPreOriginalDst struct{ baseCase } 527 528 var _ TestCase = (*NATPreOriginalDst)(nil) 529 530 // Name implements TestCase.Name. 531 func (*NATPreOriginalDst) Name() string { 532 return "NATPreOriginalDst" 533 } 534 535 // ContainerAction implements TestCase.ContainerAction. 536 func (*NATPreOriginalDst) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 537 // Redirect incoming TCP connections to acceptPort. 538 if err := natTable(ipv6, "-A", "PREROUTING", 539 "-p", "tcp", 540 "--destination-port", fmt.Sprintf("%d", dropPort), 541 "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)); err != nil { 542 return err 543 } 544 545 addrs, err := getInterfaceAddrs(ipv6) 546 if err != nil { 547 return err 548 } 549 return listenForRedirectedConn(ctx, ipv6, addrs) 550 } 551 552 // LocalAction implements TestCase.LocalAction. 553 func (*NATPreOriginalDst) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 554 return connectTCP(ctx, ip, dropPort, ipv6) 555 } 556 557 // NATOutOriginalDst tests that SO_ORIGINAL_DST returns the pre-NAT destination 558 // of OUTBOUND NATted packets. 559 type NATOutOriginalDst struct{ baseCase } 560 561 var _ TestCase = (*NATOutOriginalDst)(nil) 562 563 // Name implements TestCase.Name. 564 func (*NATOutOriginalDst) Name() string { 565 return "NATOutOriginalDst" 566 } 567 568 // ContainerAction implements TestCase.ContainerAction. 569 func (*NATOutOriginalDst) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 570 // Redirect incoming TCP connections to acceptPort. 571 if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)); err != nil { 572 return err 573 } 574 575 connCh := make(chan error) 576 go func() { 577 connCh <- connectTCP(ctx, ip, dropPort, ipv6) 578 }() 579 580 if err := listenForRedirectedConn(ctx, ipv6, []net.IP{ip}); err != nil { 581 return err 582 } 583 return <-connCh 584 } 585 586 // LocalAction implements TestCase.LocalAction. 587 func (*NATOutOriginalDst) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 588 // No-op. 589 return nil 590 } 591 592 func listenForRedirectedConn(ctx context.Context, ipv6 bool, originalDsts []net.IP) error { 593 // The net package doesn't give guaranteed access to the connection's 594 // underlying FD, and thus we cannot call getsockopt. We have to use 595 // traditional syscalls. 596 597 // Create the listening socket, bind, listen, and accept. 598 family := unix.AF_INET 599 if ipv6 { 600 family = unix.AF_INET6 601 } 602 sockfd, err := unix.Socket(family, unix.SOCK_STREAM, 0) 603 if err != nil { 604 return err 605 } 606 defer unix.Close(sockfd) 607 608 var bindAddr unix.Sockaddr 609 if ipv6 { 610 bindAddr = &unix.SockaddrInet6{ 611 Port: acceptPort, 612 Addr: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // in6addr_any 613 } 614 } else { 615 bindAddr = &unix.SockaddrInet4{ 616 Port: acceptPort, 617 Addr: [4]byte{0, 0, 0, 0}, // INADDR_ANY 618 } 619 } 620 if err := unix.Bind(sockfd, bindAddr); err != nil { 621 return err 622 } 623 624 if err := unix.Listen(sockfd, 1); err != nil { 625 return err 626 } 627 628 // Block on accept() in another goroutine. 629 connCh := make(chan int) 630 errCh := make(chan error) 631 go func() { 632 for { 633 connFD, _, err := unix.Accept(sockfd) 634 if errors.Is(err, unix.EINTR) { 635 continue 636 } 637 if err != nil { 638 errCh <- err 639 return 640 } 641 connCh <- connFD 642 return 643 } 644 }() 645 646 // Wait for accept() to return or for the context to finish. 647 var connFD int 648 select { 649 case <-ctx.Done(): 650 return ctx.Err() 651 case err := <-errCh: 652 return err 653 case connFD = <-connCh: 654 } 655 defer unix.Close(connFD) 656 657 // Verify that, despite listening on acceptPort, SO_ORIGINAL_DST 658 // indicates the packet was sent to originalDst:dropPort. 659 if ipv6 { 660 got, err := originalDestination6(connFD) 661 if err != nil { 662 return err 663 } 664 return addrMatches6(got, originalDsts, dropPort) 665 } 666 667 got, err := originalDestination4(connFD) 668 if err != nil { 669 return err 670 } 671 return addrMatches4(got, originalDsts, dropPort) 672 } 673 674 // loopbackTests runs an iptables rule and ensures that packets sent to 675 // dest:dropPort are received by localhost:acceptPort. 676 func loopbackTest(ctx context.Context, ipv6 bool, dest net.IP, args ...string) error { 677 return loopbackTestPort(ctx, ipv6, dest, dropPort, args...) 678 } 679 680 // loopbackTests runs an iptables rule and ensures that packets sent to 681 // dest:port are received by localhost:acceptPort. 682 func loopbackTestPort(ctx context.Context, ipv6 bool, dest net.IP, port int, args ...string) error { 683 if err := natTable(ipv6, args...); err != nil { 684 return err 685 } 686 sendCh := make(chan error, 1) 687 listenCh := make(chan error, 1) 688 go func() { 689 sendCh <- sendUDPLoop(ctx, dest, port, ipv6) 690 }() 691 go func() { 692 listenCh <- listenUDP(ctx, acceptPort, ipv6) 693 }() 694 select { 695 case err := <-listenCh: 696 return err 697 case err := <-sendCh: 698 return err 699 } 700 } 701 702 // NATPreRECVORIGDSTADDR tests that IP{V6}_RECVORIGDSTADDR gets the post-NAT 703 // address on the PREROUTING chain. 704 type NATPreRECVORIGDSTADDR struct{ containerCase } 705 706 var _ TestCase = (*NATPreRECVORIGDSTADDR)(nil) 707 708 // Name implements TestCase.Name. 709 func (*NATPreRECVORIGDSTADDR) Name() string { 710 return "NATPreRECVORIGDSTADDR" 711 } 712 713 // ContainerAction implements TestCase.ContainerAction. 714 func (*NATPreRECVORIGDSTADDR) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 715 if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil { 716 return err 717 } 718 719 if err := recvWithRECVORIGDSTADDR(ctx, ipv6, nil, redirectPort); err != nil { 720 return err 721 } 722 723 return nil 724 } 725 726 // LocalAction implements TestCase.LocalAction. 727 func (*NATPreRECVORIGDSTADDR) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 728 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 729 } 730 731 // NATOutRECVORIGDSTADDR tests that IP{V6}_RECVORIGDSTADDR gets the post-NAT 732 // address on the OUTPUT chain. 733 type NATOutRECVORIGDSTADDR struct{ containerCase } 734 735 var _ TestCase = (*NATOutRECVORIGDSTADDR)(nil) 736 737 // Name implements TestCase.Name. 738 func (*NATOutRECVORIGDSTADDR) Name() string { 739 return "NATOutRECVORIGDSTADDR" 740 } 741 742 // ContainerAction implements TestCase.ContainerAction. 743 func (*NATOutRECVORIGDSTADDR) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 744 if err := natTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil { 745 return err 746 } 747 748 sendCh := make(chan error) 749 go func() { 750 // Packets will be sent to a non-container IP and redirected 751 // back to the container. 752 sendCh <- sendUDPLoop(ctx, ip, acceptPort, ipv6) 753 }() 754 755 expectedIP := &net.IP{127, 0, 0, 1} 756 if ipv6 { 757 expectedIP = &net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} 758 } 759 if err := recvWithRECVORIGDSTADDR(ctx, ipv6, expectedIP, redirectPort); err != nil { 760 return err 761 } 762 763 select { 764 case err := <-sendCh: 765 return err 766 default: 767 return nil 768 } 769 } 770 771 // LocalAction implements TestCase.LocalAction. 772 func (*NATOutRECVORIGDSTADDR) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 773 // No-op. 774 return nil 775 } 776 777 func recvWithRECVORIGDSTADDR(ctx context.Context, ipv6 bool, expectedDst *net.IP, port uint16) error { 778 // The net package doesn't give guaranteed access to a connection's 779 // underlying FD, and thus we cannot call getsockopt. We have to use 780 // traditional syscalls for IP_RECVORIGDSTADDR. 781 782 // Create the listening socket. 783 var ( 784 family = unix.AF_INET 785 level = unix.SOL_IP 786 option = unix.IP_RECVORIGDSTADDR 787 bindAddr unix.Sockaddr = &unix.SockaddrInet4{ 788 Port: int(port), 789 Addr: [4]byte{0, 0, 0, 0}, // INADDR_ANY 790 } 791 ) 792 if ipv6 { 793 family = unix.AF_INET6 794 level = unix.SOL_IPV6 795 option = 74 // IPV6_RECVORIGDSTADDR, which is missing from the syscall package. 796 bindAddr = &unix.SockaddrInet6{ 797 Port: int(port), 798 Addr: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // in6addr_any 799 } 800 } 801 sockfd, err := unix.Socket(family, unix.SOCK_DGRAM, 0) 802 if err != nil { 803 return fmt.Errorf("failed Socket(%d, %d, 0): %w", family, unix.SOCK_DGRAM, err) 804 } 805 defer unix.Close(sockfd) 806 807 if err := unix.Bind(sockfd, bindAddr); err != nil { 808 return fmt.Errorf("failed Bind(%d, %+v): %v", sockfd, bindAddr, err) 809 } 810 811 // Enable IP_RECVORIGDSTADDR. 812 if err := unix.SetsockoptInt(sockfd, level, option, 1); err != nil { 813 return fmt.Errorf("failed SetsockoptByte(%d, %d, %d, 1): %v", sockfd, level, option, err) 814 } 815 816 addrCh := make(chan any) 817 errCh := make(chan error) 818 go func() { 819 var addr any 820 var err error 821 if ipv6 { 822 addr, err = recvOrigDstAddr6(sockfd) 823 } else { 824 addr, err = recvOrigDstAddr4(sockfd) 825 } 826 if err != nil { 827 errCh <- err 828 } else { 829 addrCh <- addr 830 } 831 }() 832 833 // Wait to receive a packet. 834 var addr any 835 select { 836 case <-ctx.Done(): 837 return ctx.Err() 838 case err := <-errCh: 839 return err 840 case addr = <-addrCh: 841 } 842 843 // Get a list of local IPs to verify that the packet now appears to have 844 // been sent to us. 845 var localAddrs []net.IP 846 if expectedDst != nil { 847 localAddrs = []net.IP{*expectedDst} 848 } else { 849 localAddrs, err = getInterfaceAddrs(ipv6) 850 if err != nil { 851 return fmt.Errorf("failed to get local interfaces: %w", err) 852 } 853 } 854 855 // Verify that the address has the post-NAT port and address. 856 if ipv6 { 857 return addrMatches6(addr.(unix.RawSockaddrInet6), localAddrs, redirectPort) 858 } 859 return addrMatches4(addr.(unix.RawSockaddrInet4), localAddrs, redirectPort) 860 } 861 862 func recvOrigDstAddr4(sockfd int) (unix.RawSockaddrInet4, error) { 863 buf, err := recvOrigDstAddr(sockfd, unix.SOL_IP, unix.SizeofSockaddrInet4) 864 if err != nil { 865 return unix.RawSockaddrInet4{}, err 866 } 867 var addr unix.RawSockaddrInet4 868 binary.Unmarshal(buf, hostarch.ByteOrder, &addr) 869 return addr, nil 870 } 871 872 func recvOrigDstAddr6(sockfd int) (unix.RawSockaddrInet6, error) { 873 buf, err := recvOrigDstAddr(sockfd, unix.SOL_IP, unix.SizeofSockaddrInet6) 874 if err != nil { 875 return unix.RawSockaddrInet6{}, err 876 } 877 var addr unix.RawSockaddrInet6 878 binary.Unmarshal(buf, hostarch.ByteOrder, &addr) 879 return addr, nil 880 } 881 882 func recvOrigDstAddr(sockfd int, level uintptr, addrSize int) ([]byte, error) { 883 buf := make([]byte, 64) 884 oob := make([]byte, unix.CmsgSpace(addrSize)) 885 for { 886 _, oobn, _, _, err := unix.Recvmsg( 887 sockfd, 888 buf, // Message buffer. 889 oob, // Out-of-band buffer. 890 0) // Flags. 891 if errors.Is(err, unix.EINTR) { 892 continue 893 } 894 if err != nil { 895 return nil, fmt.Errorf("failed when calling Recvmsg: %w", err) 896 } 897 oob = oob[:oobn] 898 899 // Parse out the control message. 900 msgs, err := unix.ParseSocketControlMessage(oob) 901 if err != nil { 902 return nil, fmt.Errorf("failed to parse control message: %w", err) 903 } 904 return msgs[0].Data, nil 905 } 906 } 907 908 func addrMatches4(got unix.RawSockaddrInet4, wantAddrs []net.IP, port uint16) error { 909 for _, wantAddr := range wantAddrs { 910 want := unix.RawSockaddrInet4{ 911 Family: unix.AF_INET, 912 Port: htons(port), 913 } 914 copy(want.Addr[:], wantAddr.To4()) 915 if got == want { 916 return nil 917 } 918 } 919 return fmt.Errorf("got %+v, but wanted one of %+v (note: port numbers are in network byte order)", got, wantAddrs) 920 } 921 922 func addrMatches6(got unix.RawSockaddrInet6, wantAddrs []net.IP, port uint16) error { 923 for _, wantAddr := range wantAddrs { 924 want := unix.RawSockaddrInet6{ 925 Family: unix.AF_INET6, 926 Port: htons(port), 927 } 928 copy(want.Addr[:], wantAddr.To16()) 929 if got == want { 930 return nil 931 } 932 } 933 return fmt.Errorf("got %+v, but wanted one of %+v (note: port numbers are in network byte order)", got, wantAddrs) 934 } 935 936 const ( 937 snatAddrV4 = "194.236.50.155" 938 snatAddrV6 = "2a0a::1" 939 snatPort = 43 940 ) 941 942 // NATPostSNATUDP tests that the source port/IP in the packets are modified as 943 // expected. It tests the latest-implemented revision of the SNAT target. 944 type NATPostSNATUDP struct{ localCase } 945 946 var _ TestCase = (*NATPostSNATUDP)(nil) 947 948 // Name implements TestCase.Name. 949 func (*NATPostSNATUDP) Name() string { 950 return "NATPostSNATUDP" 951 } 952 953 // ContainerAction implements TestCase.ContainerAction. 954 func (*NATPostSNATUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 955 var source string 956 if ipv6 { 957 source = fmt.Sprintf("[%s]:%d", snatAddrV6, snatPort) 958 } else { 959 source = fmt.Sprintf("%s:%d", snatAddrV4, snatPort) 960 } 961 962 if err := natTable(ipv6, "-A", "POSTROUTING", "-p", "udp", "-j", "SNAT", "--to-source", source); err != nil { 963 return err 964 } 965 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 966 } 967 968 // LocalAction implements TestCase.LocalAction. 969 func (*NATPostSNATUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 970 remote, err := listenUDPFrom(ctx, acceptPort, ipv6) 971 if err != nil { 972 return err 973 } 974 var snatAddr string 975 if ipv6 { 976 snatAddr = snatAddrV6 977 } else { 978 snatAddr = snatAddrV4 979 } 980 if got, want := remote.IP, net.ParseIP(snatAddr); !got.Equal(want) { 981 return fmt.Errorf("got remote address = %s, want = %s", got, want) 982 } 983 if got, want := remote.Port, snatPort; got != want { 984 return fmt.Errorf("got remote port = %d, want = %d", got, want) 985 } 986 return nil 987 } 988 989 // NATPostSNATTCP tests that the source port/IP in the packets are modified as 990 // expected. It tests the latest-implemented revision of the SNAT target. 991 type NATPostSNATTCP struct{ localCase } 992 993 var _ TestCase = (*NATPostSNATTCP)(nil) 994 995 // Name implements TestCase.Name. 996 func (*NATPostSNATTCP) Name() string { 997 return "NATPostSNATTCP" 998 } 999 1000 // ContainerAction implements TestCase.ContainerAction. 1001 func (*NATPostSNATTCP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 1002 addrs, err := getInterfaceAddrs(ipv6) 1003 if err != nil { 1004 return err 1005 } 1006 var source string 1007 for _, addr := range addrs { 1008 if addr.To4() != nil { 1009 if !ipv6 { 1010 source = fmt.Sprintf("%s:%d", addr, snatPort) 1011 } 1012 } else if ipv6 && addr.IsGlobalUnicast() { 1013 source = fmt.Sprintf("[%s]:%d", addr, snatPort) 1014 } 1015 } 1016 if source == "" { 1017 return fmt.Errorf("can't find any interface address to use") 1018 } 1019 1020 if err := natTable(ipv6, "-A", "POSTROUTING", "-p", "tcp", "-j", "SNAT", "--to-source", source); err != nil { 1021 return err 1022 } 1023 return connectTCP(ctx, ip, acceptPort, ipv6) 1024 } 1025 1026 // LocalAction implements TestCase.LocalAction. 1027 func (*NATPostSNATTCP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 1028 remote, err := listenTCPFrom(ctx, acceptPort, ipv6) 1029 if err != nil { 1030 return err 1031 } 1032 HostStr, portStr, err := net.SplitHostPort(remote.String()) 1033 if err != nil { 1034 return err 1035 } 1036 if got, want := HostStr, ip.String(); got != want { 1037 return fmt.Errorf("got remote address = %s, want = %s", got, want) 1038 } 1039 port, err := strconv.ParseInt(portStr, 10, 0) 1040 if err != nil { 1041 return err 1042 } 1043 if got, want := int(port), snatPort; got != want { 1044 return fmt.Errorf("got remote port = %d, want = %d", got, want) 1045 } 1046 return nil 1047 } 1048 1049 // NATOutDNAT tests that the source port/IP in the packets are modified as 1050 // expected. It tests the latest-implemented revision of the DNAT target. 1051 type NATOutDNAT struct{ containerCase } 1052 1053 var _ TestCase = (*NATOutDNAT)(nil) 1054 1055 // Name implements TestCase.Name. 1056 func (*NATOutDNAT) Name() string { 1057 return "NATOutDNAT" 1058 } 1059 1060 // ContainerAction implements TestCase.ContainerAction. 1061 func (*NATOutDNAT) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 1062 dst := nowhereIP(ipv6) 1063 return loopbackTest(ctx, ipv6, net.ParseIP(dst), 1064 "-A", "OUTPUT", 1065 "-d", dst, 1066 "-p", "udp", "-m", "udp", 1067 "-j", "DNAT", "--to-destination", fmt.Sprintf("127.0.0.1:%d", acceptPort)) 1068 } 1069 1070 // LocalAction implements TestCase.LocalAction. 1071 func (*NATOutDNAT) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 1072 return nil 1073 } 1074 1075 // NATOutDNATAddrOnly tests that the source IP only in the packets are modified 1076 // as expected. It tests the latest-implemented revision of the DNAT target. 1077 type NATOutDNATAddrOnly struct{ containerCase } 1078 1079 var _ TestCase = (*NATOutDNATAddrOnly)(nil) 1080 1081 // Name implements TestCase.Name. 1082 func (*NATOutDNATAddrOnly) Name() string { 1083 return "NATOutDNATAddrOnly" 1084 } 1085 1086 // ContainerAction implements TestCase.ContainerAction. 1087 func (*NATOutDNATAddrOnly) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 1088 dst := nowhereIP(ipv6) 1089 return loopbackTestPort(ctx, ipv6, net.ParseIP(dst), acceptPort, 1090 "-A", "OUTPUT", 1091 "-d", dst, 1092 "-p", "udp", "-m", "udp", 1093 "-j", "DNAT", "--to-destination", "127.0.0.1") 1094 } 1095 1096 // LocalAction implements TestCase.LocalAction. 1097 func (*NATOutDNATAddrOnly) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 1098 return nil 1099 } 1100 1101 // NATOutDNATPortOnly tests that the source port only in the packets are 1102 // modified as expected. It tests the latest-implemented revision of the DNAT 1103 // target. 1104 type NATOutDNATPortOnly struct{ containerCase } 1105 1106 var _ TestCase = (*NATOutDNATPortOnly)(nil) 1107 1108 // Name implements TestCase.Name. 1109 func (*NATOutDNATPortOnly) Name() string { 1110 return "NATOutDNATPortOnly" 1111 } 1112 1113 // ContainerAction implements TestCase.ContainerAction. 1114 func (*NATOutDNATPortOnly) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 1115 const dst = "127.0.0.1" 1116 return loopbackTest(ctx, ipv6, net.ParseIP(dst), 1117 "-A", "OUTPUT", 1118 "-d", dst, 1119 "-p", "udp", "-m", "udp", 1120 "-j", "DNAT", "--to-destination", fmt.Sprintf(":%d", acceptPort)) 1121 } 1122 1123 // LocalAction implements TestCase.LocalAction. 1124 func (*NATOutDNATPortOnly) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 1125 return nil 1126 }