github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+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  	// ErrNoServerHostName represents that no pxe boot server was found.
   142  	ErrNoServerHostName = errors.New("no server host name present in DHCP message")
   143  )
   144  
   145  // Boot returns the boot file assigned.
   146  func (p *Packet4) Boot() (*url.URL, error) {
   147  	// Look for dhcp option presence first, then legacy BootFileName in header.
   148  	bootFileName := p.P.BootFileNameOption()
   149  	bootFileName = strings.TrimRight(bootFileName, "\x00")
   150  	if bootFileName == "" {
   151  		if len(p.P.BootFileName) == 0 {
   152  			return nil, ErrNoBootFile
   153  		}
   154  		bootFileName = p.P.BootFileName
   155  	}
   156  
   157  	// While the default is tftp, servers may specify HTTP or FTP URIs.
   158  	u, err := url.Parse(bootFileName)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	if len(u.Scheme) == 0 {
   164  		// Defaults to tftp is not specified.
   165  		u.Scheme = "tftp"
   166  		u.Path = bootFileName
   167  		if len(p.P.ServerHostName) == 0 {
   168  			server := p.P.ServerIdentifier()
   169  			if server != nil {
   170  				u.Host = server.String()
   171  			} else if !p.P.ServerIPAddr.Equal(net.IPv4zero) {
   172  				u.Host = p.P.ServerIPAddr.String()
   173  			} else {
   174  				return nil, ErrNoServerHostName
   175  			}
   176  		} else {
   177  			u.Host = p.P.ServerHostName
   178  		}
   179  	}
   180  	return u, nil
   181  }
   182  
   183  // ISCSIBoot returns the target address and volume name to boot from if
   184  // they were part of the DHCP message.
   185  //
   186  // Parses the IPv4 DHCP Root Path for iSCSI target and volume as specified by
   187  // RFC 4173.
   188  func (p *Packet4) ISCSIBoot() (*net.TCPAddr, string, error) {
   189  	rp := p.P.RootPath()
   190  	if len(rp) == 0 {
   191  		return nil, "", fmt.Errorf("no root path in DHCP message")
   192  	}
   193  	return parseISCSIURI(rp)
   194  }
   195  
   196  // Response returns the DHCP response
   197  func (p *Packet4) Response() interface{} {
   198  	return p.P
   199  }