github.com/system-transparency/u-root@v6.0.1-0.20190919065413-ed07a650de4c+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/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 // Configure configures interface using this packet. 55 func (p *Packet4) Configure() error { 56 l := p.Lease() 57 if l == nil { 58 return fmt.Errorf("packet has no IP lease") 59 } 60 61 // Add the address to the iface. 62 dst := &netlink.Addr{ 63 IPNet: l, 64 } 65 if err := netlink.AddrReplace(p.iface, dst); err != nil { 66 return fmt.Errorf("add/replace %s to %v: %v", dst, p.iface, err) 67 } 68 69 // RFC 3442 notes that if classless static routes are available, they 70 // have priority. You have to ignore the Route Option. 71 if routes := p.P.ClasslessStaticRoute(); routes != nil { 72 for _, route := range routes { 73 r := &netlink.Route{ 74 LinkIndex: p.iface.Attrs().Index, 75 Dst: route.Dest, 76 Gw: route.Router, 77 } 78 // If no gateway is specified, the destination must be link-local. 79 if r.Gw == nil || r.Gw.Equal(net.IPv4zero) { 80 r.Scope = netlink.SCOPE_LINK 81 } 82 83 if err := netlink.RouteReplace(r); err != nil { 84 return fmt.Errorf("%s: add %s: %v", p.iface.Attrs().Name, r, err) 85 } 86 } 87 } else if gw := p.P.Router(); gw != nil && len(gw) > 0 { 88 r := &netlink.Route{ 89 LinkIndex: p.iface.Attrs().Index, 90 Gw: gw[0], 91 } 92 93 if err := netlink.RouteReplace(r); err != nil { 94 return fmt.Errorf("%s: add %s: %v", p.iface.Attrs().Name, r, err) 95 } 96 } 97 98 nameServers, searchList, domain := p.GatherDNSSettings() 99 if err := WriteDNSSettings(nameServers, searchList, domain); err != nil { 100 return err 101 } 102 103 return nil 104 } 105 106 func (p *Packet4) String() string { 107 return fmt.Sprintf("IPv4 DHCP Lease IP %s", p.Lease()) 108 } 109 110 // Lease returns the IPNet assigned. 111 func (p *Packet4) Lease() *net.IPNet { 112 netmask := p.P.SubnetMask() 113 if netmask == nil { 114 // If they did not offer a subnet mask, we choose the most 115 // restrictive option. 116 netmask = []byte{255, 255, 255, 255} 117 } 118 119 return &net.IPNet{ 120 IP: p.P.YourIPAddr, 121 Mask: net.IPMask(netmask), 122 } 123 } 124 125 var ( 126 // ErrNoBootFile represents that no pxe boot file was found. 127 ErrNoBootFile = errors.New("no boot file name present in DHCP message") 128 129 // ErrNoServerHostName represents that no pxe boot server was found. 130 ErrNoServerHostName = errors.New("no server host name present in DHCP message") 131 ) 132 133 // Boot returns the boot file assigned. 134 func (p *Packet4) Boot() (*url.URL, error) { 135 // Look for dhcp option presence first, then legacy BootFileName in header. 136 bootFileName := p.P.BootFileNameOption() 137 bootFileName = strings.TrimRight(bootFileName, "\x00") 138 if bootFileName == "" { 139 if len(p.P.BootFileName) == 0 { 140 return nil, ErrNoBootFile 141 } 142 bootFileName = p.P.BootFileName 143 } 144 145 // While the default is tftp, servers may specify HTTP or FTP URIs. 146 u, err := url.Parse(bootFileName) 147 if err != nil { 148 return nil, err 149 } 150 151 if len(u.Scheme) == 0 { 152 // Defaults to tftp is not specified. 153 u.Scheme = "tftp" 154 u.Path = bootFileName 155 if len(p.P.ServerHostName) == 0 { 156 server := p.P.ServerIdentifier() 157 if server != nil { 158 u.Host = server.String() 159 } else if !p.P.ServerIPAddr.Equal(net.IPv4zero) { 160 u.Host = p.P.ServerIPAddr.String() 161 } else { 162 return nil, ErrNoServerHostName 163 } 164 } else { 165 u.Host = p.P.ServerHostName 166 } 167 } 168 return u, nil 169 } 170 171 // ISCSIBoot returns the target address and volume name to boot from if 172 // they were part of the DHCP message. 173 // 174 // Parses the IPv4 DHCP Root Path for iSCSI target and volume as specified by 175 // RFC 4173. 176 func (p *Packet4) ISCSIBoot() (*net.TCPAddr, string, error) { 177 rp := p.P.RootPath() 178 if len(rp) == 0 { 179 return nil, "", fmt.Errorf("no root path in DHCP message") 180 } 181 return parseISCSIURI(rp) 182 }