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