gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/iptables/filter_input.go (about) 1 // Copyright 2019 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 "time" 23 ) 24 25 const ( 26 dropPort = 2401 27 acceptPort = 2402 28 sendloopDuration = 2 * time.Second 29 chainName = "foochain" 30 ) 31 32 func init() { 33 RegisterTestCase(&FilterInputDropAll{}) 34 RegisterTestCase(&FilterInputDropDifferentUDPPort{}) 35 RegisterTestCase(&FilterInputDropOnlyUDP{}) 36 RegisterTestCase(&FilterInputDropTCPDestPort{}) 37 RegisterTestCase(&FilterInputDropTCPSrcPort{}) 38 RegisterTestCase(&FilterInputDropUDPPort{}) 39 RegisterTestCase(&FilterInputDropUDP{}) 40 RegisterTestCase(&FilterInputCreateUserChain{}) 41 RegisterTestCase(&FilterInputDefaultPolicyAccept{}) 42 RegisterTestCase(&FilterInputDefaultPolicyDrop{}) 43 RegisterTestCase(&FilterInputReturnUnderflow{}) 44 RegisterTestCase(&FilterInputSerializeJump{}) 45 RegisterTestCase(&FilterInputJumpBasic{}) 46 RegisterTestCase(&FilterInputJumpReturn{}) 47 RegisterTestCase(&FilterInputJumpReturnDrop{}) 48 RegisterTestCase(&FilterInputJumpBuiltin{}) 49 RegisterTestCase(&FilterInputJumpTwice{}) 50 RegisterTestCase(&FilterInputDestination{}) 51 RegisterTestCase(&FilterInputInvertDestination{}) 52 RegisterTestCase(&FilterInputSource{}) 53 RegisterTestCase(&FilterInputInvertSource{}) 54 RegisterTestCase(&FilterInputInterfaceAccept{}) 55 RegisterTestCase(&FilterInputInterfaceDrop{}) 56 RegisterTestCase(&FilterInputInterface{}) 57 RegisterTestCase(&FilterInputInterfaceBeginsWith{}) 58 RegisterTestCase(&FilterInputInterfaceInvertDrop{}) 59 RegisterTestCase(&FilterInputInterfaceInvertAccept{}) 60 RegisterTestCase(&FilterInputInvertDportAccept{}) 61 RegisterTestCase(&FilterInputInvertDportDrop{}) 62 } 63 64 // FilterInputDropUDP tests that we can drop UDP traffic. 65 type FilterInputDropUDP struct{ containerCase } 66 67 var _ TestCase = (*FilterInputDropUDP)(nil) 68 69 // Name implements TestCase.Name. 70 func (*FilterInputDropUDP) Name() string { 71 return "FilterInputDropUDP" 72 } 73 74 // ContainerAction implements TestCase.ContainerAction. 75 func (*FilterInputDropUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 76 if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-j", "DROP"); err != nil { 77 return err 78 } 79 80 // Listen for UDP packets on dropPort. 81 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 82 defer cancel() 83 if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { 84 return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort) 85 } else if !errors.Is(err, context.DeadlineExceeded) { 86 return fmt.Errorf("error reading: %v", err) 87 } 88 89 // At this point we know that reading timed out and never received a 90 // packet. 91 return nil 92 } 93 94 // LocalAction implements TestCase.LocalAction. 95 func (*FilterInputDropUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 96 return sendUDPLoop(ctx, ip, dropPort, ipv6) 97 } 98 99 // FilterInputDropOnlyUDP tests that "-p udp -j DROP" only affects UDP traffic. 100 type FilterInputDropOnlyUDP struct{ baseCase } 101 102 var _ TestCase = (*FilterInputDropOnlyUDP)(nil) 103 104 // Name implements TestCase.Name. 105 func (*FilterInputDropOnlyUDP) Name() string { 106 return "FilterInputDropOnlyUDP" 107 } 108 109 // ContainerAction implements TestCase.ContainerAction. 110 func (*FilterInputDropOnlyUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 111 if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-j", "DROP"); err != nil { 112 return err 113 } 114 115 // Listen for a TCP connection, which should be allowed. 116 if err := listenTCP(ctx, acceptPort, ipv6); err != nil { 117 return fmt.Errorf("failed to establish a connection %v", err) 118 } 119 120 return nil 121 } 122 123 // LocalAction implements TestCase.LocalAction. 124 func (*FilterInputDropOnlyUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 125 // Try to establish a TCP connection with the container, which should 126 // succeed. 127 return connectTCP(ctx, ip, acceptPort, ipv6) 128 } 129 130 // FilterInputDropUDPPort tests that we can drop UDP traffic by port. 131 type FilterInputDropUDPPort struct{ containerCase } 132 133 var _ TestCase = (*FilterInputDropUDPPort)(nil) 134 135 // Name implements TestCase.Name. 136 func (*FilterInputDropUDPPort) Name() string { 137 return "FilterInputDropUDPPort" 138 } 139 140 // ContainerAction implements TestCase.ContainerAction. 141 func (*FilterInputDropUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 142 if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil { 143 return err 144 } 145 146 // Listen for UDP packets on dropPort. 147 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 148 defer cancel() 149 if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { 150 return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort) 151 } else if !errors.Is(err, context.DeadlineExceeded) { 152 return fmt.Errorf("error reading: %v", err) 153 } 154 155 // At this point we know that reading timed out and never received a 156 // packet. 157 return nil 158 } 159 160 // LocalAction implements TestCase.LocalAction. 161 func (*FilterInputDropUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 162 return sendUDPLoop(ctx, ip, dropPort, ipv6) 163 } 164 165 // FilterInputDropDifferentUDPPort tests that dropping traffic for a single UDP port 166 // doesn't drop packets on other ports. 167 type FilterInputDropDifferentUDPPort struct{ containerCase } 168 169 var _ TestCase = (*FilterInputDropDifferentUDPPort)(nil) 170 171 // Name implements TestCase.Name. 172 func (*FilterInputDropDifferentUDPPort) Name() string { 173 return "FilterInputDropDifferentUDPPort" 174 } 175 176 // ContainerAction implements TestCase.ContainerAction. 177 func (*FilterInputDropDifferentUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 178 if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil { 179 return err 180 } 181 182 // Listen for UDP packets on another port. 183 if err := listenUDP(ctx, acceptPort, ipv6); err != nil { 184 return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %v", acceptPort, err) 185 } 186 187 return nil 188 } 189 190 // LocalAction implements TestCase.LocalAction. 191 func (*FilterInputDropDifferentUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 192 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 193 } 194 195 // FilterInputDropTCPDestPort tests that connections are not accepted on specified source ports. 196 type FilterInputDropTCPDestPort struct{ baseCase } 197 198 var _ TestCase = (*FilterInputDropTCPDestPort)(nil) 199 200 // Name implements TestCase.Name. 201 func (*FilterInputDropTCPDestPort) Name() string { 202 return "FilterInputDropTCPDestPort" 203 } 204 205 // ContainerAction implements TestCase.ContainerAction. 206 func (*FilterInputDropTCPDestPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 207 if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil { 208 return err 209 } 210 211 // Listen for TCP packets on drop port. 212 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 213 defer cancel() 214 if err := listenTCP(timedCtx, dropPort, ipv6); err == nil { 215 return fmt.Errorf("connection on port %d should not be accepted, but got accepted", dropPort) 216 } else if !errors.Is(err, context.DeadlineExceeded) { 217 return fmt.Errorf("error reading: %v", err) 218 } 219 220 return nil 221 } 222 223 // LocalAction implements TestCase.LocalAction. 224 func (*FilterInputDropTCPDestPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 225 // Ensure we cannot connect to the container. 226 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 227 defer cancel() 228 if err := connectTCP(timedCtx, ip, dropPort, ipv6); err == nil { 229 return fmt.Errorf("expected not to connect, but was able to connect on port %d", dropPort) 230 } 231 return nil 232 } 233 234 // FilterInputDropTCPSrcPort tests that connections are not accepted on specified source ports. 235 type FilterInputDropTCPSrcPort struct{ baseCase } 236 237 var _ TestCase = (*FilterInputDropTCPSrcPort)(nil) 238 239 // Name implements TestCase.Name. 240 func (*FilterInputDropTCPSrcPort) Name() string { 241 return "FilterInputDropTCPSrcPort" 242 } 243 244 // ContainerAction implements TestCase.ContainerAction. 245 func (*FilterInputDropTCPSrcPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 246 // Drop anything from an ephemeral port. 247 if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "-m", "tcp", "--sport", "1024:65535", "-j", "DROP"); err != nil { 248 return err 249 } 250 251 // Listen for TCP packets on accept port. 252 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 253 defer cancel() 254 if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { 255 return fmt.Errorf("connection destined to port %d should not be accepted, but was", dropPort) 256 } else if !errors.Is(err, context.DeadlineExceeded) { 257 return fmt.Errorf("error reading: %v", err) 258 } 259 260 return nil 261 } 262 263 // LocalAction implements TestCase.LocalAction. 264 func (*FilterInputDropTCPSrcPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 265 // Ensure we cannot connect to the container. 266 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 267 defer cancel() 268 if err := connectTCP(timedCtx, ip, dropPort, ipv6); err == nil { 269 return fmt.Errorf("expected not to connect, but was able to connect on port %d", acceptPort) 270 } 271 return nil 272 } 273 274 // FilterInputDropAll tests that we can drop all traffic to the INPUT chain. 275 type FilterInputDropAll struct{ containerCase } 276 277 var _ TestCase = (*FilterInputDropAll)(nil) 278 279 // Name implements TestCase.Name. 280 func (*FilterInputDropAll) Name() string { 281 return "FilterInputDropAll" 282 } 283 284 // ContainerAction implements TestCase.ContainerAction. 285 func (*FilterInputDropAll) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 286 if err := filterTable(ipv6, "-A", "INPUT", "-j", "DROP"); err != nil { 287 return err 288 } 289 290 // Listen for all packets on dropPort. 291 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 292 defer cancel() 293 if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { 294 return fmt.Errorf("packets should have been dropped, but got a packet") 295 } else if !errors.Is(err, context.DeadlineExceeded) { 296 return fmt.Errorf("error reading: %v", err) 297 } 298 299 // At this point we know that reading timed out and never received a 300 // packet. 301 return nil 302 } 303 304 // LocalAction implements TestCase.LocalAction. 305 func (*FilterInputDropAll) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 306 return sendUDPLoop(ctx, ip, dropPort, ipv6) 307 } 308 309 // FilterInputMultiUDPRules verifies that multiple UDP rules are applied 310 // correctly. This has the added benefit of testing whether we're serializing 311 // rules correctly -- if we do it incorrectly, the iptables tool will 312 // misunderstand and save the wrong tables. 313 type FilterInputMultiUDPRules struct{ baseCase } 314 315 var _ TestCase = (*FilterInputMultiUDPRules)(nil) 316 317 // Name implements TestCase.Name. 318 func (*FilterInputMultiUDPRules) Name() string { 319 return "FilterInputMultiUDPRules" 320 } 321 322 // ContainerAction implements TestCase.ContainerAction. 323 func (*FilterInputMultiUDPRules) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 324 rules := [][]string{ 325 {"-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"}, 326 {"-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", acceptPort), "-j", "ACCEPT"}, 327 {"-L"}, 328 } 329 return filterTableRules(ipv6, rules) 330 } 331 332 // LocalAction implements TestCase.LocalAction. 333 func (*FilterInputMultiUDPRules) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 334 // No-op. 335 return nil 336 } 337 338 // FilterInputRequireProtocolUDP checks that "-m udp" requires "-p udp" to be 339 // specified. 340 type FilterInputRequireProtocolUDP struct{ baseCase } 341 342 var _ TestCase = (*FilterInputRequireProtocolUDP)(nil) 343 344 // Name implements TestCase.Name. 345 func (*FilterInputRequireProtocolUDP) Name() string { 346 return "FilterInputRequireProtocolUDP" 347 } 348 349 // ContainerAction implements TestCase.ContainerAction. 350 func (*FilterInputRequireProtocolUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 351 if err := filterTable(ipv6, "-A", "INPUT", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err == nil { 352 return errors.New("expected iptables to fail with out \"-p udp\", but succeeded") 353 } 354 return nil 355 } 356 357 // LocalAction implements TestCase.LocalAction. 358 func (*FilterInputRequireProtocolUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 359 // No-op. 360 return nil 361 } 362 363 // FilterInputCreateUserChain tests chain creation. 364 type FilterInputCreateUserChain struct{ baseCase } 365 366 var _ TestCase = (*FilterInputCreateUserChain)(nil) 367 368 // Name implements TestCase.Name. 369 func (*FilterInputCreateUserChain) Name() string { 370 return "FilterInputCreateUserChain" 371 } 372 373 // ContainerAction implements TestCase.ContainerAction. 374 func (*FilterInputCreateUserChain) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 375 rules := [][]string{ 376 // Create a chain. 377 {"-N", chainName}, 378 // Add a simple rule to the chain. 379 {"-A", chainName, "-j", "DROP"}, 380 } 381 return filterTableRules(ipv6, rules) 382 } 383 384 // LocalAction implements TestCase.LocalAction. 385 func (*FilterInputCreateUserChain) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 386 // No-op. 387 return nil 388 } 389 390 // FilterInputDefaultPolicyAccept tests the default ACCEPT policy. 391 type FilterInputDefaultPolicyAccept struct{ containerCase } 392 393 var _ TestCase = (*FilterInputDefaultPolicyAccept)(nil) 394 395 // Name implements TestCase.Name. 396 func (*FilterInputDefaultPolicyAccept) Name() string { 397 return "FilterInputDefaultPolicyAccept" 398 } 399 400 // ContainerAction implements TestCase.ContainerAction. 401 func (*FilterInputDefaultPolicyAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 402 // Set the default policy to accept, then receive a packet. 403 if err := filterTable(ipv6, "-P", "INPUT", "ACCEPT"); err != nil { 404 return err 405 } 406 return listenUDP(ctx, acceptPort, ipv6) 407 } 408 409 // LocalAction implements TestCase.LocalAction. 410 func (*FilterInputDefaultPolicyAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 411 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 412 } 413 414 // FilterInputDefaultPolicyDrop tests the default DROP policy. 415 type FilterInputDefaultPolicyDrop struct{ containerCase } 416 417 var _ TestCase = (*FilterInputDefaultPolicyDrop)(nil) 418 419 // Name implements TestCase.Name. 420 func (*FilterInputDefaultPolicyDrop) Name() string { 421 return "FilterInputDefaultPolicyDrop" 422 } 423 424 // ContainerAction implements TestCase.ContainerAction. 425 func (*FilterInputDefaultPolicyDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 426 if err := filterTable(ipv6, "-P", "INPUT", "DROP"); err != nil { 427 return err 428 } 429 430 // Listen for UDP packets on dropPort. 431 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 432 defer cancel() 433 if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { 434 return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort) 435 } else if !errors.Is(err, context.DeadlineExceeded) { 436 return fmt.Errorf("error reading: %v", err) 437 } 438 439 // At this point we know that reading timed out and never received a 440 // packet. 441 return nil 442 } 443 444 // LocalAction implements TestCase.LocalAction. 445 func (*FilterInputDefaultPolicyDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 446 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 447 } 448 449 // FilterInputReturnUnderflow tests that -j RETURN in a built-in chain causes 450 // the underflow rule (i.e. default policy) to be executed. 451 type FilterInputReturnUnderflow struct{ containerCase } 452 453 var _ TestCase = (*FilterInputReturnUnderflow)(nil) 454 455 // Name implements TestCase.Name. 456 func (*FilterInputReturnUnderflow) Name() string { 457 return "FilterInputReturnUnderflow" 458 } 459 460 // ContainerAction implements TestCase.ContainerAction. 461 func (*FilterInputReturnUnderflow) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 462 // Add a RETURN rule followed by an unconditional accept, and set the 463 // default policy to DROP. 464 rules := [][]string{ 465 {"-A", "INPUT", "-j", "RETURN"}, 466 {"-A", "INPUT", "-j", "DROP"}, 467 {"-P", "INPUT", "ACCEPT"}, 468 } 469 if err := filterTableRules(ipv6, rules); err != nil { 470 return err 471 } 472 473 // We should receive packets, as the RETURN rule will trigger the default 474 // ACCEPT policy. 475 return listenUDP(ctx, acceptPort, ipv6) 476 } 477 478 // LocalAction implements TestCase.LocalAction. 479 func (*FilterInputReturnUnderflow) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 480 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 481 } 482 483 // FilterInputSerializeJump verifies that we can serialize jumps. 484 type FilterInputSerializeJump struct{ baseCase } 485 486 var _ TestCase = (*FilterInputSerializeJump)(nil) 487 488 // Name implements TestCase.Name. 489 func (*FilterInputSerializeJump) Name() string { 490 return "FilterInputSerializeJump" 491 } 492 493 // ContainerAction implements TestCase.ContainerAction. 494 func (*FilterInputSerializeJump) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 495 // Write a JUMP rule, the serialize it with `-L`. 496 rules := [][]string{ 497 {"-N", chainName}, 498 {"-A", "INPUT", "-j", chainName}, 499 {"-L"}, 500 } 501 return filterTableRules(ipv6, rules) 502 } 503 504 // LocalAction implements TestCase.LocalAction. 505 func (*FilterInputSerializeJump) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 506 // No-op. 507 return nil 508 } 509 510 // FilterInputJumpBasic jumps to a chain and executes a rule there. 511 type FilterInputJumpBasic struct{ containerCase } 512 513 var _ TestCase = (*FilterInputJumpBasic)(nil) 514 515 // Name implements TestCase.Name. 516 func (*FilterInputJumpBasic) Name() string { 517 return "FilterInputJumpBasic" 518 } 519 520 // ContainerAction implements TestCase.ContainerAction. 521 func (*FilterInputJumpBasic) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 522 rules := [][]string{ 523 {"-P", "INPUT", "DROP"}, 524 {"-N", chainName}, 525 {"-A", "INPUT", "-j", chainName}, 526 {"-A", chainName, "-j", "ACCEPT"}, 527 } 528 if err := filterTableRules(ipv6, rules); err != nil { 529 return err 530 } 531 532 // Listen for UDP packets on acceptPort. 533 return listenUDP(ctx, acceptPort, ipv6) 534 } 535 536 // LocalAction implements TestCase.LocalAction. 537 func (*FilterInputJumpBasic) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 538 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 539 } 540 541 // FilterInputJumpReturn jumps, returns, and executes a rule. 542 type FilterInputJumpReturn struct{ containerCase } 543 544 var _ TestCase = (*FilterInputJumpReturn)(nil) 545 546 // Name implements TestCase.Name. 547 func (*FilterInputJumpReturn) Name() string { 548 return "FilterInputJumpReturn" 549 } 550 551 // ContainerAction implements TestCase.ContainerAction. 552 func (*FilterInputJumpReturn) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 553 rules := [][]string{ 554 {"-N", chainName}, 555 {"-P", "INPUT", "ACCEPT"}, 556 {"-A", "INPUT", "-j", chainName}, 557 {"-A", chainName, "-j", "RETURN"}, 558 {"-A", chainName, "-j", "DROP"}, 559 } 560 if err := filterTableRules(ipv6, rules); err != nil { 561 return err 562 } 563 564 // Listen for UDP packets on acceptPort. 565 return listenUDP(ctx, acceptPort, ipv6) 566 } 567 568 // LocalAction implements TestCase.LocalAction. 569 func (*FilterInputJumpReturn) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 570 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 571 } 572 573 // FilterInputJumpReturnDrop jumps to a chain, returns, and DROPs packets. 574 type FilterInputJumpReturnDrop struct{ containerCase } 575 576 var _ TestCase = (*FilterInputJumpReturnDrop)(nil) 577 578 // Name implements TestCase.Name. 579 func (*FilterInputJumpReturnDrop) Name() string { 580 return "FilterInputJumpReturnDrop" 581 } 582 583 // ContainerAction implements TestCase.ContainerAction. 584 func (*FilterInputJumpReturnDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 585 rules := [][]string{ 586 {"-N", chainName}, 587 {"-A", "INPUT", "-j", chainName}, 588 {"-A", "INPUT", "-j", "DROP"}, 589 {"-A", chainName, "-j", "RETURN"}, 590 } 591 if err := filterTableRules(ipv6, rules); err != nil { 592 return err 593 } 594 595 // Listen for UDP packets on dropPort. 596 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 597 defer cancel() 598 if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { 599 return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort) 600 } else if !errors.Is(err, context.DeadlineExceeded) { 601 return fmt.Errorf("error reading: %v", err) 602 } 603 604 // At this point we know that reading timed out and never received a 605 // packet. 606 return nil 607 } 608 609 // LocalAction implements TestCase.LocalAction. 610 func (*FilterInputJumpReturnDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 611 return sendUDPLoop(ctx, ip, dropPort, ipv6) 612 } 613 614 // FilterInputJumpBuiltin verifies that jumping to a top-levl chain is illegal. 615 type FilterInputJumpBuiltin struct{ baseCase } 616 617 var _ TestCase = (*FilterInputJumpBuiltin)(nil) 618 619 // Name implements TestCase.Name. 620 func (*FilterInputJumpBuiltin) Name() string { 621 return "FilterInputJumpBuiltin" 622 } 623 624 // ContainerAction implements TestCase.ContainerAction. 625 func (*FilterInputJumpBuiltin) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 626 if err := filterTable(ipv6, "-A", "INPUT", "-j", "OUTPUT"); err == nil { 627 return fmt.Errorf("iptables should be unable to jump to a built-in chain") 628 } 629 return nil 630 } 631 632 // LocalAction implements TestCase.LocalAction. 633 func (*FilterInputJumpBuiltin) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 634 // No-op. 635 return nil 636 } 637 638 // FilterInputJumpTwice jumps twice, then returns twice and executes a rule. 639 type FilterInputJumpTwice struct{ containerCase } 640 641 var _ TestCase = (*FilterInputJumpTwice)(nil) 642 643 // Name implements TestCase.Name. 644 func (*FilterInputJumpTwice) Name() string { 645 return "FilterInputJumpTwice" 646 } 647 648 // ContainerAction implements TestCase.ContainerAction. 649 func (*FilterInputJumpTwice) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 650 const chainName2 = chainName + "2" 651 rules := [][]string{ 652 {"-P", "INPUT", "DROP"}, 653 {"-N", chainName}, 654 {"-N", chainName2}, 655 {"-A", "INPUT", "-j", chainName}, 656 {"-A", chainName, "-j", chainName2}, 657 {"-A", "INPUT", "-j", "ACCEPT"}, 658 } 659 if err := filterTableRules(ipv6, rules); err != nil { 660 return err 661 } 662 663 // UDP packets should jump and return twice, eventually hitting the 664 // ACCEPT rule. 665 return listenUDP(ctx, acceptPort, ipv6) 666 } 667 668 // LocalAction implements TestCase.LocalAction. 669 func (*FilterInputJumpTwice) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 670 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 671 } 672 673 // FilterInputDestination verifies that we can filter packets via `-d 674 // <ipaddr>`. 675 type FilterInputDestination struct{ containerCase } 676 677 var _ TestCase = (*FilterInputDestination)(nil) 678 679 // Name implements TestCase.Name. 680 func (*FilterInputDestination) Name() string { 681 return "FilterInputDestination" 682 } 683 684 // ContainerAction implements TestCase.ContainerAction. 685 func (*FilterInputDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 686 addrs, err := localAddrs(ipv6) 687 if err != nil { 688 return err 689 } 690 691 // Make INPUT's default action DROP, then ACCEPT all packets bound for 692 // this machine. 693 rules := [][]string{{"-P", "INPUT", "DROP"}} 694 for _, addr := range addrs { 695 rules = append(rules, []string{"-A", "INPUT", "-d", addr, "-j", "ACCEPT"}) 696 } 697 if err := filterTableRules(ipv6, rules); err != nil { 698 return err 699 } 700 701 return listenUDP(ctx, acceptPort, ipv6) 702 } 703 704 // LocalAction implements TestCase.LocalAction. 705 func (*FilterInputDestination) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 706 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 707 } 708 709 // FilterInputInvertDestination verifies that we can filter packets via `! -d 710 // <ipaddr>`. 711 type FilterInputInvertDestination struct{ containerCase } 712 713 var _ TestCase = (*FilterInputInvertDestination)(nil) 714 715 // Name implements TestCase.Name. 716 func (*FilterInputInvertDestination) Name() string { 717 return "FilterInputInvertDestination" 718 } 719 720 // ContainerAction implements TestCase.ContainerAction. 721 func (*FilterInputInvertDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 722 // Make INPUT's default action DROP, then ACCEPT all packets not bound 723 // for 127.0.0.1. 724 rules := [][]string{ 725 {"-P", "INPUT", "DROP"}, 726 {"-A", "INPUT", "!", "-d", localIP(ipv6), "-j", "ACCEPT"}, 727 } 728 if err := filterTableRules(ipv6, rules); err != nil { 729 return err 730 } 731 732 return listenUDP(ctx, acceptPort, ipv6) 733 } 734 735 // LocalAction implements TestCase.LocalAction. 736 func (*FilterInputInvertDestination) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 737 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 738 } 739 740 // FilterInputSource verifies that we can filter packets via `-s 741 // <ipaddr>`. 742 type FilterInputSource struct{ containerCase } 743 744 var _ TestCase = (*FilterInputSource)(nil) 745 746 // Name implements TestCase.Name. 747 func (*FilterInputSource) Name() string { 748 return "FilterInputSource" 749 } 750 751 // ContainerAction implements TestCase.ContainerAction. 752 func (*FilterInputSource) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 753 // Make INPUT's default action DROP, then ACCEPT all packets from this 754 // machine. 755 rules := [][]string{ 756 {"-P", "INPUT", "DROP"}, 757 {"-A", "INPUT", "-s", fmt.Sprintf("%v", ip), "-j", "ACCEPT"}, 758 } 759 if err := filterTableRules(ipv6, rules); err != nil { 760 return err 761 } 762 763 return listenUDP(ctx, acceptPort, ipv6) 764 } 765 766 // LocalAction implements TestCase.LocalAction. 767 func (*FilterInputSource) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 768 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 769 } 770 771 // FilterInputInvertSource verifies that we can filter packets via `! -s 772 // <ipaddr>`. 773 type FilterInputInvertSource struct{ containerCase } 774 775 var _ TestCase = (*FilterInputInvertSource)(nil) 776 777 // Name implements TestCase.Name. 778 func (*FilterInputInvertSource) Name() string { 779 return "FilterInputInvertSource" 780 } 781 782 // ContainerAction implements TestCase.ContainerAction. 783 func (*FilterInputInvertSource) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 784 // Make INPUT's default action DROP, then ACCEPT all packets not bound 785 // for 127.0.0.1. 786 rules := [][]string{ 787 {"-P", "INPUT", "DROP"}, 788 {"-A", "INPUT", "!", "-s", localIP(ipv6), "-j", "ACCEPT"}, 789 } 790 if err := filterTableRules(ipv6, rules); err != nil { 791 return err 792 } 793 794 return listenUDP(ctx, acceptPort, ipv6) 795 } 796 797 // LocalAction implements TestCase.LocalAction. 798 func (*FilterInputInvertSource) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 799 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 800 } 801 802 // FilterInputInterfaceAccept tests that packets are accepted from interface 803 // matching the iptables rule. 804 type FilterInputInterfaceAccept struct{ localCase } 805 806 var _ TestCase = (*FilterInputInterfaceAccept)(nil) 807 808 // Name implements TestCase.Name. 809 func (*FilterInputInterfaceAccept) Name() string { 810 return "FilterInputInterfaceAccept" 811 } 812 813 // ContainerAction implements TestCase.ContainerAction. 814 func (*FilterInputInterfaceAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 815 ifname, ok := getInterfaceName() 816 if !ok { 817 return fmt.Errorf("no interface is present, except loopback") 818 } 819 if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", ifname, "-j", "ACCEPT"); err != nil { 820 return err 821 } 822 if err := listenUDP(ctx, acceptPort, ipv6); err != nil { 823 return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %w", acceptPort, err) 824 } 825 826 return nil 827 } 828 829 // LocalAction implements TestCase.LocalAction. 830 func (*FilterInputInterfaceAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 831 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 832 } 833 834 // FilterInputInterfaceDrop tests that packets are dropped from interface 835 // matching the iptables rule. 836 type FilterInputInterfaceDrop struct{ localCase } 837 838 var _ TestCase = (*FilterInputInterfaceDrop)(nil) 839 840 // Name implements TestCase.Name. 841 func (*FilterInputInterfaceDrop) Name() string { 842 return "FilterInputInterfaceDrop" 843 } 844 845 // ContainerAction implements TestCase.ContainerAction. 846 func (*FilterInputInterfaceDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 847 ifname, ok := getInterfaceName() 848 if !ok { 849 return fmt.Errorf("no interface is present, except loopback") 850 } 851 if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", ifname, "-j", "DROP"); err != nil { 852 return err 853 } 854 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 855 defer cancel() 856 if err := listenUDP(timedCtx, acceptPort, ipv6); err != nil { 857 if errors.Is(err, context.DeadlineExceeded) { 858 return nil 859 } 860 return fmt.Errorf("error reading: %w", err) 861 } 862 return fmt.Errorf("packets should have been dropped, but got a packet") 863 } 864 865 // LocalAction implements TestCase.LocalAction. 866 func (*FilterInputInterfaceDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 867 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 868 } 869 870 // FilterInputInterface tests that packets are not dropped from interface which 871 // is not matching the interface name in the iptables rule. 872 type FilterInputInterface struct{ localCase } 873 874 var _ TestCase = (*FilterInputInterface)(nil) 875 876 // Name implements TestCase.Name. 877 func (*FilterInputInterface) Name() string { 878 return "FilterInputInterface" 879 } 880 881 // ContainerAction implements TestCase.ContainerAction. 882 func (*FilterInputInterface) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 883 if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", "lo", "-j", "DROP"); err != nil { 884 return err 885 } 886 if err := listenUDP(ctx, acceptPort, ipv6); err != nil { 887 return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %w", acceptPort, err) 888 } 889 return nil 890 } 891 892 // LocalAction implements TestCase.LocalAction. 893 func (*FilterInputInterface) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 894 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 895 } 896 897 // FilterInputInterfaceBeginsWith tests that packets are dropped from an 898 // interface which begins with the given interface name. 899 type FilterInputInterfaceBeginsWith struct{ localCase } 900 901 var _ TestCase = (*FilterInputInterfaceBeginsWith)(nil) 902 903 // Name implements TestCase.Name. 904 func (*FilterInputInterfaceBeginsWith) Name() string { 905 return "FilterInputInterfaceBeginsWith" 906 } 907 908 // ContainerAction implements TestCase.ContainerAction. 909 func (*FilterInputInterfaceBeginsWith) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 910 if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", "e+", "-j", "DROP"); err != nil { 911 return err 912 } 913 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 914 defer cancel() 915 if err := listenUDP(timedCtx, acceptPort, ipv6); err != nil { 916 if errors.Is(err, context.DeadlineExceeded) { 917 return nil 918 } 919 return fmt.Errorf("error reading: %w", err) 920 } 921 return fmt.Errorf("packets should have been dropped, but got a packet") 922 } 923 924 // LocalAction implements TestCase.LocalAction. 925 func (*FilterInputInterfaceBeginsWith) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 926 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 927 } 928 929 // FilterInputInterfaceInvertDrop tests that we selectively drop packets from 930 // interface not matching the interface name. 931 type FilterInputInterfaceInvertDrop struct{ baseCase } 932 933 var _ TestCase = (*FilterInputInterfaceInvertDrop)(nil) 934 935 // Name implements TestCase.Name. 936 func (*FilterInputInterfaceInvertDrop) Name() string { 937 return "FilterInputInterfaceInvertDrop" 938 } 939 940 // ContainerAction implements TestCase.ContainerAction. 941 func (*FilterInputInterfaceInvertDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 942 if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "!", "-i", "lo", "-j", "DROP"); err != nil { 943 return err 944 } 945 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 946 defer cancel() 947 if err := listenTCP(timedCtx, acceptPort, ipv6); err != nil { 948 if errors.Is(err, context.DeadlineExceeded) { 949 return nil 950 } 951 return fmt.Errorf("error reading: %w", err) 952 } 953 return fmt.Errorf("connection on port %d should not be accepted, but was accepted", acceptPort) 954 } 955 956 // LocalAction implements TestCase.LocalAction. 957 func (*FilterInputInterfaceInvertDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 958 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 959 defer cancel() 960 if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err != nil { 961 var operr *net.OpError 962 if errors.As(err, &operr) && operr.Timeout() { 963 return nil 964 } 965 return fmt.Errorf("error connecting: %w", err) 966 } 967 return fmt.Errorf("connection destined to port %d should not be accepted, but was accepted", acceptPort) 968 } 969 970 // FilterInputInterfaceInvertAccept tests that we can selectively accept packets 971 // not matching the specific incoming interface. 972 type FilterInputInterfaceInvertAccept struct{ baseCase } 973 974 var _ TestCase = (*FilterInputInterfaceInvertAccept)(nil) 975 976 // Name implements TestCase.Name. 977 func (*FilterInputInterfaceInvertAccept) Name() string { 978 return "FilterInputInterfaceInvertAccept" 979 } 980 981 // ContainerAction implements TestCase.ContainerAction. 982 func (*FilterInputInterfaceInvertAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 983 if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "!", "-i", "lo", "-j", "ACCEPT"); err != nil { 984 return err 985 } 986 return listenTCP(ctx, acceptPort, ipv6) 987 } 988 989 // LocalAction implements TestCase.LocalAction. 990 func (*FilterInputInterfaceInvertAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 991 return connectTCP(ctx, ip, acceptPort, ipv6) 992 } 993 994 // FilterInputInvertDportAccept tests that we can send packets on a negated 995 // --dport match 996 type FilterInputInvertDportAccept struct{ baseCase } 997 998 var _ TestCase = (*FilterInputInvertDportAccept)(nil) 999 1000 // Name implements TestCase.Name. 1001 func (*FilterInputInvertDportAccept) Name() string { 1002 return "FilterInputInvertDportAccept" 1003 } 1004 1005 // ContainerAction implements TestCase.ContainerAction. 1006 func (*FilterInputInvertDportAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 1007 if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "!", "--dport", fmt.Sprintf("%d", dropPort), "-j", "ACCEPT"); err != nil { 1008 return err 1009 } 1010 1011 // Listen for TCP packets on accept port. 1012 return listenTCP(ctx, acceptPort, ipv6) 1013 } 1014 1015 // LocalAction implements TestCase.LocalAction. 1016 func (*FilterInputInvertDportAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 1017 return connectTCP(ctx, ip, acceptPort, ipv6) 1018 } 1019 1020 // FilterInputInvertDportDrop tests that we can send packets on a negated 1021 // --dport match 1022 type FilterInputInvertDportDrop struct{ baseCase } 1023 1024 var _ TestCase = (*FilterInputInvertDportDrop)(nil) 1025 1026 // Name implements TestCase.Name. 1027 func (*FilterInputInvertDportDrop) Name() string { 1028 return "FilterInputInvertDportDrop" 1029 } 1030 1031 // ContainerAction implements TestCase.ContainerAction. 1032 func (*FilterInputInvertDportDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 1033 if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "!", "--dport", fmt.Sprintf("%d", acceptPort), "-j", "DROP"); err != nil { 1034 return err 1035 } 1036 1037 // Listen for TCP packets on accept port. 1038 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 1039 defer cancel() 1040 if err := listenTCP(timedCtx, dropPort, ipv6); err == nil { 1041 return fmt.Errorf("connection was established when it shouldnt have been") 1042 } else if !errors.Is(err, context.DeadlineExceeded) { 1043 return fmt.Errorf("error reading: %v", err) 1044 } 1045 1046 return nil 1047 } 1048 1049 // LocalAction implements TestCase.LocalAction. 1050 func (*FilterInputInvertDportDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 1051 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 1052 defer cancel() 1053 if err := connectTCP(timedCtx, ip, dropPort, ipv6); err == nil { 1054 return fmt.Errorf("connection on %d port was accepted when it should have been dropped", dropPort) 1055 } 1056 1057 return nil 1058 }