gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/iptables/filter_output.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 ) 23 24 func init() { 25 RegisterTestCase(&FilterOutputDropTCPDestPort{}) 26 RegisterTestCase(&FilterOutputDropTCPSrcPort{}) 27 RegisterTestCase(&FilterOutputDestination{}) 28 RegisterTestCase(&FilterOutputInvertDestination{}) 29 RegisterTestCase(&FilterOutputAcceptTCPOwner{}) 30 RegisterTestCase(&FilterOutputDropTCPOwner{}) 31 RegisterTestCase(&FilterOutputAcceptUDPOwner{}) 32 RegisterTestCase(&FilterOutputDropUDPOwner{}) 33 RegisterTestCase(&FilterOutputOwnerFail{}) 34 RegisterTestCase(&FilterOutputAcceptGIDOwner{}) 35 RegisterTestCase(&FilterOutputDropGIDOwner{}) 36 RegisterTestCase(&FilterOutputInvertGIDOwner{}) 37 RegisterTestCase(&FilterOutputInvertUIDOwner{}) 38 RegisterTestCase(&FilterOutputInvertUIDAndGIDOwner{}) 39 RegisterTestCase(&FilterOutputInterfaceAccept{}) 40 RegisterTestCase(&FilterOutputInterfaceDrop{}) 41 RegisterTestCase(&FilterOutputInterface{}) 42 RegisterTestCase(&FilterOutputInterfaceBeginsWith{}) 43 RegisterTestCase(&FilterOutputInterfaceInvertDrop{}) 44 RegisterTestCase(&FilterOutputInterfaceInvertAccept{}) 45 RegisterTestCase(&FilterOutputInvertSportAccept{}) 46 RegisterTestCase(&FilterOutputInvertSportDrop{}) 47 } 48 49 // FilterOutputDropTCPDestPort tests that connections are not accepted on 50 // specified source ports. 51 type FilterOutputDropTCPDestPort struct{ baseCase } 52 53 var _ TestCase = (*FilterOutputDropTCPDestPort)(nil) 54 55 // Name implements TestCase.Name. 56 func (*FilterOutputDropTCPDestPort) Name() string { 57 return "FilterOutputDropTCPDestPort" 58 } 59 60 // ContainerAction implements TestCase.ContainerAction. 61 func (*FilterOutputDropTCPDestPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 62 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", "1024:65535", "-j", "DROP"); err != nil { 63 return err 64 } 65 66 // Listen for TCP packets on accept port. 67 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 68 defer cancel() 69 if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { 70 return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", dropPort) 71 } else if !errors.Is(err, context.DeadlineExceeded) { 72 return fmt.Errorf("error reading: %v", err) 73 } 74 75 return nil 76 } 77 78 // LocalAction implements TestCase.LocalAction. 79 func (*FilterOutputDropTCPDestPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 80 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 81 defer cancel() 82 if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { 83 return fmt.Errorf("connection on port %d should not be accepted, but got accepted", dropPort) 84 } 85 86 return nil 87 } 88 89 // FilterOutputDropTCPSrcPort tests that connections are not accepted on 90 // specified source ports. 91 type FilterOutputDropTCPSrcPort struct{ baseCase } 92 93 var _ TestCase = (*FilterOutputDropTCPSrcPort)(nil) 94 95 // Name implements TestCase.Name. 96 func (*FilterOutputDropTCPSrcPort) Name() string { 97 return "FilterOutputDropTCPSrcPort" 98 } 99 100 // ContainerAction implements TestCase.ContainerAction. 101 func (*FilterOutputDropTCPSrcPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 102 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--sport", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil { 103 return err 104 } 105 106 // Listen for TCP packets on drop port. 107 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 108 defer cancel() 109 if err := listenTCP(timedCtx, dropPort, ipv6); err == nil { 110 return fmt.Errorf("connection on port %d should not be accepted, but got accepted", dropPort) 111 } else if !errors.Is(err, context.DeadlineExceeded) { 112 return fmt.Errorf("error reading: %v", err) 113 } 114 115 return nil 116 } 117 118 // LocalAction implements TestCase.LocalAction. 119 func (*FilterOutputDropTCPSrcPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 120 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 121 defer cancel() 122 if err := connectTCP(timedCtx, ip, dropPort, ipv6); err == nil { 123 return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", dropPort) 124 } 125 126 return nil 127 } 128 129 // FilterOutputAcceptTCPOwner tests that TCP connections from uid owner are accepted. 130 type FilterOutputAcceptTCPOwner struct{ baseCase } 131 132 var _ TestCase = (*FilterOutputAcceptTCPOwner)(nil) 133 134 // Name implements TestCase.Name. 135 func (*FilterOutputAcceptTCPOwner) Name() string { 136 return "FilterOutputAcceptTCPOwner" 137 } 138 139 // ContainerAction implements TestCase.ContainerAction. 140 func (*FilterOutputAcceptTCPOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 141 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--uid-owner", "root", "-j", "ACCEPT"); err != nil { 142 return err 143 } 144 145 // Listen for TCP packets on accept port. 146 return listenTCP(ctx, acceptPort, ipv6) 147 } 148 149 // LocalAction implements TestCase.LocalAction. 150 func (*FilterOutputAcceptTCPOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 151 return connectTCP(ctx, ip, acceptPort, ipv6) 152 } 153 154 // FilterOutputDropTCPOwner tests that TCP connections from uid owner are dropped. 155 type FilterOutputDropTCPOwner struct{ baseCase } 156 157 var _ TestCase = (*FilterOutputDropTCPOwner)(nil) 158 159 // Name implements TestCase.Name. 160 func (*FilterOutputDropTCPOwner) Name() string { 161 return "FilterOutputDropTCPOwner" 162 } 163 164 // ContainerAction implements TestCase.ContainerAction. 165 func (*FilterOutputDropTCPOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 166 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--uid-owner", "root", "-j", "DROP"); err != nil { 167 return err 168 } 169 170 // Listen for TCP packets on accept port. 171 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 172 defer cancel() 173 if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { 174 return fmt.Errorf("connection on port %d should be dropped, but got accepted", acceptPort) 175 } else if !errors.Is(err, context.DeadlineExceeded) { 176 return fmt.Errorf("error reading: %v", err) 177 } 178 179 return nil 180 } 181 182 // LocalAction implements TestCase.LocalAction. 183 func (*FilterOutputDropTCPOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 184 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 185 defer cancel() 186 if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { 187 return fmt.Errorf("connection destined to port %d should be dropped, but got accepted", acceptPort) 188 } 189 190 return nil 191 } 192 193 // FilterOutputAcceptUDPOwner tests that UDP packets from uid owner are accepted. 194 type FilterOutputAcceptUDPOwner struct{ localCase } 195 196 var _ TestCase = (*FilterOutputAcceptUDPOwner)(nil) 197 198 // Name implements TestCase.Name. 199 func (*FilterOutputAcceptUDPOwner) Name() string { 200 return "FilterOutputAcceptUDPOwner" 201 } 202 203 // ContainerAction implements TestCase.ContainerAction. 204 func (*FilterOutputAcceptUDPOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 205 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-m", "owner", "--uid-owner", "root", "-j", "ACCEPT"); err != nil { 206 return err 207 } 208 209 // Send UDP packets on acceptPort. 210 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 211 } 212 213 // LocalAction implements TestCase.LocalAction. 214 func (*FilterOutputAcceptUDPOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 215 // Listen for UDP packets on acceptPort. 216 return listenUDP(ctx, acceptPort, ipv6) 217 } 218 219 // FilterOutputDropUDPOwner tests that UDP packets from uid owner are dropped. 220 type FilterOutputDropUDPOwner struct{ localCase } 221 222 var _ TestCase = (*FilterOutputDropUDPOwner)(nil) 223 224 // Name implements TestCase.Name. 225 func (*FilterOutputDropUDPOwner) Name() string { 226 return "FilterOutputDropUDPOwner" 227 } 228 229 // ContainerAction implements TestCase.ContainerAction. 230 func (*FilterOutputDropUDPOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 231 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-m", "owner", "--uid-owner", "root", "-j", "DROP"); err != nil { 232 return err 233 } 234 235 // Send UDP packets on dropPort. 236 return sendUDPLoop(ctx, ip, dropPort, ipv6) 237 } 238 239 // LocalAction implements TestCase.LocalAction. 240 func (*FilterOutputDropUDPOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 241 // Listen for UDP packets on dropPort. 242 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 243 defer cancel() 244 if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { 245 return fmt.Errorf("packets should not be received") 246 } else if !errors.Is(err, context.DeadlineExceeded) { 247 return fmt.Errorf("error reading: %v", err) 248 } 249 250 return nil 251 } 252 253 // FilterOutputOwnerFail tests that without uid/gid option, owner rule 254 // will fail. 255 type FilterOutputOwnerFail struct{ baseCase } 256 257 var _ TestCase = (*FilterOutputOwnerFail)(nil) 258 259 // Name implements TestCase.Name. 260 func (*FilterOutputOwnerFail) Name() string { 261 return "FilterOutputOwnerFail" 262 } 263 264 // ContainerAction implements TestCase.ContainerAction. 265 func (*FilterOutputOwnerFail) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 266 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-m", "owner", "-j", "ACCEPT"); err == nil { 267 return fmt.Errorf("invalid argument") 268 } 269 270 return nil 271 } 272 273 // LocalAction implements TestCase.LocalAction. 274 func (*FilterOutputOwnerFail) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 275 // no-op. 276 return nil 277 } 278 279 // FilterOutputAcceptGIDOwner tests that TCP connections from gid owner are accepted. 280 type FilterOutputAcceptGIDOwner struct{ baseCase } 281 282 var _ TestCase = (*FilterOutputAcceptGIDOwner)(nil) 283 284 // Name implements TestCase.Name. 285 func (*FilterOutputAcceptGIDOwner) Name() string { 286 return "FilterOutputAcceptGIDOwner" 287 } 288 289 // ContainerAction implements TestCase.ContainerAction. 290 func (*FilterOutputAcceptGIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 291 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--gid-owner", "root", "-j", "ACCEPT"); err != nil { 292 return err 293 } 294 295 // Listen for TCP packets on accept port. 296 return listenTCP(ctx, acceptPort, ipv6) 297 } 298 299 // LocalAction implements TestCase.LocalAction. 300 func (*FilterOutputAcceptGIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 301 return connectTCP(ctx, ip, acceptPort, ipv6) 302 } 303 304 // FilterOutputDropGIDOwner tests that TCP connections from gid owner are dropped. 305 type FilterOutputDropGIDOwner struct{ baseCase } 306 307 var _ TestCase = (*FilterOutputDropGIDOwner)(nil) 308 309 // Name implements TestCase.Name. 310 func (*FilterOutputDropGIDOwner) Name() string { 311 return "FilterOutputDropGIDOwner" 312 } 313 314 // ContainerAction implements TestCase.ContainerAction. 315 func (*FilterOutputDropGIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 316 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--gid-owner", "root", "-j", "DROP"); err != nil { 317 return err 318 } 319 320 // Listen for TCP packets on accept port. 321 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 322 defer cancel() 323 if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { 324 return fmt.Errorf("connection on port %d should not be accepted, but got accepted", acceptPort) 325 } else if !errors.Is(err, context.DeadlineExceeded) { 326 return fmt.Errorf("error reading: %v", err) 327 } 328 329 return nil 330 } 331 332 // LocalAction implements TestCase.LocalAction. 333 func (*FilterOutputDropGIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 334 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 335 defer cancel() 336 if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { 337 return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort) 338 } 339 340 return nil 341 } 342 343 // FilterOutputInvertGIDOwner tests that TCP connections from gid owner are dropped. 344 type FilterOutputInvertGIDOwner struct{ baseCase } 345 346 var _ TestCase = (*FilterOutputInvertGIDOwner)(nil) 347 348 // Name implements TestCase.Name. 349 func (*FilterOutputInvertGIDOwner) Name() string { 350 return "FilterOutputInvertGIDOwner" 351 } 352 353 // ContainerAction implements TestCase.ContainerAction. 354 func (*FilterOutputInvertGIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 355 rules := [][]string{ 356 {"-A", "OUTPUT", "-p", "tcp", "-m", "owner", "!", "--gid-owner", "root", "-j", "ACCEPT"}, 357 {"-A", "OUTPUT", "-p", "tcp", "-j", "DROP"}, 358 } 359 if err := filterTableRules(ipv6, rules); err != nil { 360 return err 361 } 362 363 // Listen for TCP packets on accept port. 364 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 365 defer cancel() 366 if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { 367 return fmt.Errorf("connection on port %d should not be accepted, but got accepted", acceptPort) 368 } else if !errors.Is(err, context.DeadlineExceeded) { 369 return fmt.Errorf("error reading: %v", err) 370 } 371 372 return nil 373 } 374 375 // LocalAction implements TestCase.LocalAction. 376 func (*FilterOutputInvertGIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 377 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 378 defer cancel() 379 if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { 380 return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort) 381 } 382 383 return nil 384 } 385 386 // FilterOutputInvertUIDOwner tests that TCP connections from gid owner are dropped. 387 type FilterOutputInvertUIDOwner struct{ baseCase } 388 389 var _ TestCase = (*FilterOutputInvertUIDOwner)(nil) 390 391 // Name implements TestCase.Name. 392 func (*FilterOutputInvertUIDOwner) Name() string { 393 return "FilterOutputInvertUIDOwner" 394 } 395 396 // ContainerAction implements TestCase.ContainerAction. 397 func (*FilterOutputInvertUIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 398 rules := [][]string{ 399 {"-A", "OUTPUT", "-p", "tcp", "-m", "owner", "!", "--uid-owner", "root", "-j", "DROP"}, 400 {"-A", "OUTPUT", "-p", "tcp", "-j", "ACCEPT"}, 401 } 402 if err := filterTableRules(ipv6, rules); err != nil { 403 return err 404 } 405 406 // Listen for TCP packets on accept port. 407 return listenTCP(ctx, acceptPort, ipv6) 408 } 409 410 // LocalAction implements TestCase.LocalAction. 411 func (*FilterOutputInvertUIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 412 return connectTCP(ctx, ip, acceptPort, ipv6) 413 } 414 415 // FilterOutputInvertUIDAndGIDOwner tests that TCP connections from uid and gid 416 // owner are dropped. 417 type FilterOutputInvertUIDAndGIDOwner struct{ baseCase } 418 419 var _ TestCase = (*FilterOutputInvertUIDAndGIDOwner)(nil) 420 421 // Name implements TestCase.Name. 422 func (*FilterOutputInvertUIDAndGIDOwner) Name() string { 423 return "FilterOutputInvertUIDAndGIDOwner" 424 } 425 426 // ContainerAction implements TestCase.ContainerAction. 427 func (*FilterOutputInvertUIDAndGIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 428 rules := [][]string{ 429 {"-A", "OUTPUT", "-p", "tcp", "-m", "owner", "!", "--uid-owner", "root", "!", "--gid-owner", "root", "-j", "ACCEPT"}, 430 {"-A", "OUTPUT", "-p", "tcp", "-j", "DROP"}, 431 } 432 if err := filterTableRules(ipv6, rules); err != nil { 433 return err 434 } 435 436 // Listen for TCP packets on accept port. 437 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 438 defer cancel() 439 if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { 440 return fmt.Errorf("connection on port %d should not be accepted, but got accepted", acceptPort) 441 } else if !errors.Is(err, context.DeadlineExceeded) { 442 return fmt.Errorf("error reading: %v", err) 443 } 444 445 return nil 446 } 447 448 // LocalAction implements TestCase.LocalAction. 449 func (*FilterOutputInvertUIDAndGIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 450 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 451 defer cancel() 452 if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { 453 return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort) 454 } 455 456 return nil 457 } 458 459 // FilterOutputDestination tests that we can selectively allow packets to 460 // certain destinations. 461 type FilterOutputDestination struct{ localCase } 462 463 var _ TestCase = (*FilterOutputDestination)(nil) 464 465 // Name implements TestCase.Name. 466 func (*FilterOutputDestination) Name() string { 467 return "FilterOutputDestination" 468 } 469 470 // ContainerAction implements TestCase.ContainerAction. 471 func (*FilterOutputDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 472 var rules [][]string 473 if ipv6 { 474 rules = [][]string{ 475 {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"}, 476 // Allow solicited node multicast addresses so we can send neighbor 477 // solicitations. 478 {"-A", "OUTPUT", "-d", "ff02::1:ff00:0/104", "-j", "ACCEPT"}, 479 {"-P", "OUTPUT", "DROP"}, 480 } 481 } else { 482 rules = [][]string{ 483 {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"}, 484 {"-P", "OUTPUT", "DROP"}, 485 } 486 } 487 if err := filterTableRules(ipv6, rules); err != nil { 488 return err 489 } 490 491 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 492 } 493 494 // LocalAction implements TestCase.LocalAction. 495 func (*FilterOutputDestination) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 496 return listenUDP(ctx, acceptPort, ipv6) 497 } 498 499 // FilterOutputInvertDestination tests that we can selectively allow packets 500 // not headed for a particular destination. 501 type FilterOutputInvertDestination struct{ localCase } 502 503 var _ TestCase = (*FilterOutputInvertDestination)(nil) 504 505 // Name implements TestCase.Name. 506 func (*FilterOutputInvertDestination) Name() string { 507 return "FilterOutputInvertDestination" 508 } 509 510 // ContainerAction implements TestCase.ContainerAction. 511 func (*FilterOutputInvertDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 512 rules := [][]string{ 513 {"-A", "OUTPUT", "!", "-d", localIP(ipv6), "-j", "ACCEPT"}, 514 {"-P", "OUTPUT", "DROP"}, 515 } 516 if err := filterTableRules(ipv6, rules); err != nil { 517 return err 518 } 519 520 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 521 } 522 523 // LocalAction implements TestCase.LocalAction. 524 func (*FilterOutputInvertDestination) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 525 return listenUDP(ctx, acceptPort, ipv6) 526 } 527 528 // FilterOutputInterfaceAccept tests that packets are sent via interface 529 // matching the iptables rule. 530 type FilterOutputInterfaceAccept struct{ localCase } 531 532 var _ TestCase = (*FilterOutputInterfaceAccept)(nil) 533 534 // Name implements TestCase.Name. 535 func (*FilterOutputInterfaceAccept) Name() string { 536 return "FilterOutputInterfaceAccept" 537 } 538 539 // ContainerAction implements TestCase.ContainerAction. 540 func (*FilterOutputInterfaceAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 541 ifname, ok := getInterfaceName() 542 if !ok { 543 return fmt.Errorf("no interface is present, except loopback") 544 } 545 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", ifname, "-j", "ACCEPT"); err != nil { 546 return err 547 } 548 549 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 550 } 551 552 // LocalAction implements TestCase.LocalAction. 553 func (*FilterOutputInterfaceAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 554 return listenUDP(ctx, acceptPort, ipv6) 555 } 556 557 // FilterOutputInterfaceDrop tests that packets are not sent via interface 558 // matching the iptables rule. 559 type FilterOutputInterfaceDrop struct{ localCase } 560 561 var _ TestCase = (*FilterOutputInterfaceDrop)(nil) 562 563 // Name implements TestCase.Name. 564 func (*FilterOutputInterfaceDrop) Name() string { 565 return "FilterOutputInterfaceDrop" 566 } 567 568 // ContainerAction implements TestCase.ContainerAction. 569 func (*FilterOutputInterfaceDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 570 ifname, ok := getInterfaceName() 571 if !ok { 572 return fmt.Errorf("no interface is present, except loopback") 573 } 574 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", ifname, "-j", "DROP"); err != nil { 575 return err 576 } 577 578 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 579 } 580 581 // LocalAction implements TestCase.LocalAction. 582 func (*FilterOutputInterfaceDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 583 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 584 defer cancel() 585 if err := listenUDP(timedCtx, acceptPort, ipv6); err == nil { 586 return fmt.Errorf("packets should not be received on port %v, but are received", acceptPort) 587 } else if !errors.Is(err, context.DeadlineExceeded) { 588 return fmt.Errorf("error reading: %v", err) 589 } 590 591 return nil 592 } 593 594 // FilterOutputInterface tests that packets are sent via interface which is 595 // not matching the interface name in the iptables rule. 596 type FilterOutputInterface struct{ localCase } 597 598 var _ TestCase = (*FilterOutputInterface)(nil) 599 600 // Name implements TestCase.Name. 601 func (*FilterOutputInterface) Name() string { 602 return "FilterOutputInterface" 603 } 604 605 // ContainerAction implements TestCase.ContainerAction. 606 func (*FilterOutputInterface) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 607 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", "lo", "-j", "DROP"); err != nil { 608 return err 609 } 610 611 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 612 } 613 614 // LocalAction implements TestCase.LocalAction. 615 func (*FilterOutputInterface) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 616 return listenUDP(ctx, acceptPort, ipv6) 617 } 618 619 // FilterOutputInterfaceBeginsWith tests that packets are not sent via an 620 // interface which begins with the given interface name. 621 type FilterOutputInterfaceBeginsWith struct{ localCase } 622 623 var _ TestCase = (*FilterOutputInterfaceBeginsWith)(nil) 624 625 // Name implements TestCase.Name. 626 func (*FilterOutputInterfaceBeginsWith) Name() string { 627 return "FilterOutputInterfaceBeginsWith" 628 } 629 630 // ContainerAction implements TestCase.ContainerAction. 631 func (*FilterOutputInterfaceBeginsWith) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 632 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", "e+", "-j", "DROP"); err != nil { 633 return err 634 } 635 636 return sendUDPLoop(ctx, ip, acceptPort, ipv6) 637 } 638 639 // LocalAction implements TestCase.LocalAction. 640 func (*FilterOutputInterfaceBeginsWith) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 641 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 642 defer cancel() 643 if err := listenUDP(timedCtx, acceptPort, ipv6); err == nil { 644 return fmt.Errorf("packets should not be received on port %v, but are received", acceptPort) 645 } else if !errors.Is(err, context.DeadlineExceeded) { 646 return fmt.Errorf("error reading: %v", err) 647 } 648 649 return nil 650 } 651 652 // FilterOutputInterfaceInvertDrop tests that we selectively do not send 653 // packets via interface not matching the interface name. 654 type FilterOutputInterfaceInvertDrop struct{ baseCase } 655 656 var _ TestCase = (*FilterOutputInterfaceInvertDrop)(nil) 657 658 // Name implements TestCase.Name. 659 func (*FilterOutputInterfaceInvertDrop) Name() string { 660 return "FilterOutputInterfaceInvertDrop" 661 } 662 663 // ContainerAction implements TestCase.ContainerAction. 664 func (*FilterOutputInterfaceInvertDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 665 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "!", "-o", "lo", "-j", "DROP"); err != nil { 666 return err 667 } 668 669 // Listen for TCP packets on accept port. 670 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 671 defer cancel() 672 if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { 673 return fmt.Errorf("connection on port %d should not be accepted, but got accepted", acceptPort) 674 } else if !errors.Is(err, context.DeadlineExceeded) { 675 return fmt.Errorf("error reading: %v", err) 676 } 677 678 return nil 679 } 680 681 // LocalAction implements TestCase.LocalAction. 682 func (*FilterOutputInterfaceInvertDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 683 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 684 defer cancel() 685 if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { 686 return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort) 687 } 688 689 return nil 690 } 691 692 // FilterOutputInterfaceInvertAccept tests that we can selectively send packets 693 // not matching the specific outgoing interface. 694 type FilterOutputInterfaceInvertAccept struct{ baseCase } 695 696 var _ TestCase = (*FilterOutputInterfaceInvertAccept)(nil) 697 698 // Name implements TestCase.Name. 699 func (*FilterOutputInterfaceInvertAccept) Name() string { 700 return "FilterOutputInterfaceInvertAccept" 701 } 702 703 // ContainerAction implements TestCase.ContainerAction. 704 func (*FilterOutputInterfaceInvertAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 705 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "!", "-o", "lo", "-j", "ACCEPT"); err != nil { 706 return err 707 } 708 709 // Listen for TCP packets on accept port. 710 return listenTCP(ctx, acceptPort, ipv6) 711 } 712 713 // LocalAction implements TestCase.LocalAction. 714 func (*FilterOutputInterfaceInvertAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 715 return connectTCP(ctx, ip, acceptPort, ipv6) 716 } 717 718 // FilterOutputInvertSportAccept tests that we can send packets on a negated 719 // --sport match 720 type FilterOutputInvertSportAccept struct{ baseCase } 721 722 var _ TestCase = (*FilterOutputInvertSportAccept)(nil) 723 724 // Name implements TestCase.Name. 725 func (*FilterOutputInvertSportAccept) Name() string { 726 return "FilterOutputInvertSportAccept" 727 } 728 729 // ContainerAction implements TestCase.ContainerAction. 730 func (*FilterOutputInvertSportAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 731 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "!", "--sport", fmt.Sprintf("%d", dropPort), "-j", "ACCEPT"); err != nil { 732 return err 733 } 734 735 // Listen for TCP packets on accept port. 736 return listenTCP(ctx, acceptPort, ipv6) 737 } 738 739 // LocalAction implements TestCase.LocalAction. 740 func (*FilterOutputInvertSportAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 741 return connectTCP(ctx, ip, acceptPort, ipv6) 742 } 743 744 // FilterOutputInvertSportDrop tests that we can send packets on a negated 745 // --dport match 746 type FilterOutputInvertSportDrop struct{ baseCase } 747 748 var _ TestCase = (*FilterOutputInvertSportDrop)(nil) 749 750 // Name implements TestCase.Name. 751 func (*FilterOutputInvertSportDrop) Name() string { 752 return "FilterOutputInvertSportDrop" 753 } 754 755 // ContainerAction implements TestCase.ContainerAction. 756 func (*FilterOutputInvertSportDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { 757 if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "!", "--sport", fmt.Sprintf("%d", acceptPort), "-j", "DROP"); err != nil { 758 return err 759 } 760 761 // Listen for TCP packets on accept port. 762 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 763 defer cancel() 764 if err := listenTCP(timedCtx, dropPort, ipv6); err == nil { 765 return fmt.Errorf("connection was established when it shouldnt have been") 766 } else if !errors.Is(err, context.DeadlineExceeded) { 767 return fmt.Errorf("error reading: %v", err) 768 } 769 770 return nil 771 } 772 773 // LocalAction implements TestCase.LocalAction. 774 func (*FilterOutputInvertSportDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { 775 timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) 776 defer cancel() 777 if err := connectTCP(timedCtx, ip, dropPort, ipv6); err == nil { 778 return fmt.Errorf("connection on %d port was accepted when it should have been dropped", dropPort) 779 } 780 781 return nil 782 }