github.com/hugelgupf/u-root@v0.0.0-20191023214958-4807c632154c/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 const linkUpAttempt = 30 * time.Second 31 32 // isIpv6LinkReady returns true if the interface has a link-local address 33 // which is not tentative. 34 func isIpv6LinkReady(l netlink.Link) (bool, error) { 35 addrs, err := netlink.AddrList(l, netlink.FAMILY_V6) 36 if err != nil { 37 return false, err 38 } 39 for _, addr := range addrs { 40 if addr.IP.IsLinkLocalUnicast() && (addr.Flags&unix.IFA_F_TENTATIVE == 0) { 41 if addr.Flags&unix.IFA_F_DADFAILED != 0 { 42 log.Printf("DADFAILED for %v, continuing anyhow", addr.IP) 43 } 44 return true, nil 45 } 46 } 47 return false, nil 48 } 49 50 // IfUp ensures the given network interface is up and returns the link object. 51 func IfUp(ifname string) (netlink.Link, error) { 52 start := time.Now() 53 for time.Since(start) < linkUpAttempt { 54 // Note that it may seem odd to keep trying the LinkByName 55 // operation, but consider that a hotplug device such as USB 56 // ethernet can just vanish. 57 iface, err := netlink.LinkByName(ifname) 58 if err != nil { 59 return nil, fmt.Errorf("cannot get interface %q by name: %v", ifname, err) 60 } 61 62 if iface.Attrs().Flags&net.FlagUp == net.FlagUp { 63 return iface, nil 64 } 65 66 if err := netlink.LinkSetUp(iface); err != nil { 67 return nil, fmt.Errorf("interface %q: %v can't make it up: %v", ifname, iface, err) 68 } 69 time.Sleep(100 * time.Millisecond) 70 } 71 72 return nil, fmt.Errorf("link %q still down after %d seconds", ifname, linkUpAttempt) 73 } 74 75 // WriteDNSSettings writes the given nameservers, search list, and domain to resolv.conf. 76 func WriteDNSSettings(ns []net.IP, sl []string, domain string) error { 77 rc := &bytes.Buffer{} 78 if domain != "" { 79 rc.WriteString(fmt.Sprintf("domain %s\n", domain)) 80 } 81 for _, ip := range ns { 82 rc.WriteString(fmt.Sprintf("nameserver %s\n", ip)) 83 } 84 if sl != nil { 85 rc.WriteString("search ") 86 rc.WriteString(strings.Join(sl, " ")) 87 rc.WriteString("\n") 88 } 89 return ioutil.WriteFile("/etc/resolv.conf", rc.Bytes(), 0644) 90 } 91 92 // Lease is a network configuration obtained by DHCP. 93 type Lease interface { 94 fmt.Stringer 95 96 // Configure configures the associated interface with the network 97 // configuration. 98 Configure() error 99 100 // Boot is a URL to obtain booting information from that was part of 101 // the network config. 102 Boot() (*url.URL, error) 103 104 // ISCSIBoot returns the target address and volume name to boot from if 105 // they were part of the DHCP message. 106 ISCSIBoot() (*net.TCPAddr, string, error) 107 108 // Link is the interface the configuration is for. 109 Link() netlink.Link 110 } 111 112 // LogLevel is the amount of information to log. 113 type LogLevel uint8 114 115 // LogLevel are the levels. 116 const ( 117 LogInfo LogLevel = 0 118 LogSummary LogLevel = 1 119 LogDebug LogLevel = 2 120 ) 121 122 // Config is a DHCP client configuration. 123 type Config struct { 124 // Timeout is the timeout for one DHCP request attempt. 125 Timeout time.Duration 126 127 // Retries is how many times to retry DHCP attempts. 128 Retries int 129 130 // LogLevel determines the amount of information printed for each 131 // attempt. The highest log level should print each entire packet sent 132 // and received. 133 LogLevel LogLevel 134 } 135 136 func lease4(ctx context.Context, iface netlink.Link, c Config) (Lease, error) { 137 mods := []nclient4.ClientOpt{ 138 nclient4.WithTimeout(c.Timeout), 139 nclient4.WithRetry(c.Retries), 140 } 141 switch c.LogLevel { 142 case LogSummary: 143 mods = append(mods, nclient4.WithSummaryLogger()) 144 case LogDebug: 145 mods = append(mods, nclient4.WithDebugLogger()) 146 } 147 client, err := nclient4.New(iface.Attrs().Name, mods...) 148 if err != nil { 149 return nil, err 150 } 151 152 log.Printf("Attempting to get DHCPv4 lease on %s", iface.Attrs().Name) 153 _, p, err := client.Request(ctx, dhcpv4.WithNetboot, 154 dhcpv4.WithOption(dhcpv4.OptClassIdentifier("PXE UROOT")), 155 dhcpv4.WithRequestedOptions(dhcpv4.OptionSubnetMask)) 156 if err != nil { 157 return nil, err 158 } 159 160 packet := NewPacket4(iface, p) 161 log.Printf("Got DHCPv4 lease on %s: %v", iface.Attrs().Name, p.Summary()) 162 return packet, nil 163 } 164 165 func lease6(ctx context.Context, iface netlink.Link, c Config) (Lease, error) { 166 // For ipv6, we cannot bind to the port until Duplicate Address 167 // Detection (DAD) is complete which is indicated by the link being no 168 // longer marked as "tentative". This usually takes about a second. 169 170 // If the link is never going to be ready, don't wait forever. 171 // (The user may not have configured a ctx with a timeout.) 172 // 173 // Hardcode the timeout to 30s for now. 174 linkTimeout := time.After(linkUpAttempt) 175 for { 176 if ready, err := isIpv6LinkReady(iface); err != nil { 177 return nil, err 178 } else if ready { 179 break 180 } 181 select { 182 case <-time.After(100 * time.Millisecond): 183 continue 184 case <-linkTimeout: 185 return nil, errors.New("timeout after waiting for a non-tentative IPv6 address") 186 case <-ctx.Done(): 187 return nil, errors.New("timeout after waiting for a non-tentative IPv6 address") 188 } 189 } 190 191 mods := []nclient6.ClientOpt{ 192 nclient6.WithTimeout(c.Timeout), 193 nclient6.WithRetry(c.Retries), 194 } 195 switch c.LogLevel { 196 case LogSummary: 197 mods = append(mods, nclient6.WithSummaryLogger()) 198 case LogDebug: 199 mods = append(mods, nclient6.WithDebugLogger()) 200 } 201 client, err := nclient6.New(iface.Attrs().Name, mods...) 202 if err != nil { 203 return nil, err 204 } 205 206 log.Printf("Attempting to get DHCPv6 lease on %s", iface.Attrs().Name) 207 p, err := client.RapidSolicit(ctx, dhcpv6.WithNetboot) 208 if err != nil { 209 return nil, err 210 } 211 212 packet := NewPacket6(iface, p) 213 log.Printf("Got DHCPv6 lease on %s: %v", iface.Attrs().Name, p.Summary()) 214 return packet, nil 215 } 216 217 // NetworkProtocol is either IPv4 or IPv6. 218 type NetworkProtocol int 219 220 // Possible network protocols; either IPv4, IPv6, or both. 221 const ( 222 NetIPv4 NetworkProtocol = 1 223 NetIPv6 NetworkProtocol = 2 224 NetBoth NetworkProtocol = 3 225 ) 226 227 func (n NetworkProtocol) String() string { 228 switch n { 229 case NetIPv4: 230 return "IPv4" 231 case NetIPv6: 232 return "IPv6" 233 case NetBoth: 234 return "IPv4+IPv6" 235 } 236 return fmt.Sprintf("unknown network protocol (%#x)", n) 237 } 238 239 // Result is the result of a particular DHCP attempt. 240 type Result struct { 241 // Protocol is the IP protocol that we tried to configure. 242 Protocol NetworkProtocol 243 244 // Interface is the network interface the attempt was sent on. 245 Interface netlink.Link 246 247 // Lease is the DHCP configuration returned. 248 // 249 // If Lease is set, Err is nil. 250 Lease Lease 251 252 // Err is an error that occured during the DHCP attempt. 253 Err error 254 } 255 256 // SendRequests coordinates soliciting DHCP configuration on all ifs. 257 // 258 // ipv4 and ipv6 determine whether to send DHCPv4 and DHCPv6 requests, 259 // respectively. 260 // 261 // The *Result channel will be closed when all requests have completed. 262 func SendRequests(ctx context.Context, ifs []netlink.Link, ipv4, ipv6 bool, c Config) chan *Result { 263 // Yeah, this is a hack, until we can cancel all leases in progress. 264 r := make(chan *Result, 3*len(ifs)) 265 266 var wg sync.WaitGroup 267 for _, iface := range ifs { 268 wg.Add(1) 269 go func(iface netlink.Link) { 270 defer wg.Done() 271 272 log.Printf("Bringing up interface %s...", iface.Attrs().Name) 273 if _, err := IfUp(iface.Attrs().Name); err != nil { 274 log.Printf("Could not bring up interface %s: %v", iface.Attrs().Name, err) 275 return 276 } 277 278 if ipv4 { 279 wg.Add(1) 280 go func(iface netlink.Link) { 281 defer wg.Done() 282 lease, err := lease4(ctx, iface, c) 283 r <- &Result{NetIPv4, iface, lease, err} 284 }(iface) 285 } 286 287 if ipv6 { 288 wg.Add(1) 289 go func(iface netlink.Link) { 290 defer wg.Done() 291 lease, err := lease6(ctx, iface, c) 292 r <- &Result{NetIPv6, iface, lease, err} 293 }(iface) 294 } 295 }(iface) 296 } 297 298 go func() { 299 wg.Wait() 300 close(r) 301 }() 302 return r 303 }