github.com/rootless-containers/rootlesskit/v2@v2.3.4/pkg/network/lxcusernic/lxcusernic.go (about)

     1  package lxcusernic
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"os"
     9  	"os/exec"
    10  	"strconv"
    11  	"strings"
    12  	"syscall"
    13  	"time"
    14  
    15  	"github.com/insomniacslk/dhcp/dhcpv4"
    16  	"github.com/insomniacslk/dhcp/dhcpv4/client4"
    17  
    18  	"github.com/containernetworking/plugins/pkg/ns"
    19  	"github.com/rootless-containers/rootlesskit/v2/pkg/api"
    20  	"github.com/rootless-containers/rootlesskit/v2/pkg/common"
    21  	"github.com/rootless-containers/rootlesskit/v2/pkg/messages"
    22  	"github.com/rootless-containers/rootlesskit/v2/pkg/network"
    23  	"github.com/sirupsen/logrus"
    24  )
    25  
    26  func NewParentDriver(binary string, mtu int, bridge, ifname string) (network.ParentDriver, error) {
    27  	if binary == "" {
    28  		return nil, errors.New("got empty binary")
    29  	}
    30  	if mtu < 0 {
    31  		return nil, errors.New("got negative mtu")
    32  	}
    33  	if mtu == 0 {
    34  		mtu = 1500
    35  	}
    36  	if bridge == "" {
    37  		return nil, errors.New("got empty bridge")
    38  	}
    39  	if ifname == "" {
    40  		ifname = "eth0"
    41  	}
    42  	return &parentDriver{
    43  		binary: binary,
    44  		mtu:    mtu,
    45  		bridge: bridge,
    46  		ifname: ifname,
    47  	}, nil
    48  }
    49  
    50  type parentDriver struct {
    51  	binary string
    52  	mtu    int
    53  	bridge string
    54  	ifname string
    55  }
    56  
    57  const DriverName = "lxc-user-nic"
    58  
    59  func (d *parentDriver) Info(ctx context.Context) (*api.NetworkDriverInfo, error) {
    60  	return &api.NetworkDriverInfo{
    61  		Driver: DriverName,
    62  		// TODO: fill DNS
    63  		// TODO: fill IP
    64  		DynamicChildIP: true,
    65  	}, nil
    66  }
    67  
    68  func (d *parentDriver) MTU() int {
    69  	return d.mtu
    70  }
    71  
    72  func (d *parentDriver) ConfigureNetwork(childPID int, stateDir, detachedNetNSPath string) (*messages.ParentInitNetworkDriverCompleted, func() error, error) {
    73  	if detachedNetNSPath != "" {
    74  		cmd := exec.Command("nsenter", "-t", strconv.Itoa(childPID), "-n"+detachedNetNSPath, "--no-fork", "-m", "-U", "--preserve-credentials", "sleep", "infinity")
    75  		cmd.SysProcAttr = &syscall.SysProcAttr{
    76  			Pdeathsig: syscall.SIGKILL,
    77  		}
    78  		err := cmd.Start()
    79  		if err != nil {
    80  			return nil, nil, err
    81  		}
    82  		childPID = cmd.Process.Pid
    83  	}
    84  	var cleanups []func() error
    85  	dummyLXCPath := "/dev/null"
    86  	dummyLXCName := "dummy"
    87  	cmd := exec.Command(d.binary, "create", dummyLXCPath, dummyLXCName, strconv.Itoa(childPID), "veth", d.bridge, d.ifname)
    88  	b, err := cmd.CombinedOutput()
    89  	if err != nil {
    90  		return nil, common.Seq(cleanups), fmt.Errorf("%s failed: %s: %w", d.binary, string(b), err)
    91  	}
    92  	netmsg := messages.ParentInitNetworkDriverCompleted{
    93  		Dev: d.ifname,
    94  		// IP, Netmask, Gateway, and DNS are configured in Child (via DHCP)
    95  		MTU: d.mtu,
    96  	}
    97  	return &netmsg, common.Seq(cleanups), nil
    98  }
    99  
   100  func NewChildDriver() network.ChildDriver {
   101  	return &childDriver{}
   102  }
   103  
   104  type childDriver struct {
   105  }
   106  
   107  func exchangeDHCP(c *client4.Client, dev string, detachedNetNSPath string) (*dhcpv4.DHCPv4, error) {
   108  	logrus.Debugf("exchanging DHCP messages using %s, may take a few seconds", dev)
   109  	var (
   110  		ps  []*dhcpv4.DHCPv4
   111  		err error
   112  	)
   113  	exchange := func(ns.NetNS) error {
   114  		for {
   115  			ps, err = c.Exchange(dev)
   116  			if err != nil {
   117  				// `github.com/insomniacslk/dhcp` does not use errors.Wrap,
   118  				// so we need to compare the string.
   119  				if strings.Contains(err.Error(), "interrupted system call") {
   120  					// Retry on EINTR
   121  					continue
   122  				}
   123  				return fmt.Errorf("could not exchange DHCP with %s: %w", dev, err)
   124  			}
   125  			return nil
   126  		}
   127  	}
   128  	nsPath := "/proc/self/ns/net"
   129  	if detachedNetNSPath != "" {
   130  		nsPath = detachedNetNSPath
   131  	}
   132  	if err := ns.WithNetNSPath(nsPath, exchange); err != nil {
   133  		return nil, err
   134  	}
   135  	if len(ps) < 1 {
   136  		return nil, errors.New("got empty DHCP message")
   137  	}
   138  	var ack *dhcpv4.DHCPv4
   139  	for i, p := range ps {
   140  		logrus.Debugf("DHCP message %d: %s", i, p.Summary())
   141  		if p.MessageType() == dhcpv4.MessageTypeAck {
   142  			ack = p
   143  		}
   144  	}
   145  	if ack == nil {
   146  		return nil, errors.New("did not get DHCPACK")
   147  	}
   148  	return ack, nil
   149  }
   150  
   151  func (d *childDriver) ChildDriverInfo() (*network.ChildDriverInfo, error) {
   152  	return &network.ChildDriverInfo{
   153  		ConfiguresInterface: false,
   154  	}, nil
   155  }
   156  
   157  func (d *childDriver) ConfigureNetworkChild(netmsg *messages.ParentInitNetworkDriverCompleted, detachedNetNSPath string) (string, error) {
   158  	dev := netmsg.Dev
   159  	if dev == "" {
   160  		return "", errors.New("could not determine the dev")
   161  	}
   162  	nsPath := "/proc/self/ns/net"
   163  	if detachedNetNSPath != "" {
   164  		nsPath = detachedNetNSPath
   165  	}
   166  	cmds := [][]string{
   167  		// FIXME(AkihiroSuda): this should be moved to pkg/child?
   168  		{"nsenter", "-n" + nsPath, "ip", "link", "set", dev, "up"},
   169  	}
   170  	if err := common.Execs(os.Stderr, os.Environ(), cmds); err != nil {
   171  		return "", fmt.Errorf("executing %v: %w", cmds, err)
   172  	}
   173  	c := client4.NewClient()
   174  	c.ReadTimeout = 30 * time.Second
   175  	c.WriteTimeout = 30 * time.Second
   176  	p, err := exchangeDHCP(c, dev, detachedNetNSPath)
   177  	if err != nil {
   178  		return "", err
   179  	}
   180  	if p.YourIPAddr.Equal(net.IPv4zero) {
   181  		return "", errors.New("got zero YourIPAddr")
   182  	}
   183  	if len(p.Router()) == 0 {
   184  		return "", errors.New("got no Router")
   185  	}
   186  	if len(p.DNS()) == 0 {
   187  		return "", errors.New("got no DNS")
   188  	}
   189  	netmsg.IP = p.YourIPAddr.To4().String()
   190  	netmask, _ := p.SubnetMask().Size()
   191  	netmsg.Netmask = netmask
   192  	netmsg.Gateway = p.Router()[0].To4().String()
   193  	netmsg.DNS = []string{p.DNS()[0].To4().String()}
   194  	go dhcpRenewRoutine(c, dev, p.YourIPAddr.To4(), p.IPAddressLeaseTime(time.Hour), detachedNetNSPath)
   195  	return dev, nil
   196  }
   197  
   198  func dhcpRenewRoutine(c *client4.Client, dev string, initialIP net.IP, lease time.Duration, detachedNetNSPath string) {
   199  	for {
   200  		if lease <= 0 {
   201  			return
   202  		}
   203  		logrus.Debugf("DHCP lease=%s, sleeping lease * 0.9", lease)
   204  		time.Sleep(time.Duration(float64(lease) * 0.9))
   205  		p, err := exchangeDHCP(c, dev, detachedNetNSPath)
   206  		if err != nil {
   207  			panic(err)
   208  		}
   209  		ip := p.YourIPAddr.To4()
   210  		if !ip.Equal(initialIP) {
   211  			// FIXME(AkihiroSuda): unlikely to happen for LXC usecase but good to consider supporting
   212  			panic(fmt.Errorf("expected to retain %s, got %s", initialIP, ip))
   213  		}
   214  		lease = p.IPAddressLeaseTime(lease)
   215  	}
   216  }