Skip to content

Commit

Permalink
feat: optional arp checks
Browse files Browse the repository at this point in the history
  • Loading branch information
umegbewe committed Jan 16, 2025
1 parent 90f2e60 commit 9c8560b
Show file tree
Hide file tree
Showing 6 changed files with 25 additions and 17 deletions.
1 change: 1 addition & 0 deletions conf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ server:
interface: "en0" # Network interface to bind the DHCP server to
lease_db_path: "leases.db" # Path to the db file for lease persistence
cleanup_expired_interval: 120 # Interval in seconds for cleaning up expired leases
arp_check: true # Enable or disable server ARP check for ip conflicts

logging:
level: "info" # Log level (debug, info, warn, error)
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Config struct {
Port int `yaml:"port" default:"67"`
LeaseDBPath string `yaml:"lease_db_path"`
CleanupExpiredInterval int `yaml:"cleanup_expired_interval" default:"120"`
ARPCheck bool `yaml:"arp_check" default:"true"`
} `yaml:"server"`
Logging struct {
Level string `yaml:"level"`
Expand Down
8 changes: 4 additions & 4 deletions pkg/dhcp/lease.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func LeasePool(start, end string, leaseTime int, dbPath string) (*Pool, error) {
return p, nil
}

func (p *Pool) Allocate(iface *net.Interface, mac net.HardwareAddr) (net.IP, error) {
func (p *Pool) Allocate(iface *net.Interface, mac net.HardwareAddr, arp bool) (net.IP, error) {
p.mutex.Lock()
defer p.mutex.Unlock()

Expand Down Expand Up @@ -175,7 +175,7 @@ func (p *Pool) Allocate(iface *net.Interface, mac net.HardwareAddr) (net.IP, err
ip := offsetToIP(p.start, freeOffset)

// loopback interfaces should never do arp resolution
if iface.Name != "lo0" && iface.Name != "lo" {
if arp && iface.Name != "lo0" && iface.Name != "lo" {
inUse, err := ARPCheck(iface, ip, 2*time.Second)
if err != nil {
// ARP check failed, bail out.
Expand Down Expand Up @@ -392,7 +392,7 @@ func (p *Pool) CleanupExpiredLeases() error {
l, de := deserialize(v)

if de != nil {
continue // skip corrupt
continue // skip corrupt
}
if now.After(l.ExpireAt) {
if err := c.Delete(); err != nil {
Expand All @@ -402,7 +402,7 @@ func (p *Pool) CleanupExpiredLeases() error {
return err
}

// track offset for clearing in bitmap
// track offset for clearing in bitmap
offset := int(ipToUint32(l.IP) - startUint)
if offset >= 0 && offset < p.totalLeases {
freedOffsets = append(freedOffsets, offset)
Expand Down
4 changes: 2 additions & 2 deletions pkg/dhcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func (s *Server) HandleMessage(data []byte, remoteAddr *net.UDPAddr) {
func (s *Server) HandleDiscover(message *Message, remoteAddr *net.UDPAddr) {
log.Infof("[DHCPDISCOVER] from MAC=%s;", message.CHAddr)

ip, err := s.LeasePool.Allocate(s.Interface, message.CHAddr)
ip, err := s.LeasePool.Allocate(s.Interface, message.CHAddr, s.Config.Server.ARPCheck)
if err != nil {
log.Errorf("Error allocating lease: %v", err)
return
Expand Down Expand Up @@ -188,7 +188,7 @@ func (s *Server) HandleRequest(message *Message, remoteAddr *net.UDPAddr) {
leases := s.LeasePool.GetLeaseByMAC(message.CHAddr)
if leases == nil {
// client is new or changed MACs. We can attempt new allocation
ip, err := s.LeasePool.Allocate(s.Interface, message.CHAddr)
ip, err := s.LeasePool.Allocate(s.Interface, message.CHAddr, s.Config.Server.ARPCheck)
if err != nil {
s.SendNAK(message, "No available IP")
return
Expand Down
22 changes: 11 additions & 11 deletions tests/lease_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestLeasePoolBasic(t *testing.T) {
defer pool.Close()

mac := net.HardwareAddr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}
ip, err := pool.Allocate(&net.Interface{Name: "lo"}, mac)
ip, err := pool.Allocate(&net.Interface{Name: "lo"}, mac, false)
if err != nil {
t.Fatalf("Failed to allocate IP for MAC=%s: %v", mac, err)
}
Expand Down Expand Up @@ -61,14 +61,14 @@ func TestLeasePoolExhaustion(t *testing.T) {
{0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x30},
}
for _, mac := range macs {
_, err := pool.Allocate(&net.Interface{Name: "lo"}, mac)
_, err := pool.Allocate(&net.Interface{Name: "lo"}, mac, false)
if err != nil {
t.Fatalf("Unexpected error allocating IP: %v", err)
}
}

// next allocation should fail
_, err = pool.Allocate(&net.Interface{Name: "lo"}, net.HardwareAddr{0xDE, 0xAD, 0xBE, 0xEF, 0x04, 0x40})
_, err = pool.Allocate(&net.Interface{Name: "lo"}, net.HardwareAddr{0xDE, 0xAD, 0xBE, 0xEF, 0x04, 0x40}, false)
if err == nil {
t.Fatal("Expected an error due to IP exhaustion, but got nil")
}
Expand All @@ -85,7 +85,7 @@ func TestLeasePoolExpiry(t *testing.T) {
defer pool.Close()

mac := net.HardwareAddr{0xCA, 0xFE, 0xBA, 0xBE, 0xBC, 0x01}
ip, err := pool.Allocate(&net.Interface{Name: "lo"}, mac)
ip, err := pool.Allocate(&net.Interface{Name: "lo"}, mac, false)
if err != nil {
t.Fatalf("Failed to allocate IP=%v", err)
}
Expand All @@ -98,7 +98,7 @@ func TestLeasePoolExpiry(t *testing.T) {
}

// allocation should return the same IP
ip2, err := pool.Allocate(&net.Interface{Name: "lo"}, mac)
ip2, err := pool.Allocate(&net.Interface{Name: "lo"}, mac, false)
if err != nil {
t.Fatalf("Failed to re-allocate IP after expiry: %v", err)
}
Expand All @@ -125,7 +125,7 @@ func TestLeaseWrapAround(t *testing.T) {
mac4, _ := net.ParseMAC("00:11:22:33:44:04")

//(offset 0)
ip1, err := pool.Allocate(iface, mac1)
ip1, err := pool.Allocate(iface, mac1, false)
if err != nil {
t.Fatalf("Allocate(mac1) failed: %v", err)
}
Expand All @@ -134,7 +134,7 @@ func TestLeaseWrapAround(t *testing.T) {
}

//(offset 1)
ip2, err := pool.Allocate(iface, mac2)
ip2, err := pool.Allocate(iface, mac2, false)
if err != nil {
t.Fatalf("Allocate(mac2) failed: %v", err)
}
Expand All @@ -143,7 +143,7 @@ func TestLeaseWrapAround(t *testing.T) {
}

//(offset 2)
ip3, err := pool.Allocate(iface, mac3)
ip3, err := pool.Allocate(iface, mac3, false)
if err != nil {
t.Fatalf("Allocate(mac3) failed: %v", err)
}
Expand All @@ -156,7 +156,7 @@ func TestLeaseWrapAround(t *testing.T) {
t.Fatalf("Release(ip2) failed: %v", err)
}

mac4IP, err := pool.Allocate(iface, mac4)
mac4IP, err := pool.Allocate(iface, mac4, false)
if err != nil {
t.Fatalf("Allocate(mac4) failed: %v", err)
}
Expand All @@ -165,7 +165,7 @@ func TestLeaseWrapAround(t *testing.T) {
}

mac5, _ := net.ParseMAC("00:11:22:33:44:05")
ip5, err := pool.Allocate(iface, mac5)
ip5, err := pool.Allocate(iface, mac5, false)
if err != nil {
t.Fatalf("Allocate(mac5) failed: %v", err)
}
Expand All @@ -174,7 +174,7 @@ func TestLeaseWrapAround(t *testing.T) {
}

mac6, _ := net.ParseMAC("00:11:22:33:44:06")
ip6, err := pool.Allocate(iface, mac6)
ip6, err := pool.Allocate(iface, mac6, false)
if err != nil {
t.Fatalf("Allocate(mac6) failed: %v", err)
}
Expand Down
6 changes: 6 additions & 0 deletions tests/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func createTestConfig(dbPath string) *config.Config {
Port int `yaml:"port" default:"67"`
LeaseDBPath string `yaml:"lease_db_path"`
CleanupExpiredInterval int `yaml:"cleanup_expired_interval" default:"120"`
ARPCheck bool `yaml:"arp_check" default:"true"`
}{
IPStart: "192.168.100.10",
IPEnd: "192.168.100.12",
Expand All @@ -38,6 +39,7 @@ func createTestConfig(dbPath string) *config.Config {
Interface: "lo",
Port: 6767,
LeaseDBPath: dbPath,
ARPCheck: false,
},
}
}
Expand Down Expand Up @@ -161,6 +163,7 @@ func TestRequestFlow(t *testing.T) {
Port int `yaml:"port" default:"67"`
LeaseDBPath string `yaml:"lease_db_path"`
CleanupExpiredInterval int `yaml:"cleanup_expired_interval" default:"120"`
ARPCheck bool `yaml:"arp_check" default:"true"`
}{
IPStart: "192.168.100.10",
IPEnd: "192.168.100.15",
Expand All @@ -172,6 +175,7 @@ func TestRequestFlow(t *testing.T) {
Interface: "lo",
Port: 6767,
LeaseDBPath: "test_request_flow.db",
ARPCheck: false,
},
}

Expand Down Expand Up @@ -244,6 +248,7 @@ func TestHighConcurrencyFlood(t *testing.T) {
Port int `yaml:"port" default:"67"`
LeaseDBPath string `yaml:"lease_db_path"`
CleanupExpiredInterval int `yaml:"cleanup_expired_interval" default:"120"`
ARPCheck bool `yaml:"arp_check" default:"true"`
}{
IPStart: "192.168.0.10",
IPEnd: "192.183.255.0",
Expand All @@ -255,6 +260,7 @@ func TestHighConcurrencyFlood(t *testing.T) {
Interface: "lo",
Port: 6767,
LeaseDBPath: "test_high_concurrency_flood.db",
ARPCheck: false,
}, Logging: struct {
Level string `yaml:"level"`
}{Level: "error"},
Expand Down

0 comments on commit 9c8560b

Please sign in to comment.