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