diff --git a/cmd/dhcp/dhcp.go b/cmd/dhcp/dhcp.go index 68b4c1a..29a60c3 100644 --- a/cmd/dhcp/dhcp.go +++ b/cmd/dhcp/dhcp.go @@ -9,7 +9,6 @@ package main import ( "bytes" - "encoding/binary" "fmt" "io/ioutil" "log" @@ -19,101 +18,109 @@ import ( "syscall" "time" - "golang.org/x/sys/unix" - - "github.com/d2g/dhcp4" - "github.com/d2g/dhcp4client" "github.com/gokrazy/gokrazy/internal/iface" + "github.com/google/gopacket/layers" + "github.com/mdlayher/raw" + "github.com/rtr7/dhcp4" + "golang.org/x/sys/unix" ) -func parseDHCPDuration(b []byte) time.Duration { - return time.Duration(binary.BigEndian.Uint32(b)) * time.Second -} - var ( defaultDst = net.IP([]byte{0, 0, 0, 0}) defaultNetmask = net.IPMask([]byte{0, 0, 0, 0}) - - hardwareAddr net.HardwareAddr ) -func addHostname(p *dhcp4.Packet) { - var utsname unix.Utsname - if err := unix.Uname(&utsname); err != nil { - log.Fatal(err) - } - nnb := utsname.Nodename[:bytes.IndexByte(utsname.Nodename[:], 0)] - p.AddOption(dhcp4.OptionHostName, nnb) +type client struct { + hostname string + hardwareAddr net.HardwareAddr + generateXID func() uint32 + conn net.PacketConn } -func addClientId(p *dhcp4.Packet) { - id := make([]byte, len(hardwareAddr)+1) - id[0] = 1 // hardware type ethernet, https://tools.ietf.org/html/rfc1700 - copy(id[1:], hardwareAddr) - p.AddOption(dhcp4.OptionClientIdentifier, id) +func (c *client) packet(xid uint32, opts []layers.DHCPOption) *layers.DHCPv4 { + return &layers.DHCPv4{ + Operation: layers.DHCPOpRequest, + HardwareType: layers.LinkTypeEthernet, + HardwareLen: uint8(len(layers.EthernetBroadcast)), + HardwareOpts: 0, // TODO: document + Xid: xid, + Secs: 0, // TODO: fill in? + Flags: 0, // TODO: document + ClientHWAddr: c.hardwareAddr, + ServerName: nil, + File: nil, + Options: opts, + } } -// dhcpRequest is a copy of (dhcp4client/Client).Request which -// includes the hostname. -func dhcpRequest(c *dhcp4client.Client) (bool, dhcp4.Packet, error) { - discoveryPacket := c.DiscoverPacket() - addHostname(&discoveryPacket) - addClientId(&discoveryPacket) - discoveryPacket.PadToMinSize() - - if err := c.SendPacket(discoveryPacket); err != nil { - return false, discoveryPacket, err +func (c *client) discover() (*layers.DHCPv4, error) { + discover := c.packet(c.generateXID(), []layers.DHCPOption{ + dhcp4.MessageTypeOpt(layers.DHCPMsgTypeDiscover), + dhcp4.HostnameOpt(c.hostname), + dhcp4.ClientIDOpt(layers.LinkTypeEthernet, c.hardwareAddr), + dhcp4.ParamsRequestOpt( + layers.DHCPOptDNS, + layers.DHCPOptRouter, + layers.DHCPOptSubnetMask), + }) + if err := dhcp4.Write(c.conn, discover); err != nil { + return nil, err } - offerPacket, err := c.GetOffer(&discoveryPacket) - if err != nil { - return false, offerPacket, err + // Look for DHCPOFFER packet (TODO: RFC) + c.conn.SetDeadline(time.Now().Add(5 * time.Second)) + for { + offer, err := dhcp4.Read(c.conn) + if err != nil { + return nil, err + } + if offer == nil { + continue // not a DHCPv4 packet + } + if offer.Xid != discover.Xid { + continue // broadcast reply for different DHCP transaction + } + if !dhcp4.HasMessageType(offer.Options, layers.DHCPMsgTypeOffer) { + continue + } + return offer, nil } - - requestPacket := c.RequestPacket(&offerPacket) - addHostname(&requestPacket) - addClientId(&requestPacket) - requestPacket.PadToMinSize() - - if err := c.SendPacket(requestPacket); err != nil { - return false, requestPacket, err - } - - acknowledgement, err := c.GetAcknowledgement(&requestPacket) - if err != nil { - return false, acknowledgement, err - } - - acknowledgementOptions := acknowledgement.ParseOptions() - if dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK { - return false, acknowledgement, nil - } - - return true, acknowledgement, nil } -// dhcpRenew is a copy of (dhcp4client/Client).Renew which -// includes the hostname. -func dhcpRenew(c *dhcp4client.Client, packet dhcp4.Packet) (bool, dhcp4.Packet, error) { - addHostname(&packet) - addClientId(&packet) - packet.PadToMinSize() - - if err := c.SendPacket(packet); err != nil { - return false, packet, err +func (c *client) request(last *layers.DHCPv4) (*layers.DHCPv4, error) { + // Build a DHCPREQUEST packet: + request := c.packet(last.Xid, append([]layers.DHCPOption{ + dhcp4.MessageTypeOpt(layers.DHCPMsgTypeRequest), + dhcp4.RequestIPOpt(last.YourClientIP), + dhcp4.HostnameOpt(c.hostname), + dhcp4.ClientIDOpt(layers.LinkTypeEthernet, c.hardwareAddr), + dhcp4.ParamsRequestOpt( + layers.DHCPOptDNS, + layers.DHCPOptRouter, + layers.DHCPOptSubnetMask), + }, dhcp4.ServerID(last.Options)...)) + if err := dhcp4.Write(c.conn, request); err != nil { + return nil, err } - acknowledgement, err := c.GetAcknowledgement(&packet) - if err != nil { - return false, acknowledgement, err + c.conn.SetDeadline(time.Now().Add(10 * time.Second)) + for { + // Look for DHCPACK packet (described in RFC2131 4.3.1): + ack, err := dhcp4.Read(c.conn) + if err != nil { + return nil, err + } + if ack == nil { + continue // not a DHCPv4 packet + } + if ack.Xid != request.Xid { + continue // broadcast reply for different DHCP transaction + } + if !dhcp4.HasMessageType(ack.Options, layers.DHCPMsgTypeAck) { + continue + } + return ack, nil } - - acknowledgementOptions := acknowledgement.ParseOptions() - if dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK { - return false, acknowledgement, nil - } - - return true, acknowledgement, nil } func main() { @@ -122,6 +129,12 @@ func main() { // NOTE: cannot gokrazy.WaitForClock() here, since the clock can only be // initialized once the network is up. + var utsname unix.Utsname + if err := unix.Uname(&utsname); err != nil { + log.Fatal(err) + } + hostname := string(utsname.Nodename[:bytes.IndexByte(utsname.Nodename[:], 0)]) + eth0, err := net.InterfaceByName("eth0") if err != nil { log.Fatal(err) @@ -148,73 +161,67 @@ func main() { time.Sleep(1 * time.Second) } - hardwareAddr = eth0.HardwareAddr - - pktsock, err := dhcp4client.NewPacketSock(eth0.Index) - if err != nil { - log.Fatal(err) - } - dhcp, err := dhcp4client.New( - dhcp4client.HardwareAddr(eth0.HardwareAddr), - dhcp4client.Timeout(5*time.Second), - dhcp4client.Broadcast(false), - dhcp4client.Connection(pktsock), - ) + conn, err := raw.ListenPacket(eth0, syscall.ETH_P_IP, &raw.Config{ + LinuxSockDGRAM: true, + }) if err != nil { log.Fatal(err) } - ok, ack, err := dhcpRequest(dhcp) + c := &client{ + hostname: hostname, + hardwareAddr: eth0.HardwareAddr, + generateXID: dhcp4.XIDGenerator(eth0.HardwareAddr), + conn: conn, + } + offer, err := c.discover() + if err != nil { + log.Fatal(err) + } + last := offer for { + last, err = c.request(last) if err != nil { log.Fatal(err) } - if !ok { - log.Fatal("received DHCPNAK") - } - opts := ack.ParseOptions() - // DHCPACK (described in RFC2131 4.3.1) - // - yiaddr: IP address assigned to client + lease := dhcp4.LeaseFromACK(last) + + // Log the received DHCPACK packet: details := []string{ - fmt.Sprintf("IP %v", ack.YIAddr()), + fmt.Sprintf("IP %v", lease.IP), } - - if b, ok := opts[dhcp4.OptionSubnetMask]; ok { + if len(lease.Netmask) > 0 { ipnet := net.IPNet{ - IP: ack.YIAddr(), - Mask: net.IPMask(b), + IP: lease.IP, + Mask: lease.Netmask, } details[0] = fmt.Sprintf("IP %v", ipnet.String()) } - - if b, ok := opts[dhcp4.OptionBroadcastAddress]; ok { - details = append(details, fmt.Sprintf("broadcast %v", net.IP(b))) + if len(lease.Router) > 0 { + details = append(details, fmt.Sprintf("router %v", lease.Router)) } - - if b, ok := opts[dhcp4.OptionRouter]; ok { - details = append(details, fmt.Sprintf("router %v", net.IP(b))) + if len(lease.DNS) > 0 { + details = append(details, fmt.Sprintf("DNS %v", lease.DNS)) } - - if b, ok := opts[dhcp4.OptionDomainNameServer]; ok { - details = append(details, fmt.Sprintf("DNS %v", net.IP(b))) + if len(lease.Broadcast) > 0 { + details = append(details, fmt.Sprintf("broadcast %v", lease.Broadcast)) } log.Printf("DHCPACK: %v", strings.Join(details, ", ")) - if err := cs.SetAddress(ack.YIAddr()); err != nil { + // Apply the received settings: + if err := cs.SetAddress(lease.IP); err != nil { log.Fatal(err) } - - if b, ok := opts[dhcp4.OptionSubnetMask]; ok { - if err := cs.SetNetmask(net.IPMask(b)); err != nil { - log.Fatalf("setNetmask(%v): %v", net.IPMask(b), err) + if len(lease.Netmask) > 0 { + if err := cs.SetNetmask(lease.Netmask); err != nil { + log.Fatalf("setNetmask(%v): %v", lease.Netmask, err) } } - - if b, ok := opts[dhcp4.OptionBroadcastAddress]; ok { - if err := cs.SetBroadcast(net.IP(b)); err != nil { - log.Fatalf("setBroadcast(%v): %v", net.IP(b), err) + if b := lease.Broadcast; len(b) > 0 { + if err := cs.SetBroadcast(b); err != nil { + log.Fatalf("setBroadcast(%v): %v", b, err) } } @@ -222,22 +229,22 @@ func main() { log.Fatal(err) } - if b, ok := opts[dhcp4.OptionRouter]; ok { - if errno := cs.AddRoute(defaultDst, net.IP(b), defaultNetmask); errno != 0 { + if r := lease.Router; len(r) > 0 { + if errno := cs.AddRoute(defaultDst, r, defaultNetmask); errno != 0 { if errno == syscall.EEXIST { - if errno := cs.DelRoute(defaultDst, net.IP(b), defaultNetmask); errno != 0 { - log.Printf("delRoute(%v): %v", net.IP(b), errno) + if errno := cs.DelRoute(defaultDst, r, defaultNetmask); errno != 0 { + log.Printf("delRoute(%v): %v", r, errno) } - if errno := cs.AddRoute(defaultDst, net.IP(b), defaultNetmask); errno != 0 { - log.Fatalf("addRoute(%v): %v", net.IP(b), errno) + if errno := cs.AddRoute(defaultDst, r, defaultNetmask); errno != 0 { + log.Fatalf("addRoute(%v): %v", r, errno) } } else { - log.Fatalf("addRoute(%v): %v", net.IP(b), errno) + log.Fatalf("addRoute(%v): %v", r, errno) } } } - if b, ok := opts[dhcp4.OptionDomainNameServer]; ok { + if len(lease.DNS) > 0 { resolvConf := "/etc/resolv.conf" if dest, err := os.Readlink("/etc/resolv.conf"); err == nil && dest == "/tmp/resolv.conf" { resolvConf = "/tmp/resolv.conf" @@ -247,11 +254,13 @@ func main() { log.Fatalf("resolv.conf: %v", err) } var lines []string - if domain, ok := opts[dhcp4.OptionDomainName]; ok { - lines = append(lines, fmt.Sprintf("domain %s", string(domain))) - lines = append(lines, fmt.Sprintf("search %s", string(domain))) + if domain := lease.Domain; domain != "" { + lines = append(lines, fmt.Sprintf("domain %s", domain)) + lines = append(lines, fmt.Sprintf("search %s", domain)) + } + for _, ns := range lease.DNS { + lines = append(lines, fmt.Sprintf("nameserver %v", ns)) } - lines = append(lines, fmt.Sprintf("nameserver %v", net.IP(b))) if err := ioutil.WriteFile(resolvConf, []byte(strings.Join(lines, "\n")+"\n"), 0644); err != nil { log.Fatalf("resolv.conf: %v", err) } @@ -263,19 +272,6 @@ func main() { log.Printf("send SIGHUP to init: %v", err) } - leaseTime := 10 * time.Minute // seems sensible as a fallback - if b, ok := opts[dhcp4.OptionIPAddressLeaseTime]; ok && len(b) == 4 { - leaseTime = parseDHCPDuration(b) - } - - // As per RFC 2131 section 4.4.5: - // renewal time defaults to 50% of the lease time - renewalTime := time.Duration(float64(leaseTime) * 0.5) - if b, ok := opts[dhcp4.OptionRenewalTimeValue]; ok && len(b) == 4 { - renewalTime = parseDHCPDuration(b) - } - - time.Sleep(renewalTime) - ok, ack, err = dhcpRenew(dhcp, dhcp.RenewalRequestPacket(&ack)) + time.Sleep(lease.RenewalTime) } }