dhcp: switch to github.com/rtr7/dhcp4
All existing DHCPv4 packages I looked at were unappealing for one reason or another, so we’re now using a little helper to glue github.com/google/gopacket and github.com/mdlayher/raw together, which suffices for our use-case and gives us more control.
This commit is contained in:
parent
a6ce446055
commit
14287515bc
284
cmd/dhcp/dhcp.go
284
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)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user