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