gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/iptables/nat.go (about)

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