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