Skip to content

Commit 9c8560b

Browse files
committed
feat: optional arp checks
1 parent 90f2e60 commit 9c8560b

File tree

6 files changed

+25
-17
lines changed

6 files changed

+25
-17
lines changed

conf.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ server:
1212
interface: "en0" # Network interface to bind the DHCP server to
1313
lease_db_path: "leases.db" # Path to the db file for lease persistence
1414
cleanup_expired_interval: 120 # Interval in seconds for cleaning up expired leases
15+
arp_check: true # Enable or disable server ARP check for ip conflicts
1516

1617
logging:
1718
level: "info" # Log level (debug, info, warn, error)

internal/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type Config struct {
2323
Port int `yaml:"port" default:"67"`
2424
LeaseDBPath string `yaml:"lease_db_path"`
2525
CleanupExpiredInterval int `yaml:"cleanup_expired_interval" default:"120"`
26+
ARPCheck bool `yaml:"arp_check" default:"true"`
2627
} `yaml:"server"`
2728
Logging struct {
2829
Level string `yaml:"level"`

pkg/dhcp/lease.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func LeasePool(start, end string, leaseTime int, dbPath string) (*Pool, error) {
107107
return p, nil
108108
}
109109

110-
func (p *Pool) Allocate(iface *net.Interface, mac net.HardwareAddr) (net.IP, error) {
110+
func (p *Pool) Allocate(iface *net.Interface, mac net.HardwareAddr, arp bool) (net.IP, error) {
111111
p.mutex.Lock()
112112
defer p.mutex.Unlock()
113113

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

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

394394
if de != nil {
395-
continue // skip corrupt
395+
continue // skip corrupt
396396
}
397397
if now.After(l.ExpireAt) {
398398
if err := c.Delete(); err != nil {
@@ -402,7 +402,7 @@ func (p *Pool) CleanupExpiredLeases() error {
402402
return err
403403
}
404404

405-
// track offset for clearing in bitmap
405+
// track offset for clearing in bitmap
406406
offset := int(ipToUint32(l.IP) - startUint)
407407
if offset >= 0 && offset < p.totalLeases {
408408
freedOffsets = append(freedOffsets, offset)

pkg/dhcp/server.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func (s *Server) HandleMessage(data []byte, remoteAddr *net.UDPAddr) {
145145
func (s *Server) HandleDiscover(message *Message, remoteAddr *net.UDPAddr) {
146146
log.Infof("[DHCPDISCOVER] from MAC=%s;", message.CHAddr)
147147

148-
ip, err := s.LeasePool.Allocate(s.Interface, message.CHAddr)
148+
ip, err := s.LeasePool.Allocate(s.Interface, message.CHAddr, s.Config.Server.ARPCheck)
149149
if err != nil {
150150
log.Errorf("Error allocating lease: %v", err)
151151
return
@@ -188,7 +188,7 @@ func (s *Server) HandleRequest(message *Message, remoteAddr *net.UDPAddr) {
188188
leases := s.LeasePool.GetLeaseByMAC(message.CHAddr)
189189
if leases == nil {
190190
// client is new or changed MACs. We can attempt new allocation
191-
ip, err := s.LeasePool.Allocate(s.Interface, message.CHAddr)
191+
ip, err := s.LeasePool.Allocate(s.Interface, message.CHAddr, s.Config.Server.ARPCheck)
192192
if err != nil {
193193
s.SendNAK(message, "No available IP")
194194
return

tests/lease_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func TestLeasePoolBasic(t *testing.T) {
2020
defer pool.Close()
2121

2222
mac := net.HardwareAddr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}
23-
ip, err := pool.Allocate(&net.Interface{Name: "lo"}, mac)
23+
ip, err := pool.Allocate(&net.Interface{Name: "lo"}, mac, false)
2424
if err != nil {
2525
t.Fatalf("Failed to allocate IP for MAC=%s: %v", mac, err)
2626
}
@@ -61,14 +61,14 @@ func TestLeasePoolExhaustion(t *testing.T) {
6161
{0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x30},
6262
}
6363
for _, mac := range macs {
64-
_, err := pool.Allocate(&net.Interface{Name: "lo"}, mac)
64+
_, err := pool.Allocate(&net.Interface{Name: "lo"}, mac, false)
6565
if err != nil {
6666
t.Fatalf("Unexpected error allocating IP: %v", err)
6767
}
6868
}
6969

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

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

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

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

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

145145
//(offset 2)
146-
ip3, err := pool.Allocate(iface, mac3)
146+
ip3, err := pool.Allocate(iface, mac3, false)
147147
if err != nil {
148148
t.Fatalf("Allocate(mac3) failed: %v", err)
149149
}
@@ -156,7 +156,7 @@ func TestLeaseWrapAround(t *testing.T) {
156156
t.Fatalf("Release(ip2) failed: %v", err)
157157
}
158158

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

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

176176
mac6, _ := net.ParseMAC("00:11:22:33:44:06")
177-
ip6, err := pool.Allocate(iface, mac6)
177+
ip6, err := pool.Allocate(iface, mac6, false)
178178
if err != nil {
179179
t.Fatalf("Allocate(mac6) failed: %v", err)
180180
}

tests/server_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func createTestConfig(dbPath string) *config.Config {
2727
Port int `yaml:"port" default:"67"`
2828
LeaseDBPath string `yaml:"lease_db_path"`
2929
CleanupExpiredInterval int `yaml:"cleanup_expired_interval" default:"120"`
30+
ARPCheck bool `yaml:"arp_check" default:"true"`
3031
}{
3132
IPStart: "192.168.100.10",
3233
IPEnd: "192.168.100.12",
@@ -38,6 +39,7 @@ func createTestConfig(dbPath string) *config.Config {
3839
Interface: "lo",
3940
Port: 6767,
4041
LeaseDBPath: dbPath,
42+
ARPCheck: false,
4143
},
4244
}
4345
}
@@ -161,6 +163,7 @@ func TestRequestFlow(t *testing.T) {
161163
Port int `yaml:"port" default:"67"`
162164
LeaseDBPath string `yaml:"lease_db_path"`
163165
CleanupExpiredInterval int `yaml:"cleanup_expired_interval" default:"120"`
166+
ARPCheck bool `yaml:"arp_check" default:"true"`
164167
}{
165168
IPStart: "192.168.100.10",
166169
IPEnd: "192.168.100.15",
@@ -172,6 +175,7 @@ func TestRequestFlow(t *testing.T) {
172175
Interface: "lo",
173176
Port: 6767,
174177
LeaseDBPath: "test_request_flow.db",
178+
ARPCheck: false,
175179
},
176180
}
177181

@@ -244,6 +248,7 @@ func TestHighConcurrencyFlood(t *testing.T) {
244248
Port int `yaml:"port" default:"67"`
245249
LeaseDBPath string `yaml:"lease_db_path"`
246250
CleanupExpiredInterval int `yaml:"cleanup_expired_interval" default:"120"`
251+
ARPCheck bool `yaml:"arp_check" default:"true"`
247252
}{
248253
IPStart: "192.168.0.10",
249254
IPEnd: "192.183.255.0",
@@ -255,6 +260,7 @@ func TestHighConcurrencyFlood(t *testing.T) {
255260
Interface: "lo",
256261
Port: 6767,
257262
LeaseDBPath: "test_high_concurrency_flood.db",
263+
ARPCheck: false,
258264
}, Logging: struct {
259265
Level string `yaml:"level"`
260266
}{Level: "error"},

0 commit comments

Comments
 (0)