From 917f20fe1cb3589979e7c75e09725fe295946c63 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Thu, 21 May 2020 12:27:32 +0300 Subject: [PATCH] wip --- dhcpd/dhcpd_test.go | 246 +++++++------------------------------------- dhcpd/v4.go | 9 +- dhcpd/v6.go | 132 ++++++++++++++++++------ dhcpd/v6_test.go | 58 +++++++++-- 4 files changed, 193 insertions(+), 252 deletions(-) diff --git a/dhcpd/dhcpd_test.go b/dhcpd/dhcpd_test.go index b08ffb86..ca472c11 100644 --- a/dhcpd/dhcpd_test.go +++ b/dhcpd/dhcpd_test.go @@ -7,7 +7,6 @@ import ( "testing" "time" - "github.com/krolaw/dhcp4" "github.com/stretchr/testify/assert" ) @@ -17,234 +16,63 @@ func check(t *testing.T, result bool, msg string) { } } -// Tests performed: -// . Handle Discover message (lease reserve) -// . Handle Request message (lease commit) -// . Static leases -func TestDHCP(t *testing.T) { - var s = Server{} - s.conf.DBFilePath = dbFilename - defer func() { _ = os.Remove(dbFilename) }() - var p, p2 dhcp4.Packet - var hw net.HardwareAddr - var lease *Lease - var opt dhcp4.Options - - s.reset() - s.leaseStart = []byte{1, 1, 1, 1} - s.leaseStop = []byte{1, 1, 1, 2} - s.leaseTime = 5 * time.Second - s.leaseOptions = dhcp4.Options{} - s.ipnet = &net.IPNet{ - IP: []byte{1, 2, 3, 4}, - Mask: []byte{0xff, 0xff, 0xff, 0xff}, - } - - p = make(dhcp4.Packet, 241) - - // Discover and reserve an IP - hw = []byte{3, 2, 3, 4, 5, 6} - p.SetCHAddr(hw) - p.SetCIAddr([]byte{0, 0, 0, 0}) - opt = make(dhcp4.Options, 10) - p2 = s.handleDiscover(p, opt) - opt = p2.ParseOptions() - check(t, bytes.Equal(opt[dhcp4.OptionDHCPMessageType], []byte{byte(dhcp4.Offer)}), "dhcp4.Offer") - check(t, bytes.Equal(p2.YIAddr(), []byte{1, 1, 1, 1}), "p2.YIAddr") - check(t, bytes.Equal(p2.CHAddr(), hw), "p2.CHAddr") - check(t, bytes.Equal(opt[dhcp4.OptionIPAddressLeaseTime], dhcp4.OptionsLeaseTime(5*time.Second)), "OptionIPAddressLeaseTime") - check(t, bytes.Equal(opt[dhcp4.OptionServerIdentifier], s.ipnet.IP), "OptionServerIdentifier") - - lease = s.findLease(p) - check(t, bytes.Equal(lease.HWAddr, hw), "lease.HWAddr") - check(t, bytes.Equal(lease.IP, []byte{1, 1, 1, 1}), "lease.IP") - - // Reserve an IP - the next IP from the range - hw = []byte{2, 2, 3, 4, 5, 6} - p.SetCHAddr(hw) - lease, _ = s.reserveLease(p) - check(t, bytes.Equal(lease.HWAddr, hw), "lease.HWAddr") - check(t, bytes.Equal(lease.IP, []byte{1, 1, 1, 2}), "lease.IP") - - // Reserve an IP - we have no more available IPs, - // so the first expired (or, in our case, not yet committed) lease is returned - hw = []byte{1, 2, 3, 4, 5, 6} - p.SetCHAddr(hw) - lease, _ = s.reserveLease(p) - check(t, bytes.Equal(lease.HWAddr, hw), "lease.HWAddr") - check(t, bytes.Equal(lease.IP, []byte{1, 1, 1, 1}), "lease.IP") - - // Decline request for a lease which doesn't match our internal state - hw = []byte{1, 2, 3, 4, 5, 6} - p.SetCHAddr(hw) - p.SetCIAddr([]byte{0, 0, 0, 0}) - opt = make(dhcp4.Options, 10) - // ask a different IP - opt[dhcp4.OptionRequestedIPAddress] = []byte{1, 1, 1, 2} - p2 = s.handleDHCP4Request(p, opt) - opt = p2.ParseOptions() - check(t, bytes.Equal(opt[dhcp4.OptionDHCPMessageType], []byte{byte(dhcp4.NAK)}), "dhcp4.NAK") - - // Commit the previously reserved lease - hw = []byte{1, 2, 3, 4, 5, 6} - p.SetCHAddr(hw) - p.SetCIAddr([]byte{0, 0, 0, 0}) - opt = make(dhcp4.Options, 10) - opt[dhcp4.OptionRequestedIPAddress] = []byte{1, 1, 1, 1} - p2 = s.handleDHCP4Request(p, opt) - opt = p2.ParseOptions() - check(t, bytes.Equal(opt[dhcp4.OptionDHCPMessageType], []byte{byte(dhcp4.ACK)}), "dhcp4.ACK") - check(t, bytes.Equal(p2.YIAddr(), []byte{1, 1, 1, 1}), "p2.YIAddr") - check(t, bytes.Equal(p2.CHAddr(), hw), "p2.CHAddr") - check(t, bytes.Equal(opt[dhcp4.OptionIPAddressLeaseTime], dhcp4.OptionsLeaseTime(5*time.Second)), "OptionIPAddressLeaseTime") - check(t, bytes.Equal(opt[dhcp4.OptionServerIdentifier], s.ipnet.IP), "OptionServerIdentifier") - - check(t, bytes.Equal(s.FindIPbyMAC(hw), []byte{1, 1, 1, 1}), "FindIPbyMAC") - - // Commit the previously reserved lease #2 - hw = []byte{2, 2, 3, 4, 5, 6} - p.SetCHAddr(hw) - p.SetCIAddr([]byte{0, 0, 0, 0}) - opt = make(dhcp4.Options, 10) - opt[dhcp4.OptionRequestedIPAddress] = []byte{1, 1, 1, 2} - p2 = s.handleDHCP4Request(p, opt) - check(t, bytes.Equal(p2.YIAddr(), []byte{1, 1, 1, 2}), "p2.YIAddr") - - // Reserve an IP - we have no more available IPs - hw = []byte{3, 2, 3, 4, 5, 6} - p.SetCHAddr(hw) - lease, _ = s.reserveLease(p) - check(t, lease == nil, "lease == nil") - - s.reset() - testStaticLeases(t, &s) - testStaticLeaseReplaceByMAC(t, &s) - - s.reset() - misc(t, &s) -} - -func testStaticLeases(t *testing.T, s *Server) { - var err error - var l Lease - l.IP = []byte{1, 1, 1, 1} - - l.HWAddr = []byte{1, 2, 3, 4, 5, 6} - s.leases = append(s.leases, &l) - - // replace dynamic lease with a static (same IP) - l.HWAddr = []byte{2, 2, 3, 4, 5, 6} - err = s.AddStaticLease(l) - check(t, err == nil, "AddStaticLease") - - ll := s.Leases(LeasesAll) - assert.True(t, len(ll) == 1) - assert.True(t, bytes.Equal(ll[0].IP, []byte{1, 1, 1, 1})) - assert.True(t, bytes.Equal(ll[0].HWAddr, []byte{2, 2, 3, 4, 5, 6})) - assert.True(t, ll[0].Expiry.Unix() == leaseExpireStatic) - - err = s.RemoveStaticLease(l) - assert.True(t, err == nil) - - ll = s.Leases(LeasesAll) - assert.True(t, len(ll) == 0) -} - -func testStaticLeaseReplaceByMAC(t *testing.T, s *Server) { - var err error - var l Lease - l.HWAddr = []byte{1, 2, 3, 4, 5, 6} - - l.IP = []byte{1, 1, 1, 1} - l.Expiry = time.Now().Add(time.Hour) - s.leases = append(s.leases, &l) - - // replace dynamic lease with a static (same MAC) - l.IP = []byte{2, 1, 1, 1} - err = s.AddStaticLease(l) - assert.True(t, err == nil) - - ll := s.Leases(LeasesAll) - assert.True(t, len(ll) == 1) - assert.True(t, bytes.Equal(ll[0].IP, []byte{2, 1, 1, 1})) - assert.True(t, bytes.Equal(ll[0].HWAddr, []byte{1, 2, 3, 4, 5, 6})) -} - -// Small tests that don't require a static server's state -func misc(t *testing.T, s *Server) { - var p, p2 dhcp4.Packet - var hw net.HardwareAddr - var opt dhcp4.Options - - p = make(dhcp4.Packet, 241) - - // Try to commit a lease for an IP without prior Discover-Offer packets - hw = []byte{2, 2, 3, 4, 5, 6} - p.SetCHAddr(hw) - p.SetCIAddr([]byte{0, 0, 0, 0}) - opt = make(dhcp4.Options, 10) - opt[dhcp4.OptionRequestedIPAddress] = []byte{1, 1, 1, 1} - p2 = s.handleDHCP4Request(p, opt) - opt = p2.ParseOptions() - check(t, bytes.Equal(opt[dhcp4.OptionDHCPMessageType], []byte{byte(dhcp4.NAK)}), "dhcp4.NAK") -} - // Leases database store/load func TestDB(t *testing.T) { - var s = Server{} + var err error + s := Server{} s.conf.DBFilePath = dbFilename - var p dhcp4.Packet - var hw1, hw2 net.HardwareAddr - var lease *Lease - s.reset() - s.leaseStart = []byte{1, 1, 1, 1} - s.leaseStop = []byte{1, 1, 1, 2} - s.leaseTime = 5 * time.Second - s.leaseOptions = dhcp4.Options{} - s.ipnet = &net.IPNet{ - IP: []byte{1, 2, 3, 4}, - Mask: []byte{0xff, 0xff, 0xff, 0xff}, + conf := V4ServerConf{ + Enabled: true, + RangeStart: "192.168.10.100", + RangeEnd: "192.168.10.200", + GatewayIP: "192.168.10.1", + SubnetMask: "255.255.255.0", + notify: notify4, } + s.srv4, err = v4Create(conf) + assert.True(t, err == nil) - p = make(dhcp4.Packet, 241) + s.srv6, err = v6Create(V6ServerConf{}) + assert.True(t, err == nil) - hw1 = []byte{1, 2, 3, 4, 5, 6} - p.SetCHAddr(hw1) - lease, _ = s.reserveLease(p) - lease.Expiry = time.Unix(4000000001, 0) + l := Lease{} + l.IP = net.ParseIP("192.168.10.100").To4() + l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") + exp1 := time.Now().Add(time.Hour) + l.Expiry = exp1 + s.srv4.addLease(&l) - hw2 = []byte{2, 2, 3, 4, 5, 6} - p.SetCHAddr(hw2) - lease, _ = s.reserveLease(p) - lease.Expiry = time.Unix(4000000002, 0) + l2 := Lease{} + l2.IP = net.ParseIP("192.168.10.101").To4() + l2.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:bb") + s.srv4.AddStaticLease(l2) _ = os.Remove("leases.db") s.dbStore() s.reset() s.dbLoad() - check(t, bytes.Equal(s.leases[0].HWAddr, hw1), "leases[0].HWAddr") - check(t, bytes.Equal(s.leases[0].IP, []byte{1, 1, 1, 1}), "leases[0].IP") - check(t, s.leases[0].Expiry.Unix() == 4000000001, "leases[0].Expiry") - check(t, bytes.Equal(s.leases[1].HWAddr, hw2), "leases[1].HWAddr") - check(t, bytes.Equal(s.leases[1].IP, []byte{1, 1, 1, 2}), "leases[1].IP") - check(t, s.leases[1].Expiry.Unix() == 4000000002, "leases[1].Expiry") + ll := s.srv4.GetLeases(LeasesAll) + + assert.Equal(t, "aa:aa:aa:aa:aa:bb", ll[0].HWAddr.String()) + assert.Equal(t, "192.168.10.101", ll[0].IP.String()) + assert.Equal(t, int64(leaseExpireStatic), ll[0].Expiry.Unix()) + + assert.Equal(t, "aa:aa:aa:aa:aa:aa", ll[1].HWAddr.String()) + assert.Equal(t, "192.168.10.100", ll[1].IP.String()) + assert.Equal(t, exp1.Unix(), ll[1].Expiry.Unix()) _ = os.Remove("leases.db") } func TestIsValidSubnetMask(t *testing.T) { - if !isValidSubnetMask([]byte{255, 255, 255, 0}) { - t.Fatalf("isValidSubnetMask([]byte{255,255,255,0})") - } - if isValidSubnetMask([]byte{255, 255, 253, 0}) { - t.Fatalf("isValidSubnetMask([]byte{255,255,253,0})") - } - if isValidSubnetMask([]byte{0, 255, 255, 255}) { - t.Fatalf("isValidSubnetMask([]byte{255,255,253,0})") - } + assert.True(t, isValidSubnetMask([]byte{255, 255, 255, 0})) + assert.True(t, isValidSubnetMask([]byte{255, 255, 254, 0})) + assert.True(t, isValidSubnetMask([]byte{255, 255, 252, 0})) + assert.True(t, !isValidSubnetMask([]byte{255, 255, 253, 0})) + assert.True(t, !isValidSubnetMask([]byte{255, 255, 255, 1})) } func TestNormalizeLeases(t *testing.T) { diff --git a/dhcpd/v4.go b/dhcpd/v4.go index d41e94a2..04928c7c 100644 --- a/dhcpd/v4.go +++ b/dhcpd/v4.go @@ -282,13 +282,16 @@ func (s *V4Server) findLease(mac net.HardwareAddr) *Lease { // Get next free IP func (s *V4Server) findFreeIP() net.IP { - for i := s.conf.ipStart[3]; i != s.conf.ipEnd[3]; i++ { + for i := s.conf.ipStart[3]; ; i++ { if s.ipAddrs[i] == 0 { ip := make([]byte, 4) copy(ip, s.conf.ipStart) ip[3] = i return ip } + if i == s.conf.ipEnd[3] { + break + } } return nil } @@ -329,7 +332,6 @@ func (s *V4Server) reserveLease(mac net.HardwareAddr) *Lease { func (s *V4Server) process(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) int { var lease *Lease - mac := req.ClientHWAddr if len(mac) != 6 { log.Debug("DHCPv4: Invalid ClientHWAddr") @@ -338,6 +340,8 @@ func (s *V4Server) process(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) int { hostname := req.Options.Get(dhcpv4.OptionHostName) reqIP := req.Options.Get(dhcpv4.OptionRequestedIPAddress) + resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0])) + switch req.MessageType() { case dhcpv4.MessageTypeDiscover: @@ -472,7 +476,6 @@ func (s *V4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4 log.Debug("DHCPv4: dhcpv4.New: %s", err) return } - resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0])) r := s.process(req, resp) if r < 0 { diff --git a/dhcpd/v6.go b/dhcpd/v6.go index e61ac9aa..a4a2d70f 100644 --- a/dhcpd/v6.go +++ b/dhcpd/v6.go @@ -18,8 +18,9 @@ const valueIAID = "ADGH" // value for IANA.ID // V6Server - DHCPv6 server type V6Server struct { srv *server6.Server - leases []*Lease leasesLock sync.Mutex + leases []*Lease + ipAddrs [256]byte conf V6ServerConf } @@ -97,6 +98,46 @@ func (s *V6Server) FindMACbyIP6(ip net.IP) net.HardwareAddr { return nil } +// Remove (swap) lease by index +func (s *V6Server) leaseRemoveSwapByIndex(i int) { + s.ipAddrs[s.leases[i].IP[15]] = 0 + log.Debug("DHCPv6: removed lease %s", s.leases[i].HWAddr) + + n := len(s.leases) + if i != n-1 { + s.leases[i] = s.leases[n-1] // swap with the last element + } + s.leases = s.leases[:n-1] +} + +// Remove a dynamic lease with the same properties +// Return error if a static lease is found +func (s *V6Server) rmDynamicLease(lease Lease) error { + for i := 0; i < len(s.leases); i++ { + l := s.leases[i] + + if bytes.Equal(l.HWAddr, lease.HWAddr) { + + if l.Expiry.Unix() == leaseExpireStatic { + return fmt.Errorf("static lease already exists") + } + + s.leaseRemoveSwapByIndex(i) + l = s.leases[i] + } + + if bytes.Equal(l.IP, lease.IP) { + + if l.Expiry.Unix() == leaseExpireStatic { + return fmt.Errorf("static lease already exists") + } + + s.leaseRemoveSwapByIndex(i) + } + } + return nil +} + // AddStaticLease - add a static lease func (s *V6Server) AddStaticLease(l Lease) error { if len(l.IP) != 16 { @@ -109,13 +150,15 @@ func (s *V6Server) AddStaticLease(l Lease) error { l.Expiry = time.Unix(leaseExpireStatic, 0) s.leasesLock.Lock() - err := s.addLease(l) + err := s.rmDynamicLease(l) if err != nil { s.leasesLock.Unlock() return err } + s.addLease(&l) s.conf.notify(LeaseChangedDBStore) s.leasesLock.Unlock() + s.conf.notify(LeaseChangedAddedStatic) return nil } @@ -142,36 +185,28 @@ func (s *V6Server) RemoveStaticLease(l Lease) error { } // Add a lease -func (s *V6Server) addLease(l Lease) error { - for _, it := range s.leases { - if net.IP.Equal(it.IP, l.IP) || - bytes.Equal(it.HWAddr, l.HWAddr) { - return fmt.Errorf("Lease already exists") - } - } - s.leases = append(s.leases, &l) - return nil +func (s *V6Server) addLease(l *Lease) { + s.leases = append(s.leases, l) + s.ipAddrs[l.IP[15]] = 1 + log.Debug("DHCPv6: added lease %s <-> %s", l.IP, l.HWAddr) } -// Remove a lease -func (s *V6Server) rmLease(l Lease) error { - var newLeases []*Lease - for _, lease := range s.leases { - if net.IP.Equal(lease.IP, l.IP) { - if !bytes.Equal(lease.HWAddr, l.HWAddr) { +// Remove a lease with the same properies +func (s *V6Server) rmLease(lease Lease) error { + for i, l := range s.leases { + if bytes.Equal(l.IP, lease.IP) { + + if !bytes.Equal(l.HWAddr, lease.HWAddr) || + l.Hostname != lease.Hostname { + return fmt.Errorf("Lease not found") } - continue + + s.leaseRemoveSwapByIndex(i) + return nil } - newLeases = append(newLeases, lease) } - - if len(newLeases) == len(s.leases) { - return fmt.Errorf("Lease not found: %s", l.IP) - } - - s.leases = newLeases - return nil + return fmt.Errorf("lease not found") } // Find lease by MAC @@ -187,26 +222,55 @@ func (s *V6Server) findLease(mac net.HardwareAddr) *Lease { return nil } +// Find an expired lease and return its index or -1 +func (s *V6Server) findExpiredLease() int { + now := time.Now().Unix() + for i, lease := range s.leases { + if lease.Expiry.Unix() != leaseExpireStatic && + lease.Expiry.Unix() <= now { + return i + } + } + return -1 +} + +// Get next free IP +func (s *V6Server) findFreeIP() net.IP { + for i := s.conf.ipStart[15]; ; i++ { + if s.ipAddrs[i] == 0 { + ip := make([]byte, 16) + copy(ip, s.conf.ipStart) + ip[15] = i + return ip + } + if i == 0xff { + break + } + } + return nil +} + // Reserve lease for MAC func (s *V6Server) reserveLease(mac net.HardwareAddr) *Lease { l := Lease{} l.HWAddr = make([]byte, 6) copy(l.HWAddr, mac) - l.IP = make([]byte, 16) s.leasesLock.Lock() defer s.leasesLock.Unlock() copy(l.IP, s.conf.ipStart) - if s.conf.ipStart[15] == 0xff { - return nil + l.IP = s.findFreeIP() + if l.IP == nil { + i := s.findExpiredLease() + if i < 0 { + return nil + } + copy(s.leases[i].HWAddr, mac) + return s.leases[i] } - s.conf.ipStart[15]++ - err := s.addLease(l) - if err != nil { - return nil - } + s.addLease(&l) return &l } diff --git a/dhcpd/v6_test.go b/dhcpd/v6_test.go index 4afe8827..90a94ccc 100644 --- a/dhcpd/v6_test.go +++ b/dhcpd/v6_test.go @@ -9,14 +9,14 @@ import ( "github.com/stretchr/testify/assert" ) -func notify(flags uint32) { +func notify6(flags uint32) { } -func TestV6StaticLease(t *testing.T) { +func TestV6StaticLeaseAddRemove(t *testing.T) { conf := V6ServerConf{ Enabled: true, RangeStart: "2001::1", - notify: notify, + notify: notify6, } s, err := v6Create(conf) assert.True(t, err == nil) @@ -53,15 +53,61 @@ func TestV6StaticLease(t *testing.T) { // check ls = s.GetLeases(LeasesStatic) assert.Equal(t, 0, len(ls)) +} - s.Stop() +func TestV6StaticLeaseAddReplaceDynamic(t *testing.T) { + conf := V6ServerConf{ + Enabled: true, + RangeStart: "2001::1", + notify: notify6, + } + s, err := v6Create(conf) + assert.True(t, err == nil) + + // add dynamic lease + ld := Lease{} + ld.IP = net.ParseIP("2001::1") + ld.HWAddr, _ = net.ParseMAC("11:aa:aa:aa:aa:aa") + s.addLease(&ld) + + // add dynamic lease + { + ld := Lease{} + ld.IP = net.ParseIP("2001::2") + ld.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa") + s.addLease(&ld) + } + + // add static lease with the same IP + l := Lease{} + l.IP = net.ParseIP("2001::1") + l.HWAddr, _ = net.ParseMAC("33:aa:aa:aa:aa:aa") + assert.True(t, s.AddStaticLease(l) == nil) + + // add static lease with the same MAC + l = Lease{} + l.IP = net.ParseIP("2001::3") + l.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa") + assert.True(t, s.AddStaticLease(l) == nil) + + // check + ls := s.GetLeases(LeasesStatic) + assert.Equal(t, 2, len(ls)) + + assert.Equal(t, "2001::1", ls[0].IP.String()) + assert.Equal(t, "33:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) + assert.True(t, ls[0].Expiry.Unix() == leaseExpireStatic) + + assert.Equal(t, "2001::3", ls[1].IP.String()) + assert.Equal(t, "22:aa:aa:aa:aa:aa", ls[1].HWAddr.String()) + assert.True(t, ls[1].Expiry.Unix() == leaseExpireStatic) } func TestV6GetLease(t *testing.T) { conf := V6ServerConf{ Enabled: true, RangeStart: "2001::1", - notify: notify, + notify: notify6, } s, err := v6Create(conf) assert.True(t, err == nil) @@ -122,7 +168,7 @@ func TestV6GetDynamicLease(t *testing.T) { conf := V6ServerConf{ Enabled: true, RangeStart: "2001::2", - notify: notify, + notify: notify6, } s, err := v6Create(conf) assert.True(t, err == nil)