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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@ -19,101 +18,109 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/d2g/dhcp4"
|
|
||||||
"github.com/d2g/dhcp4client"
|
|
||||||
"github.com/gokrazy/gokrazy/internal/iface"
|
"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 (
|
var (
|
||||||
defaultDst = net.IP([]byte{0, 0, 0, 0})
|
defaultDst = net.IP([]byte{0, 0, 0, 0})
|
||||||
defaultNetmask = net.IPMask([]byte{0, 0, 0, 0})
|
defaultNetmask = net.IPMask([]byte{0, 0, 0, 0})
|
||||||
|
|
||||||
hardwareAddr net.HardwareAddr
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func addHostname(p *dhcp4.Packet) {
|
type client struct {
|
||||||
var utsname unix.Utsname
|
hostname string
|
||||||
if err := unix.Uname(&utsname); err != nil {
|
hardwareAddr net.HardwareAddr
|
||||||
log.Fatal(err)
|
generateXID func() uint32
|
||||||
}
|
conn net.PacketConn
|
||||||
nnb := utsname.Nodename[:bytes.IndexByte(utsname.Nodename[:], 0)]
|
|
||||||
p.AddOption(dhcp4.OptionHostName, nnb)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addClientId(p *dhcp4.Packet) {
|
func (c *client) packet(xid uint32, opts []layers.DHCPOption) *layers.DHCPv4 {
|
||||||
id := make([]byte, len(hardwareAddr)+1)
|
return &layers.DHCPv4{
|
||||||
id[0] = 1 // hardware type ethernet, https://tools.ietf.org/html/rfc1700
|
Operation: layers.DHCPOpRequest,
|
||||||
copy(id[1:], hardwareAddr)
|
HardwareType: layers.LinkTypeEthernet,
|
||||||
p.AddOption(dhcp4.OptionClientIdentifier, id)
|
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
|
func (c *client) discover() (*layers.DHCPv4, error) {
|
||||||
// includes the hostname.
|
discover := c.packet(c.generateXID(), []layers.DHCPOption{
|
||||||
func dhcpRequest(c *dhcp4client.Client) (bool, dhcp4.Packet, error) {
|
dhcp4.MessageTypeOpt(layers.DHCPMsgTypeDiscover),
|
||||||
discoveryPacket := c.DiscoverPacket()
|
dhcp4.HostnameOpt(c.hostname),
|
||||||
addHostname(&discoveryPacket)
|
dhcp4.ClientIDOpt(layers.LinkTypeEthernet, c.hardwareAddr),
|
||||||
addClientId(&discoveryPacket)
|
dhcp4.ParamsRequestOpt(
|
||||||
discoveryPacket.PadToMinSize()
|
layers.DHCPOptDNS,
|
||||||
|
layers.DHCPOptRouter,
|
||||||
if err := c.SendPacket(discoveryPacket); err != nil {
|
layers.DHCPOptSubnetMask),
|
||||||
return false, discoveryPacket, err
|
})
|
||||||
|
if err := dhcp4.Write(c.conn, discover); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
offerPacket, err := c.GetOffer(&discoveryPacket)
|
// Look for DHCPOFFER packet (TODO: RFC)
|
||||||
if err != nil {
|
c.conn.SetDeadline(time.Now().Add(5 * time.Second))
|
||||||
return false, offerPacket, err
|
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
|
func (c *client) request(last *layers.DHCPv4) (*layers.DHCPv4, error) {
|
||||||
// includes the hostname.
|
// Build a DHCPREQUEST packet:
|
||||||
func dhcpRenew(c *dhcp4client.Client, packet dhcp4.Packet) (bool, dhcp4.Packet, error) {
|
request := c.packet(last.Xid, append([]layers.DHCPOption{
|
||||||
addHostname(&packet)
|
dhcp4.MessageTypeOpt(layers.DHCPMsgTypeRequest),
|
||||||
addClientId(&packet)
|
dhcp4.RequestIPOpt(last.YourClientIP),
|
||||||
packet.PadToMinSize()
|
dhcp4.HostnameOpt(c.hostname),
|
||||||
|
dhcp4.ClientIDOpt(layers.LinkTypeEthernet, c.hardwareAddr),
|
||||||
if err := c.SendPacket(packet); err != nil {
|
dhcp4.ParamsRequestOpt(
|
||||||
return false, packet, err
|
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)
|
c.conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||||
if err != nil {
|
for {
|
||||||
return false, acknowledgement, err
|
// 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() {
|
func main() {
|
||||||
@ -122,6 +129,12 @@ func main() {
|
|||||||
// NOTE: cannot gokrazy.WaitForClock() here, since the clock can only be
|
// NOTE: cannot gokrazy.WaitForClock() here, since the clock can only be
|
||||||
// initialized once the network is up.
|
// 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")
|
eth0, err := net.InterfaceByName("eth0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -148,73 +161,67 @@ func main() {
|
|||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
hardwareAddr = eth0.HardwareAddr
|
conn, err := raw.ListenPacket(eth0, syscall.ETH_P_IP, &raw.Config{
|
||||||
|
LinuxSockDGRAM: true,
|
||||||
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),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
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 {
|
for {
|
||||||
|
last, err = c.request(last)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if !ok {
|
|
||||||
log.Fatal("received DHCPNAK")
|
|
||||||
}
|
|
||||||
opts := ack.ParseOptions()
|
|
||||||
|
|
||||||
// DHCPACK (described in RFC2131 4.3.1)
|
lease := dhcp4.LeaseFromACK(last)
|
||||||
// - yiaddr: IP address assigned to client
|
|
||||||
|
// Log the received DHCPACK packet:
|
||||||
details := []string{
|
details := []string{
|
||||||
fmt.Sprintf("IP %v", ack.YIAddr()),
|
fmt.Sprintf("IP %v", lease.IP),
|
||||||
}
|
}
|
||||||
|
if len(lease.Netmask) > 0 {
|
||||||
if b, ok := opts[dhcp4.OptionSubnetMask]; ok {
|
|
||||||
ipnet := net.IPNet{
|
ipnet := net.IPNet{
|
||||||
IP: ack.YIAddr(),
|
IP: lease.IP,
|
||||||
Mask: net.IPMask(b),
|
Mask: lease.Netmask,
|
||||||
}
|
}
|
||||||
details[0] = fmt.Sprintf("IP %v", ipnet.String())
|
details[0] = fmt.Sprintf("IP %v", ipnet.String())
|
||||||
}
|
}
|
||||||
|
if len(lease.Router) > 0 {
|
||||||
if b, ok := opts[dhcp4.OptionBroadcastAddress]; ok {
|
details = append(details, fmt.Sprintf("router %v", lease.Router))
|
||||||
details = append(details, fmt.Sprintf("broadcast %v", net.IP(b)))
|
|
||||||
}
|
}
|
||||||
|
if len(lease.DNS) > 0 {
|
||||||
if b, ok := opts[dhcp4.OptionRouter]; ok {
|
details = append(details, fmt.Sprintf("DNS %v", lease.DNS))
|
||||||
details = append(details, fmt.Sprintf("router %v", net.IP(b)))
|
|
||||||
}
|
}
|
||||||
|
if len(lease.Broadcast) > 0 {
|
||||||
if b, ok := opts[dhcp4.OptionDomainNameServer]; ok {
|
details = append(details, fmt.Sprintf("broadcast %v", lease.Broadcast))
|
||||||
details = append(details, fmt.Sprintf("DNS %v", net.IP(b)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("DHCPACK: %v", strings.Join(details, ", "))
|
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)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if len(lease.Netmask) > 0 {
|
||||||
if b, ok := opts[dhcp4.OptionSubnetMask]; ok {
|
if err := cs.SetNetmask(lease.Netmask); err != nil {
|
||||||
if err := cs.SetNetmask(net.IPMask(b)); err != nil {
|
log.Fatalf("setNetmask(%v): %v", lease.Netmask, err)
|
||||||
log.Fatalf("setNetmask(%v): %v", net.IPMask(b), err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if b := lease.Broadcast; len(b) > 0 {
|
||||||
if b, ok := opts[dhcp4.OptionBroadcastAddress]; ok {
|
if err := cs.SetBroadcast(b); err != nil {
|
||||||
if err := cs.SetBroadcast(net.IP(b)); err != nil {
|
log.Fatalf("setBroadcast(%v): %v", b, err)
|
||||||
log.Fatalf("setBroadcast(%v): %v", net.IP(b), err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,22 +229,22 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if b, ok := opts[dhcp4.OptionRouter]; ok {
|
if r := lease.Router; len(r) > 0 {
|
||||||
if errno := cs.AddRoute(defaultDst, net.IP(b), defaultNetmask); errno != 0 {
|
if errno := cs.AddRoute(defaultDst, r, defaultNetmask); errno != 0 {
|
||||||
if errno == syscall.EEXIST {
|
if errno == syscall.EEXIST {
|
||||||
if errno := cs.DelRoute(defaultDst, net.IP(b), defaultNetmask); errno != 0 {
|
if errno := cs.DelRoute(defaultDst, r, defaultNetmask); errno != 0 {
|
||||||
log.Printf("delRoute(%v): %v", net.IP(b), errno)
|
log.Printf("delRoute(%v): %v", r, errno)
|
||||||
}
|
}
|
||||||
if errno := cs.AddRoute(defaultDst, net.IP(b), defaultNetmask); errno != 0 {
|
if errno := cs.AddRoute(defaultDst, r, defaultNetmask); errno != 0 {
|
||||||
log.Fatalf("addRoute(%v): %v", net.IP(b), errno)
|
log.Fatalf("addRoute(%v): %v", r, errno)
|
||||||
}
|
}
|
||||||
} else {
|
} 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"
|
resolvConf := "/etc/resolv.conf"
|
||||||
if dest, err := os.Readlink("/etc/resolv.conf"); err == nil && dest == "/tmp/resolv.conf" {
|
if dest, err := os.Readlink("/etc/resolv.conf"); err == nil && dest == "/tmp/resolv.conf" {
|
||||||
resolvConf = "/tmp/resolv.conf"
|
resolvConf = "/tmp/resolv.conf"
|
||||||
@ -247,11 +254,13 @@ func main() {
|
|||||||
log.Fatalf("resolv.conf: %v", err)
|
log.Fatalf("resolv.conf: %v", err)
|
||||||
}
|
}
|
||||||
var lines []string
|
var lines []string
|
||||||
if domain, ok := opts[dhcp4.OptionDomainName]; ok {
|
if domain := lease.Domain; domain != "" {
|
||||||
lines = append(lines, fmt.Sprintf("domain %s", string(domain)))
|
lines = append(lines, fmt.Sprintf("domain %s", domain))
|
||||||
lines = append(lines, fmt.Sprintf("search %s", string(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 {
|
if err := ioutil.WriteFile(resolvConf, []byte(strings.Join(lines, "\n")+"\n"), 0644); err != nil {
|
||||||
log.Fatalf("resolv.conf: %v", err)
|
log.Fatalf("resolv.conf: %v", err)
|
||||||
}
|
}
|
||||||
@ -263,19 +272,6 @@ func main() {
|
|||||||
log.Printf("send SIGHUP to init: %v", err)
|
log.Printf("send SIGHUP to init: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
leaseTime := 10 * time.Minute // seems sensible as a fallback
|
time.Sleep(lease.RenewalTime)
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user