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  }