github.com/hugelgupf/u-root@v0.0.0-20191023214958-4807c632154c/pkg/dhclient/dhcp4.go (about) 1 // Copyright 2017-2018 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 6 7 import ( 8 "errors" 9 "fmt" 10 "net" 11 "net/url" 12 "strings" 13 14 "github.com/insomniacslk/dhcp/dhcpv4" 15 "github.com/vishvananda/netlink" 16 ) 17 18 // Packet4 implements convenience functions for DHCPv4 packets. 19 type Packet4 struct { 20 iface netlink.Link 21 P *dhcpv4.DHCPv4 22 } 23 24 var _ Lease = &Packet4{} 25 26 // NewPacket4 wraps a DHCPv4 packet with some convenience methods. 27 func NewPacket4(iface netlink.Link, p *dhcpv4.DHCPv4) *Packet4 { 28 return &Packet4{ 29 iface: iface, 30 P: p, 31 } 32 } 33 34 // Link is a netlink link 35 func (p *Packet4) Link() netlink.Link { 36 return p.iface 37 } 38 39 // GatherDNSSettings gets the DNS related infromation from a dhcp packet 40 // including, nameservers, domain, and search options 41 func (p *Packet4) GatherDNSSettings() (ns []net.IP, sl []string, dom string) { 42 if nameservers := p.P.DNS(); nameservers != nil { 43 ns = nameservers 44 } 45 if searchList := p.P.DomainSearch(); searchList != nil { 46 sl = searchList.Labels 47 } 48 if domain := p.P.DomainName(); domain != "" { 49 dom = domain 50 } 51 return 52 } 53 54 // Configure4 adds IP addresses, routes, and DNS servers to the system. 55 func Configure4(iface netlink.Link, packet *dhcpv4.DHCPv4) error { 56 p := NewPacket4(iface, packet) 57 return p.Configure() 58 } 59 60 // Configure configures interface using this packet. 61 func (p *Packet4) Configure() error { 62 l := p.Lease() 63 if l == nil { 64 return fmt.Errorf("packet has no IP lease") 65 } 66 67 // Add the address to the iface. 68 dst := &netlink.Addr{ 69 IPNet: l, 70 } 71 if err := netlink.AddrReplace(p.iface, dst); err != nil { 72 return fmt.Errorf("add/replace %s to %v: %v", dst, p.iface, err) 73 } 74 75 // RFC 3442 notes that if classless static routes are available, they 76 // have priority. You have to ignore the Route Option. 77 if routes := p.P.ClasslessStaticRoute(); routes != nil { 78 for _, route := range routes { 79 r := &netlink.Route{ 80 LinkIndex: p.iface.Attrs().Index, 81 Dst: route.Dest, 82 Gw: route.Router, 83 } 84 // If no gateway is specified, the destination must be link-local. 85 if r.Gw == nil || r.Gw.Equal(net.IPv4zero) { 86 r.Scope = netlink.SCOPE_LINK 87 } 88 89 if err := netlink.RouteReplace(r); err != nil { 90 return fmt.Errorf("%s: add %s: %v", p.iface.Attrs().Name, r, err) 91 } 92 } 93 } else if gw := p.P.Router(); len(gw) > 0 { 94 r := &netlink.Route{ 95 LinkIndex: p.iface.Attrs().Index, 96 Gw: gw[0], 97 } 98 99 if err := netlink.RouteReplace(r); err != nil { 100 return fmt.Errorf("%s: add %s: %v", p.iface.Attrs().Name, r, err) 101 } 102 } 103 104 nameServers, searchList, domain := p.GatherDNSSettings() 105 if err := WriteDNSSettings(nameServers, searchList, domain); err != nil { 106 return err 107 } 108 109 return nil 110 } 111 112 func (p *Packet4) String() string { 113 return fmt.Sprintf("IPv4 DHCP Lease IP %s", p.Lease()) 114 } 115 116 // Lease returns the IPNet assigned. 117 func (p *Packet4) Lease() *net.IPNet { 118 netmask := p.P.SubnetMask() 119 if netmask == nil { 120 // If they did not offer a subnet mask, we choose the most 121 // restrictive option. 122 netmask = []byte{255, 255, 255, 255} 123 } 124 125 return &net.IPNet{ 126 IP: p.P.YourIPAddr, 127 Mask: net.IPMask(netmask), 128 } 129 } 130 131 var ( 132 // ErrNoBootFile represents that no pxe boot file was found. 133 ErrNoBootFile = errors.New("no boot file name present in DHCP message") 134 135 // ErrNoServerHostName represents that no pxe boot server was found. 136 ErrNoServerHostName = errors.New("no server host name present in DHCP message") 137 ) 138 139 // Boot returns the boot file assigned. 140 func (p *Packet4) Boot() (*url.URL, error) { 141 // Look for dhcp option presence first, then legacy BootFileName in header. 142 bootFileName := p.P.BootFileNameOption() 143 bootFileName = strings.TrimRight(bootFileName, "\x00") 144 if bootFileName == "" { 145 if len(p.P.BootFileName) == 0 { 146 return nil, ErrNoBootFile 147 } 148 bootFileName = p.P.BootFileName 149 } 150 151 // While the default is tftp, servers may specify HTTP or FTP URIs. 152 u, err := url.Parse(bootFileName) 153 if err != nil { 154 return nil, err 155 } 156 157 if len(u.Scheme) == 0 { 158 // Defaults to tftp is not specified. 159 u.Scheme = "tftp" 160 u.Path = bootFileName 161 if len(p.P.ServerHostName) == 0 { 162 server := p.P.ServerIdentifier() 163 if server != nil { 164 u.Host = server.String() 165 } else if !p.P.ServerIPAddr.Equal(net.IPv4zero) { 166 u.Host = p.P.ServerIPAddr.String() 167 } else { 168 return nil, ErrNoServerHostName 169 } 170 } else { 171 u.Host = p.P.ServerHostName 172 } 173 } 174 return u, nil 175 } 176 177 // ISCSIBoot returns the target address and volume name to boot from if 178 // they were part of the DHCP message. 179 // 180 // Parses the IPv4 DHCP Root Path for iSCSI target and volume as specified by 181 // RFC 4173. 182 func (p *Packet4) ISCSIBoot() (*net.TCPAddr, string, error) { 183 rp := p.P.RootPath() 184 if len(rp) == 0 { 185 return nil, "", fmt.Errorf("no root path in DHCP message") 186 } 187 return parseISCSIURI(rp) 188 }