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  }