github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/dhclient/dhclient.go (about) 1 // Copyright 2017-2019 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package dhclient allows for getting both DHCPv4 and DHCPv6 leases on 6 // multiple network interfaces in parallel. 7 package dhclient 8 9 import ( 10 "bytes" 11 "context" 12 "errors" 13 "fmt" 14 "io/ioutil" 15 "log" 16 "net" 17 "net/url" 18 "strings" 19 "sync" 20 "time" 21 22 "github.com/insomniacslk/dhcp/dhcpv4" 23 "github.com/insomniacslk/dhcp/dhcpv4/nclient4" 24 "github.com/insomniacslk/dhcp/dhcpv6" 25 "github.com/insomniacslk/dhcp/dhcpv6/nclient6" 26 "github.com/vishvananda/netlink" 27 "golang.org/x/sys/unix" 28 ) 29 30 // isIpv6LinkReady returns true if the interface has a link-local address 31 // which is not tentative. 32 func isIpv6LinkReady(l netlink.Link) (bool, error) { 33 addrs, err := netlink.AddrList(l, netlink.FAMILY_V6) 34 if err != nil { 35 return false, err 36 } 37 for _, addr := range addrs { 38 if addr.IP.IsLinkLocalUnicast() && (addr.Flags&unix.IFA_F_TENTATIVE == 0) { 39 if addr.Flags&unix.IFA_F_DADFAILED != 0 { 40 log.Printf("DADFAILED for %v, continuing anyhow", addr.IP) 41 } 42 return true, nil 43 } 44 } 45 return false, nil 46 } 47 48 // IfUp ensures the given network interface is up and returns the link object. 49 func IfUp(ifname string, linkUpTimeout time.Duration) (netlink.Link, error) { 50 start := time.Now() 51 for time.Since(start) < linkUpTimeout { 52 // Note that it may seem odd to keep trying the LinkByName 53 // operation, but consider that a hotplug device such as USB 54 // ethernet can just vanish. 55 iface, err := netlink.LinkByName(ifname) 56 if err != nil { 57 return nil, fmt.Errorf("cannot get interface %q by name: %v", ifname, err) 58 } 59 60 // Check if link is actually operational. 61 // https://www.kernel.org/doc/Documentation/networking/operstates.txt states that we should check 62 // for OperUp and OperUnknown. 63 if o := iface.Attrs().OperState; o == netlink.OperUp || o == netlink.OperUnknown { 64 return iface, nil 65 } 66 67 if err := netlink.LinkSetUp(iface); err != nil { 68 return nil, fmt.Errorf("interface %q: %v can't make it up: %v", ifname, iface, err) 69 } 70 time.Sleep(100 * time.Millisecond) 71 } 72 73 return nil, fmt.Errorf("link %q still down after %v seconds", ifname, linkUpTimeout.Seconds()) 74 } 75 76 // WriteDNSSettings writes the given nameservers, search list, and domain to resolv.conf. 77 func WriteDNSSettings(ns []net.IP, sl []string, domain string) error { 78 rc := &bytes.Buffer{} 79 if domain != "" { 80 rc.WriteString(fmt.Sprintf("domain %s\n", domain)) 81 } 82 for _, ip := range ns { 83 rc.WriteString(fmt.Sprintf("nameserver %s\n", ip)) 84 } 85 if sl != nil { 86 rc.WriteString("search ") 87 rc.WriteString(strings.Join(sl, " ")) 88 rc.WriteString("\n") 89 } 90 return ioutil.WriteFile("/etc/resolv.conf", rc.Bytes(), 0644) 91 } 92 93 // Lease is a network configuration obtained by DHCP. 94 type Lease interface { 95 fmt.Stringer 96 97 // Configure configures the associated interface with the network 98 // configuration. 99 Configure() error 100 101 // Boot is a URL to obtain booting information from that was part of 102 // the network config. 103 Boot() (*url.URL, error) 104 105 // ISCSIBoot returns the target address and volume name to boot from if 106 // they were part of the DHCP message. 107 ISCSIBoot() (*net.TCPAddr, string, error) 108 109 // Link is the interface the configuration is for. 110 Link() netlink.Link 111 112 // Return the full DHCP response, this is either a *dhcpv4.DHCPv4 or a 113 // *dhcpv6.Message. 114 Message() (*dhcpv4.DHCPv4, *dhcpv6.Message) 115 } 116 117 // LogLevel is the amount of information to log. 118 type LogLevel uint8 119 120 // LogLevel are the levels. 121 const ( 122 LogInfo LogLevel = 0 123 LogSummary LogLevel = 1 124 LogDebug LogLevel = 2 125 ) 126 127 // Config is a DHCP client configuration. 128 type Config struct { 129 // Timeout is the timeout for one DHCP request attempt. 130 Timeout time.Duration 131 132 // Retries is how many times to retry DHCP attempts. 133 Retries int 134 135 // LogLevel determines the amount of information printed for each 136 // attempt. The highest log level should print each entire packet sent 137 // and received. 138 LogLevel LogLevel 139 140 // Modifiers4 allows modifications to the IPv4 DHCP request. 141 Modifiers4 []dhcpv4.Modifier 142 143 // Modifiers6 allows modifications to the IPv6 DHCP request. 144 Modifiers6 []dhcpv6.Modifier 145 146 // V6ServerAddr can be a unicast or broadcast destination for DHCPv6 147 // messages. 148 // 149 // If not set, it will default to nclient6's default (all servers & 150 // relay agents). 151 V6ServerAddr *net.UDPAddr 152 153 // V4ServerAddr can be a unicast or broadcast destination for IPv4 DHCP 154 // messages. 155 // 156 // If not set, it will default to nclient4's default (DHCP broadcast 157 // address). 158 V4ServerAddr *net.UDPAddr 159 } 160 161 func lease4(ctx context.Context, iface netlink.Link, c Config) (Lease, error) { 162 mods := []nclient4.ClientOpt{ 163 nclient4.WithTimeout(c.Timeout), 164 nclient4.WithRetry(c.Retries), 165 } 166 switch c.LogLevel { 167 case LogSummary: 168 mods = append(mods, nclient4.WithSummaryLogger()) 169 case LogDebug: 170 mods = append(mods, nclient4.WithDebugLogger()) 171 } 172 if c.V4ServerAddr != nil { 173 mods = append(mods, nclient4.WithServerAddr(c.V4ServerAddr)) 174 } 175 client, err := nclient4.New(iface.Attrs().Name, mods...) 176 if err != nil { 177 return nil, err 178 } 179 defer client.Close() 180 181 // Prepend modifiers with default options, so they can be overriden. 182 reqmods := append( 183 []dhcpv4.Modifier{ 184 dhcpv4.WithOption(dhcpv4.OptClassIdentifier("PXE UROOT")), 185 dhcpv4.WithRequestedOptions(dhcpv4.OptionSubnetMask), 186 dhcpv4.WithNetboot, 187 }, 188 c.Modifiers4...) 189 190 log.Printf("Attempting to get DHCPv4 lease on %s", iface.Attrs().Name) 191 _, p, err := client.Request(ctx, reqmods...) 192 if err != nil { 193 return nil, err 194 } 195 196 packet := NewPacket4(iface, p) 197 log.Printf("Got DHCPv4 lease on %s: %v", iface.Attrs().Name, p.Summary()) 198 return packet, nil 199 } 200 201 func lease6(ctx context.Context, iface netlink.Link, c Config, linkUpTimeout time.Duration) (Lease, error) { 202 // For ipv6, we cannot bind to the port until Duplicate Address 203 // Detection (DAD) is complete which is indicated by the link being no 204 // longer marked as "tentative". This usually takes about a second. 205 206 // If the link is never going to be ready, don't wait forever. 207 // (The user may not have configured a ctx with a timeout.) 208 // 209 // Hardcode the timeout to 30s for now. 210 linkTimeout := time.After(linkUpTimeout) 211 for { 212 if ready, err := isIpv6LinkReady(iface); err != nil { 213 return nil, err 214 } else if ready { 215 break 216 } 217 select { 218 case <-time.After(100 * time.Millisecond): 219 continue 220 case <-linkTimeout: 221 return nil, errors.New("timeout after waiting for a non-tentative IPv6 address") 222 case <-ctx.Done(): 223 return nil, errors.New("timeout after waiting for a non-tentative IPv6 address") 224 } 225 } 226 227 mods := []nclient6.ClientOpt{ 228 nclient6.WithTimeout(c.Timeout), 229 nclient6.WithRetry(c.Retries), 230 } 231 switch c.LogLevel { 232 case LogSummary: 233 mods = append(mods, nclient6.WithSummaryLogger()) 234 case LogDebug: 235 mods = append(mods, nclient6.WithDebugLogger()) 236 } 237 if c.V6ServerAddr != nil { 238 mods = append(mods, nclient6.WithBroadcastAddr(c.V6ServerAddr)) 239 } 240 client, err := nclient6.New(iface.Attrs().Name, mods...) 241 if err != nil { 242 return nil, err 243 } 244 defer client.Close() 245 246 // Prepend modifiers with default options, so they can be overriden. 247 reqmods := append( 248 []dhcpv6.Modifier{ 249 dhcpv6.WithNetboot, 250 }, 251 c.Modifiers6...) 252 253 log.Printf("Attempting to get DHCPv6 lease on %s", iface.Attrs().Name) 254 p, err := client.RapidSolicit(ctx, reqmods...) 255 if err != nil { 256 return nil, err 257 } 258 259 packet := NewPacket6(iface, p) 260 log.Printf("Got DHCPv6 lease on %s: %v", iface.Attrs().Name, p.Summary()) 261 return packet, nil 262 } 263 264 // NetworkProtocol is either IPv4 or IPv6. 265 type NetworkProtocol int 266 267 // Possible network protocols; either IPv4, IPv6, or both. 268 const ( 269 NetIPv4 NetworkProtocol = 1 270 NetIPv6 NetworkProtocol = 2 271 NetBoth NetworkProtocol = 3 272 ) 273 274 func (n NetworkProtocol) String() string { 275 switch n { 276 case NetIPv4: 277 return "IPv4" 278 case NetIPv6: 279 return "IPv6" 280 case NetBoth: 281 return "IPv4+IPv6" 282 } 283 return fmt.Sprintf("unknown network protocol (%#x)", n) 284 } 285 286 // Result is the result of a particular DHCP attempt. 287 type Result struct { 288 // Protocol is the IP protocol that we tried to configure. 289 Protocol NetworkProtocol 290 291 // Interface is the network interface the attempt was sent on. 292 Interface netlink.Link 293 294 // Lease is the DHCP configuration returned. 295 // 296 // If Lease is set, Err is nil. 297 Lease Lease 298 299 // Err is an error that occured during the DHCP attempt. 300 Err error 301 } 302 303 // SendRequests coordinates soliciting DHCP configuration on all ifs. 304 // 305 // ipv4 and ipv6 determine whether to send DHCPv4 and DHCPv6 requests, 306 // respectively. 307 // 308 // The *Result channel will be closed when all requests have completed. 309 func SendRequests(ctx context.Context, ifs []netlink.Link, ipv4, ipv6 bool, c Config, linkUpTimeout time.Duration) chan *Result { 310 // Yeah, this is a hack, until we can cancel all leases in progress. 311 r := make(chan *Result, 3*len(ifs)) 312 313 var wg sync.WaitGroup 314 for _, iface := range ifs { 315 wg.Add(1) 316 go func(iface netlink.Link) { 317 defer wg.Done() 318 319 log.Printf("Bringing up interface %s...", iface.Attrs().Name) 320 if _, err := IfUp(iface.Attrs().Name, linkUpTimeout); err != nil { 321 log.Printf("Could not bring up interface %s: %v", iface.Attrs().Name, err) 322 return 323 } 324 325 if ipv4 { 326 wg.Add(1) 327 go func(iface netlink.Link) { 328 defer wg.Done() 329 lease, err := lease4(ctx, iface, c) 330 r <- &Result{NetIPv4, iface, lease, err} 331 }(iface) 332 } 333 334 if ipv6 { 335 wg.Add(1) 336 go func(iface netlink.Link) { 337 defer wg.Done() 338 lease, err := lease6(ctx, iface, c, linkUpTimeout) 339 r <- &Result{NetIPv6, iface, lease, err} 340 }(iface) 341 } 342 }(iface) 343 } 344 345 go func() { 346 wg.Wait() 347 close(r) 348 }() 349 return r 350 }