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  }