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

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package iptables
    16  
    17  import (
    18  	"context"
    19  	"encoding/binary"
    20  	"errors"
    21  	"fmt"
    22  	"net"
    23  	"os/exec"
    24  	"strings"
    25  	"time"
    26  
    27  	"gvisor.dev/gvisor/pkg/test/testutil"
    28  )
    29  
    30  // filterTable calls `ip{6}tables -t filter` with the given args.
    31  func filterTable(ipv6 bool, args ...string) error {
    32  	return tableCmd(ipv6, "filter", args)
    33  }
    34  
    35  // natTable calls `ip{6}tables -t nat` with the given args.
    36  func natTable(ipv6 bool, args ...string) error {
    37  	return tableCmd(ipv6, "nat", args)
    38  }
    39  
    40  func tableCmd(ipv6 bool, table string, args []string) error {
    41  	args = append([]string{"-t", table}, args...)
    42  	binary := "iptables-legacy"
    43  	if ipv6 {
    44  		binary = "ip6tables-legacy"
    45  	}
    46  	cmd := exec.Command(binary, args...)
    47  	if out, err := cmd.CombinedOutput(); err != nil {
    48  		return fmt.Errorf("error running iptables with args %v\nerror: %v\noutput: %s", args, err, string(out))
    49  	}
    50  	return nil
    51  }
    52  
    53  // filterTableRules is like filterTable, but runs multiple iptables commands.
    54  func filterTableRules(ipv6 bool, argsList [][]string) error {
    55  	return tableRules(ipv6, "filter", argsList)
    56  }
    57  
    58  // natTableRules is like natTable, but runs multiple iptables commands.
    59  func natTableRules(ipv6 bool, argsList [][]string) error {
    60  	return tableRules(ipv6, "nat", argsList)
    61  }
    62  
    63  func tableRules(ipv6 bool, table string, argsList [][]string) error {
    64  	for _, args := range argsList {
    65  		if err := tableCmd(ipv6, table, args); err != nil {
    66  			return err
    67  		}
    68  	}
    69  	return nil
    70  }
    71  
    72  // listenUDP listens on a UDP port and returns nil if the first read from that
    73  // port is successful.
    74  func listenUDP(ctx context.Context, port int, ipv6 bool) error {
    75  	_, err := listenUDPFrom(ctx, port, ipv6)
    76  	return err
    77  }
    78  
    79  // listenUDPFrom listens on a UDP port and returns the sender's UDP address if
    80  // the first read from that port is successful.
    81  func listenUDPFrom(ctx context.Context, port int, ipv6 bool) (*net.UDPAddr, error) {
    82  	localAddr := net.UDPAddr{
    83  		Port: port,
    84  	}
    85  	conn, err := net.ListenUDP(udpNetwork(ipv6), &localAddr)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	defer conn.Close()
    90  
    91  	type result struct {
    92  		remoteAddr *net.UDPAddr
    93  		err        error
    94  	}
    95  
    96  	ch := make(chan result)
    97  	go func() {
    98  		_, remoteAddr, err := conn.ReadFromUDP([]byte{0})
    99  		ch <- result{remoteAddr, err}
   100  	}()
   101  
   102  	select {
   103  	case res := <-ch:
   104  		return res.remoteAddr, res.err
   105  	case <-ctx.Done():
   106  		return nil, fmt.Errorf("timed out reading from %s: %w", &localAddr, ctx.Err())
   107  	}
   108  }
   109  
   110  // sendUDPLoop sends 1 byte UDP packets repeatedly to the IP and port specified
   111  // over a duration.
   112  func sendUDPLoop(ctx context.Context, ip net.IP, port int, ipv6 bool) error {
   113  	remote := net.UDPAddr{
   114  		IP:   ip,
   115  		Port: port,
   116  	}
   117  	conn, err := net.DialUDP(udpNetwork(ipv6), nil, &remote)
   118  	if err != nil {
   119  		return err
   120  	}
   121  	defer conn.Close()
   122  
   123  	for {
   124  		// This may return an error (connection refused) if the remote
   125  		// hasn't started listening yet or they're dropping our
   126  		// packets. So we ignore Write errors and depend on the remote
   127  		// to report a failure if it doesn't get a packet it needs.
   128  		conn.Write([]byte{0})
   129  		select {
   130  		case <-ctx.Done():
   131  			// Being cancelled or timing out isn't an error, as we
   132  			// cannot tell with UDP whether we succeeded.
   133  			return nil
   134  		// Continue looping.
   135  		case <-time.After(200 * time.Millisecond):
   136  		}
   137  	}
   138  }
   139  
   140  // listenTCP listens for connections on a TCP port, and returns nil if a
   141  // connection is established.
   142  func listenTCP(ctx context.Context, port int, ipv6 bool) error {
   143  	_, err := listenTCPFrom(ctx, port, ipv6)
   144  	return err
   145  }
   146  
   147  // listenTCP listens for connections on a TCP port, and returns the remote
   148  // TCP address if a connection is established.
   149  func listenTCPFrom(ctx context.Context, port int, ipv6 bool) (net.Addr, error) {
   150  	localAddr := net.TCPAddr{
   151  		Port: port,
   152  	}
   153  
   154  	// Starts listening on port.
   155  	lConn, err := net.ListenTCP(tcpNetwork(ipv6), &localAddr)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	defer lConn.Close()
   160  
   161  	type result struct {
   162  		remoteAddr net.Addr
   163  		err        error
   164  	}
   165  
   166  	// Accept connections on port.
   167  	ch := make(chan result)
   168  	go func() {
   169  		conn, err := lConn.AcceptTCP()
   170  		var remoteAddr net.Addr
   171  		if err == nil {
   172  			remoteAddr = conn.RemoteAddr()
   173  		}
   174  		ch <- result{remoteAddr, err}
   175  		conn.Close()
   176  	}()
   177  
   178  	select {
   179  	case res := <-ch:
   180  		return res.remoteAddr, res.err
   181  	case <-ctx.Done():
   182  		return nil, fmt.Errorf("timed out waiting for a connection at %s: %w", &localAddr, ctx.Err())
   183  	}
   184  }
   185  
   186  // connectTCP connects to the given IP and port from an ephemeral local address.
   187  func connectTCP(ctx context.Context, ip net.IP, port int, ipv6 bool) error {
   188  	contAddr := net.TCPAddr{
   189  		IP:   ip,
   190  		Port: port,
   191  	}
   192  	// The container may not be listening when we first connect, so retry
   193  	// upon error.
   194  	callback := func() error {
   195  		var d net.Dialer
   196  		conn, err := d.DialContext(ctx, tcpNetwork(ipv6), contAddr.String())
   197  		if conn != nil {
   198  			conn.Close()
   199  		}
   200  		return err
   201  	}
   202  	if err := testutil.PollContext(ctx, callback); err != nil {
   203  		return fmt.Errorf("timed out waiting to connect IP on port %v, most recent error: %w", port, err)
   204  	}
   205  
   206  	return nil
   207  }
   208  
   209  // localAddrs returns a list of local network interface addresses. When ipv6 is
   210  // true, only IPv6 addresses are returned. Otherwise only IPv4 addresses are
   211  // returned.
   212  func localAddrs(ipv6 bool) ([]string, error) {
   213  	addrs, err := net.InterfaceAddrs()
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	addrStrs := make([]string, 0, len(addrs))
   218  	for _, addr := range addrs {
   219  		// Add only IPv4 or only IPv6 addresses.
   220  		parts := strings.Split(addr.String(), "/")
   221  		if len(parts) != 2 {
   222  			return nil, fmt.Errorf("bad interface address: %q", addr.String())
   223  		}
   224  		if isIPv6 := net.ParseIP(parts[0]).To4() == nil; isIPv6 == ipv6 {
   225  			addrStrs = append(addrStrs, addr.String())
   226  		}
   227  	}
   228  	return filterAddrs(addrStrs, ipv6), nil
   229  }
   230  
   231  func filterAddrs(addrs []string, ipv6 bool) []string {
   232  	addrStrs := make([]string, 0, len(addrs))
   233  	for _, addr := range addrs {
   234  		// Add only IPv4 or only IPv6 addresses.
   235  		parts := strings.Split(addr, "/")
   236  		if isIPv6 := net.ParseIP(parts[0]).To4() == nil; isIPv6 == ipv6 {
   237  			addrStrs = append(addrStrs, parts[0])
   238  		}
   239  	}
   240  	return addrStrs
   241  }
   242  
   243  // getInterfaceName returns the name of the interface other than loopback.
   244  func getInterfaceName() (string, bool) {
   245  	iface, ok := getNonLoopbackInterface()
   246  	if !ok {
   247  		return "", false
   248  	}
   249  	return iface.Name, true
   250  }
   251  
   252  func getInterfaceAddrs(ipv6 bool) ([]net.IP, error) {
   253  	iface, ok := getNonLoopbackInterface()
   254  	if !ok {
   255  		return nil, errors.New("no non-loopback interface found")
   256  	}
   257  	addrs, err := iface.Addrs()
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	// Get only IPv4 or IPv6 addresses.
   263  	ips := make([]net.IP, 0, len(addrs))
   264  	for _, addr := range addrs {
   265  		parts := strings.Split(addr.String(), "/")
   266  		var ip net.IP
   267  		// To16() returns IPv4 addresses as IPv4-mapped IPv6 addresses.
   268  		// So we check whether To4() returns nil to test whether the
   269  		// address is v4 or v6.
   270  		if v4 := net.ParseIP(parts[0]).To4(); ipv6 && v4 == nil {
   271  			ip = net.ParseIP(parts[0]).To16()
   272  		} else {
   273  			ip = v4
   274  		}
   275  		if ip != nil {
   276  			ips = append(ips, ip)
   277  		}
   278  	}
   279  	return ips, nil
   280  }
   281  
   282  func getNonLoopbackInterface() (net.Interface, bool) {
   283  	if interfaces, err := net.Interfaces(); err == nil {
   284  		for _, intf := range interfaces {
   285  			if intf.Name != "lo" {
   286  				return intf, true
   287  			}
   288  		}
   289  	}
   290  	return net.Interface{}, false
   291  }
   292  
   293  func htons(x uint16) uint16 {
   294  	buf := make([]byte, 2)
   295  	binary.BigEndian.PutUint16(buf, x)
   296  	return binary.LittleEndian.Uint16(buf)
   297  }
   298  
   299  func localIP(ipv6 bool) string {
   300  	if ipv6 {
   301  		return "::1"
   302  	}
   303  	return "127.0.0.1"
   304  }
   305  
   306  func nowhereIP(ipv6 bool) string {
   307  	if ipv6 {
   308  		return "2001:db8::1"
   309  	}
   310  	return "192.0.2.1"
   311  }
   312  
   313  // udpNetwork returns an IPv6 or IPv6 UDP network argument to net.Dial.
   314  func udpNetwork(ipv6 bool) string {
   315  	if ipv6 {
   316  		return "udp6"
   317  	}
   318  	return "udp4"
   319  }
   320  
   321  // tcpNetwork returns an IPv6 or IPv6 TCP network argument to net.Dial.
   322  func tcpNetwork(ipv6 bool) string {
   323  	if ipv6 {
   324  		return "tcp6"
   325  	}
   326  	return "tcp4"
   327  }