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  }