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