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 }