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 }