github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/cmds/dhclient/dhclient.go (about)

     1  // Copyright 2017 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  // dhclient sets up DHCP.
     6  //
     7  // Synopsis:
     8  //     dhclient [OPTIONS...]
     9  //
    10  // Options:
    11  //     -timeout:  lease timeout in seconds
    12  //     -renewals: number of DHCP renewals before exiting
    13  //     -verbose:  verbose output
    14  package main
    15  
    16  import (
    17  	"crypto/rand"
    18  	"flag"
    19  	"fmt"
    20  	"log"
    21  	"regexp"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/u-root/dhcp4"
    26  	"github.com/u-root/dhcp4/dhcp4client"
    27  	"github.com/u-root/u-root/pkg/dhclient"
    28  	"github.com/u-root/u-root/pkg/dhcp6client"
    29  	"github.com/vishvananda/netlink"
    30  )
    31  
    32  const (
    33  	// slop is the slop in our lease time.
    34  	slop          = 10 * time.Second
    35  	linkUpAttempt = 30 * time.Second
    36  )
    37  
    38  var (
    39  	ifName         = "^e.*"
    40  	leasetimeout   = flag.Int("timeout", 15, "Lease timeout in seconds")
    41  	retry          = flag.Int("retry", 5, "Max number of attempts for DHCP clients to send requests. -1 means infinity")
    42  	renewals       = flag.Int("renewals", 0, "Number of DHCP renewals before exiting. -1 means infinity")
    43  	renewalTimeout = flag.Int("renewal timeout", 3600, "How long to wait before renewing in seconds")
    44  	verbose        = flag.Bool("verbose", false, "Verbose output")
    45  	ipv4           = flag.Bool("ipv4", true, "use IPV4")
    46  	ipv6           = flag.Bool("ipv6", true, "use IPV6")
    47  	test           = flag.Bool("test", false, "Test mode")
    48  	debug          = func(string, ...interface{}) {}
    49  )
    50  
    51  func ifup(ifname string) (netlink.Link, error) {
    52  	debug("Try bringing up %v", ifname)
    53  	start := time.Now()
    54  	for time.Since(start) < linkUpAttempt {
    55  		// Note that it may seem odd to keep trying the
    56  		// LinkByName operation, by consider that a hotplug
    57  		// device such as USB ethernet can just vanish.
    58  		iface, err := netlink.LinkByName(ifname)
    59  		debug("LinkByName(%v) returns (%v, %v)", ifname, iface, err)
    60  		if err != nil {
    61  			return nil, fmt.Errorf("cannot get interface by name %v: %v", ifname, err)
    62  		}
    63  
    64  		if iface.Attrs().OperState == netlink.OperUp {
    65  			debug("Link %v is up", ifname)
    66  			return iface, nil
    67  		}
    68  
    69  		if err := netlink.LinkSetUp(iface); err != nil {
    70  			return nil, fmt.Errorf("%v: %v can't make it up: %v", ifname, iface, err)
    71  		}
    72  		time.Sleep(1 * time.Second)
    73  	}
    74  	return nil, fmt.Errorf("Link %v still down after %d seconds", ifname, linkUpAttempt)
    75  }
    76  
    77  func dhclient4(iface netlink.Link, timeout time.Duration, retry int, numRenewals int) error {
    78  	client, err := dhcp4client.New(iface,
    79  		dhcp4client.WithTimeout(timeout),
    80  		dhcp4client.WithRetry(retry))
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	for i := 0; numRenewals < 0 || i <= numRenewals; i++ {
    86  		var packet *dhcp4.Packet
    87  		var err error
    88  		if i == 0 {
    89  			packet, err = client.Request()
    90  		} else {
    91  			time.Sleep(time.Duration(*renewalTimeout) * time.Second)
    92  			packet, err = client.Renew(packet)
    93  		}
    94  		if err != nil {
    95  			return err
    96  		}
    97  
    98  		if err := dhclient.Configure4(iface, packet); err != nil {
    99  			return err
   100  		}
   101  	}
   102  	return nil
   103  }
   104  
   105  func dhclient6(iface netlink.Link, timeout time.Duration, retry int, numRenewals int) error {
   106  	client, err := dhcp6client.New(iface,
   107  		dhcp6client.WithTimeout(timeout),
   108  		dhcp6client.WithRetry(retry))
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	for i := 0; numRenewals < 0 || i <= numRenewals; i++ {
   114  		if i != 0 {
   115  			time.Sleep(time.Duration(*renewalTimeout) * time.Second)
   116  		}
   117  		iana, packet, err := client.RapidSolicit()
   118  		if err != nil {
   119  			return err
   120  		}
   121  
   122  		if err := dhclient.Configure6(iface, packet, iana); err != nil {
   123  			return err
   124  		}
   125  	}
   126  	return nil
   127  }
   128  
   129  func main() {
   130  	flag.Parse()
   131  	if *verbose {
   132  		debug = log.Printf
   133  	}
   134  
   135  	// if we boot quickly enough, the random number generator
   136  	// may not be ready, and the dhcp package panics in that case.
   137  	// Worse, /dev/urandom, which the Go package falls back to,
   138  	// might not be there. Still worse, the Go package is "sticky"
   139  	// in that once it decides to use /dev/urandom, it won't go back,
   140  	// even if the system call would subsequently work.
   141  	// You're screwed. Exit.
   142  	// Wouldn't it be nice if we could just do the blocking system
   143  	// call? But that comes with its own giant set of headaches.
   144  	// Maybe we'll end up in a loop, sleeping, and just running
   145  	// ourselves.
   146  	if n, err := rand.Read([]byte{0}); err != nil || n != 1 {
   147  		log.Fatalf("We're sorry, the random number generator is not up. Please file a ticket")
   148  	}
   149  
   150  	if len(flag.Args()) > 1 {
   151  		log.Fatalf("only one re")
   152  	}
   153  
   154  	if len(flag.Args()) > 0 {
   155  		ifName = flag.Args()[0]
   156  	}
   157  
   158  	ifRE := regexp.MustCompilePOSIX(ifName)
   159  
   160  	ifnames, err := netlink.LinkList()
   161  	if err != nil {
   162  		log.Fatalf("Can't get list of link names: %v", err)
   163  	}
   164  
   165  	timeout := time.Duration(*leasetimeout) * time.Second
   166  	// if timeout is < slop, it's too short.
   167  	if timeout < slop {
   168  		timeout = 2 * slop
   169  		log.Printf("increased lease timeout to %s", timeout)
   170  	}
   171  
   172  	var wg sync.WaitGroup
   173  	done := make(chan error)
   174  	for _, i := range ifnames {
   175  		if !ifRE.MatchString(i.Attrs().Name) {
   176  			continue
   177  		}
   178  		wg.Add(1)
   179  		go func(ifname string) {
   180  			defer wg.Done()
   181  			iface, err := dhclient.IfUp(ifname)
   182  			if err != nil {
   183  				done <- err
   184  				return
   185  			}
   186  			if *ipv4 {
   187  				wg.Add(1)
   188  				go func() {
   189  					defer wg.Done()
   190  					done <- dhclient4(iface, timeout, *retry, *renewals)
   191  				}()
   192  			}
   193  			if *ipv6 {
   194  				wg.Add(1)
   195  				go func() {
   196  					defer wg.Done()
   197  					done <- dhclient6(iface, timeout, *retry, *renewals)
   198  				}()
   199  			}
   200  			debug("Done dhclient for %v", ifname)
   201  		}(i.Attrs().Name)
   202  	}
   203  
   204  	go func() {
   205  		wg.Wait()
   206  		close(done)
   207  	}()
   208  	// Wait for all goroutines to finish.
   209  	var nif int
   210  	for err := range done {
   211  		debug("err from done %v", err)
   212  		if err != nil {
   213  			log.Print(err)
   214  		}
   215  		nif++
   216  	}
   217  
   218  	if nif == 0 {
   219  		log.Fatalf("No interfaces match %v\n", ifName)
   220  	}
   221  	fmt.Printf("%d dhclient attempts were sent", nif)
   222  }