diff --git a/Gopkg.lock b/Gopkg.lock index 1213c8ec6c..8b353f7277 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -202,16 +202,17 @@ [[projects]] branch = "master" - digest = "1:6e0859e017e676eab41ee8461a9d114503699347fa0700141a562a164a6e3908" + digest = "1:f06cd40b99d2fcb4f1cbf05e10b309a6ce4acc50e9c392e94a8b2a9b76eeabb4" name = "github.com/u-root/dhcp4" packages = [ ".", "dhcp4client", "dhcp4opts", + "dhcp4server", "internal/buffer", ] pruneopts = "NUT" - revision = "f78158b7c380d2ae515105471716468098373c72" + revision = "8fce2c0a78c8e92233c390983acefb152d6c233f" [[projects]] branch = "master" @@ -336,6 +337,7 @@ "github.com/u-root/dhcp4", "github.com/u-root/dhcp4/dhcp4client", "github.com/u-root/dhcp4/dhcp4opts", + "github.com/u-root/dhcp4/dhcp4server", "github.com/vishvananda/netlink", "golang.org/x/crypto/ed25519", "golang.org/x/crypto/md4", diff --git a/vendor/github.com/u-root/dhcp4/dhcp4opts/options.go b/vendor/github.com/u-root/dhcp4/dhcp4opts/options.go index 286162092f..5a9ed24145 100644 --- a/vendor/github.com/u-root/dhcp4/dhcp4opts/options.go +++ b/vendor/github.com/u-root/dhcp4/dhcp4opts/options.go @@ -8,6 +8,8 @@ package dhcp4opts import ( + "time" + "github.com/u-root/dhcp4" ) @@ -337,3 +339,15 @@ func GetMaximumDHCPMessageSize(o dhcp4.Options) (uint16, error) { var u Uint16 return uint16(u), (&u).UnmarshalBinary(v) } + +// GetIPAddressLeaseTime returns the proposed lease time. +// +// The IP address lease time message is defined by RFC 2132, Section 9.2. +func GetIPAddressLeaseTime(o dhcp4.Options) (time.Duration, error) { + v := o.Get(dhcp4.OptionIPAddressLeaseTime) + if v == nil { + return 0, dhcp4.ErrOptionNotPresent + } + var u Uint32 + return time.Duration(u) * time.Second, (&u).UnmarshalBinary(v) +} diff --git a/vendor/github.com/u-root/dhcp4/dhcp4opts/types.go b/vendor/github.com/u-root/dhcp4/dhcp4opts/types.go index 7a0faeac6c..5159647fc3 100644 --- a/vendor/github.com/u-root/dhcp4/dhcp4opts/types.go +++ b/vendor/github.com/u-root/dhcp4/dhcp4opts/types.go @@ -206,3 +206,24 @@ func (u *Uint16) UnmarshalBinary(p []byte) error { *u = Uint16(b.Read16()) return nil } + +// Uint32 implements encoding.BinaryMarshaler and encapsulates binary encoding +// and decoding methods of uint32s as defined by RFC 2132 Section 9.2. +type Uint32 uint32 + +// MarshalBinary writes the uint32 to binary. +func (u Uint32) MarshalBinary() ([]byte, error) { + b := buffer.New(nil) + b.Write32(uint32(u)) + return b.Data(), nil +} + +// UnmarshalBinary reads the uint32 from binary. +func (u *Uint32) UnmarshalBinary(p []byte) error { + b := buffer.New(p) + if b.Len() < 2 { + return io.ErrUnexpectedEOF + } + *u = Uint32(b.Read32()) + return nil +} diff --git a/vendor/github.com/u-root/dhcp4/dhcp4server/ipalloc.go b/vendor/github.com/u-root/dhcp4/dhcp4server/ipalloc.go new file mode 100644 index 0000000000..39a3350947 --- /dev/null +++ b/vendor/github.com/u-root/dhcp4/dhcp4server/ipalloc.go @@ -0,0 +1,78 @@ +// Copyright 2018 the u-root Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dhcp4server + +import ( + "encoding/binary" + "fmt" + "net" +) + +func nextIP(ip net.IP) { + for i := len(ip) - 1; i >= 0; i-- { + ip[i]++ + if ip[i] != 0 { + break + } + } +} + +type ipAllocator struct { + // subnet is the range of IP addresses that can be allocated by this + // DHCP server. + subnet *net.IPNet + + // allocated is the set of IP addresses currently allocated to a + // client. + allocated map[uint32]struct{} +} + +func newIPAllocator(subnet *net.IPNet) *ipAllocator { + return &ipAllocator{ + subnet: subnet, + allocated: make(map[uint32]struct{}), + } +} + +func ipToUint32(ip net.IP) uint32 { + return binary.LittleEndian.Uint32(ip.To4()) +} + +func (ia *ipAllocator) usable(ip net.IP) bool { + _, ok := ia.allocated[ipToUint32(ip)] + return !ok +} + +func (ia *ipAllocator) grab(ip net.IP) bool { + if !ia.subnet.Contains(ip) || !ia.usable(ip) { + return false + } + ia.allocated[ipToUint32(ip)] = struct{}{} + return true +} + +func (ia *ipAllocator) alloc() net.IP { + // Make a copy so we can modify it. + try := make([]byte, len(ia.subnet.IP)) + copy(try, ia.subnet.IP) + + // Just try em all. + for ia.subnet.Contains(try) { + if ia.usable(try) { + ia.allocated[ipToUint32(try)] = struct{}{} + return try + } + nextIP(try) + } + return nil +} + +func (ia *ipAllocator) free(ip net.IP) error { + if ia.usable(ip) { + return fmt.Errorf("cannot free unallocated IP %v", ip) + } + delete(ia.allocated, ipToUint32(ip)) + return nil +} diff --git a/vendor/github.com/u-root/dhcp4/dhcp4server/server.go b/vendor/github.com/u-root/dhcp4/dhcp4server/server.go new file mode 100644 index 0000000000..02dd4d5745 --- /dev/null +++ b/vendor/github.com/u-root/dhcp4/dhcp4server/server.go @@ -0,0 +1,213 @@ +// Copyright 2018 the u-root Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dhcp4server + +import ( + "fmt" + "log" + "net" + + "github.com/u-root/dhcp4" + "github.com/u-root/dhcp4/dhcp4opts" +) + +type macAddr [16]byte + +const maxMessageSize = 1500 + +type Server struct { + // whoami + ip net.IP + + ips *ipAllocator + conns map[macAddr]net.IP + + sname, filename string +} + +func New(ip net.IP, subnet *net.IPNet, sname, filename string) *Server { + return &Server{ + ip: ip.To4(), + ips: newIPAllocator(subnet), + conns: make(map[macAddr]net.IP), + sname: sname, + filename: filename, + } +} + +func (s *Server) responsePacket(request *dhcp4.Packet, typ dhcp4opts.DHCPMessageType) *dhcp4.Packet { + packet := dhcp4.NewPacket(dhcp4.BootReply) + packet.HType = request.HType + + // Must include the following according to RFC 2131 Section 4.3.1 Table 3. + packet.TransactionID = request.TransactionID + packet.CHAddr = request.CHAddr + packet.Broadcast = request.Broadcast + packet.GIAddr = request.GIAddr + packet.Options.Add(dhcp4.OptionDHCPMessageType, typ) + packet.Options.Add(dhcp4.OptionServerIdentifier, dhcp4opts.IP(s.ip)) + + // IP of next bootstrap server (us). + packet.SIAddr = s.ip + + // Optional. + packet.Options.Add(dhcp4.OptionMaximumDHCPMessageSize, dhcp4opts.Uint16(maxMessageSize)) + return packet +} + +func (s *Server) getIP(haddr net.HardwareAddr) net.IP { + mac := getMac(haddr) + + // Already allocated an IP to this client. + if ip, ok := s.conns[mac]; ok { + return ip + } + return nil +} + +func (s *Server) newIP(haddr net.HardwareAddr) net.IP { + mac := getMac(haddr) + + // Already allocated an IP to this client. + if ip, ok := s.conns[mac]; ok { + return ip + } + + ip := s.ips.alloc() + if ip == nil { + return nil + } + s.conns[mac] = ip + return ip +} + +func (s *Server) grabIP(haddr net.HardwareAddr, ip net.IP) bool { + mac := getMac(haddr) + + if _, ok := s.conns[mac]; ok { + return false + } + + if !s.ips.grab(ip) { + return false + } + s.conns[mac] = ip + return true +} + +func getMac(haddr net.HardwareAddr) macAddr { + var mac macAddr + copy(mac[:], haddr) + return mac +} + +func (s *Server) release(haddr net.HardwareAddr) { + mac := getMac(haddr) + ip, ok := s.conns[mac] + if !ok { + return + } + delete(s.conns, mac) + s.ips.free(ip) +} + +func (s *Server) writePacket(conn net.PacketConn, addr net.Addr, p *dhcp4.Packet) error { + pkt, err := p.MarshalBinary() + if err != nil { + return err + } + + uaddr, ok := addr.(*net.UDPAddr) + if !ok { + return fmt.Errorf("DHCP send: addr %v is not a UDP address", addr) + } + if uaddr.IP.Equal(net.IPv4zero) { + // Broadcast instead + _, err = conn.WriteTo(pkt, &net.UDPAddr{IP: net.IPv4bcast, Port: uaddr.Port}) + return err + } + _, err = conn.WriteTo(pkt, addr) + return err +} + +func (s *Server) Serve(logger *log.Logger, conn net.PacketConn) error { + var buf [maxMessageSize]byte + for { + n, addr, err := conn.ReadFrom(buf[:]) + if err != nil { + return err + } + + pkt, err := dhcp4.ParsePacket(buf[:n]) + if err != nil { + logger.Printf("Invalid DHCP packet from %v: %v", addr, err) + continue + } + + switch typ := dhcp4opts.GetDHCPMessageType(pkt.Options); typ { + case dhcp4opts.DHCPDiscover: + offer := s.responsePacket(pkt, dhcp4opts.DHCPOffer) + mac := getMac(pkt.CHAddr) + + if ip, ok := s.conns[mac]; ok { + // Already has an IP allocated. + offer.YIAddr = ip + } else if rip := dhcp4opts.GetRequestedIPAddress(pkt.Options); s.grabIP(pkt.CHAddr, net.IP(rip)) { + // Requested IP is available. + offer.YIAddr = net.IP(rip) + } else if ip := s.newIP(pkt.CHAddr); ip != nil { + // Grab a random new IP. + offer.YIAddr = ip + } + + offer.ServerName = s.sname + offer.BootFile = s.filename + if offer.YIAddr != nil { + if err := s.writePacket(conn, addr, offer); err != nil { + // TODO Undo address assignment. + return err + } + } else { + // TODO: send rejection. + } + + case dhcp4opts.DHCPRequest: + offered := s.getIP(pkt.CHAddr) + + rip := dhcp4opts.GetRequestedIPAddress(pkt.Options) + var re *dhcp4.Packet + if !net.IP(rip).Equal(offered) { + // Client is confused about IP offered? + re = s.responsePacket(pkt, dhcp4opts.DHCPNAK) + } else { + re = s.responsePacket(pkt, dhcp4opts.DHCPACK) + re.CIAddr = pkt.CIAddr + re.YIAddr = offered + re.ServerName = s.sname + re.BootFile = s.filename + } + + if err := s.writePacket(conn, addr, re); err != nil { + // TODO: Undo address assignment. + return err + } + + case dhcp4opts.DHCPDecline, dhcp4opts.DHCPRelease: + // TODO + s.release(pkt.CHAddr) + + case dhcp4opts.DHCPInform: + // TODO + + case dhcp4opts.DHCPOffer, dhcp4opts.DHCPACK, dhcp4opts.DHCPNAK: + // DHCP servers ignore these according to RFC 2131, + // Section 4.3. + + default: + logger.Printf("DHCP message with unknown type %v", typ) + continue + } + } +} diff --git a/vendor/github.com/u-root/dhcp4/packet.go b/vendor/github.com/u-root/dhcp4/packet.go index 95b061db4b..dd4968b734 100644 --- a/vendor/github.com/u-root/dhcp4/packet.go +++ b/vendor/github.com/u-root/dhcp4/packet.go @@ -143,6 +143,15 @@ func (p *Packet) MarshalBinary() ([]byte, error) { return b.Data(), nil } +// ParsePacket parses a DHCP4 packet from q. +func ParsePacket(q []byte) (*Packet, error) { + var pkt Packet + if err := (&pkt).UnmarshalBinary(q); err != nil { + return nil, err + } + return &pkt, nil +} + // UnmarshalBinary reads the packet from binary. func (p *Packet) UnmarshalBinary(q []byte) error { b := buffer.New(q)