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