Skip to content

Commit 110f5c8

Browse files
committed
Refactor pool discovery to be closer to other dhcpd implementations
Like how Cisco IOS and ISC do it, determine the relevant pool by comparing the subnet/mask in each configured pool with the IPs of each local network interface, as opposed to doing one pool per interface. This gets us closer to supporting requests from relay agents, as well as supports the use case of multiple IPs/subnets per interface.
1 parent 8c4778e commit 110f5c8

File tree

8 files changed

+164
-107
lines changed

8 files changed

+164
-107
lines changed

README.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,11 @@ I wanted to make a small and safe Go solution, as well as to learn the DHCP prot
1919

2020
### Configuration
2121

22-
We use yaml. Multiple pools can be defined this way, with one per interface as needed. DHCP traffic to interfaces
23-
not listed will be ignored.
22+
We use yaml. Multiple pools can be defined this way. DHCP traffic to interfaces not listed will be ignored.
2423

2524
```yaml
2625
pools:
2726
- name: vm testing
28-
interface: eth1
2927
network: 172.17.0.0
3028
mask: 255.255.255.0
3129
start: 172.17.0.100
@@ -35,6 +33,7 @@ pools:
3533
routers: [ 172.17.0.1 ]
3634
dns: [ 1.1.1.1, 8.8.8.8 ]
3735

36+
interfaces: [ eth1 ]
3837
leasedir: /var/lib/godhcpd
3938
```
4039

app.go

+60-24
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,26 @@ import (
1010
)
1111

1212
type App struct {
13-
pools []*Pool
14-
interface2Pool map[string]*Pool
13+
ipnet2pool map[HashableIpNet]*Pool
14+
interfaces map[string]struct{}
1515
}
1616

1717
func NewApp() *App {
1818
return &App{
19-
pools: []*Pool{},
20-
interface2Pool: map[string]*Pool{},
19+
ipnet2pool: map[HashableIpNet]*Pool{},
20+
interfaces: map[string]struct{}{},
2121
}
2222
}
2323

24-
func (a *App) InitPools(conf *Conf) error {
24+
func (a *App) InitConf(conf *Conf) error {
25+
26+
for _, iface := range conf.Interfaces {
27+
a.interfaces[iface] = struct{}{}
28+
}
29+
30+
if len(a.interfaces) == 0 {
31+
return errors.New("No interfaces configured")
32+
}
2533

2634
for _, pc := range conf.Pools {
2735
pool, err := pc.ToPool()
@@ -37,9 +45,9 @@ func (a *App) InitPools(conf *Conf) error {
3745
}
3846

3947
if count == 1 {
40-
log.Printf("Loaded pool %v on interface %v with %v lease", pool.Name, pool.Interface, count)
48+
log.Printf("Loaded pool %v with %v lease", pool.Name, count)
4149
} else {
42-
log.Printf("Loaded pool %v on interface %v with %v leases", pool.Name, pool.Interface, count)
50+
log.Printf("Loaded pool %v with %v leases", pool.Name, count)
4351
}
4452

4553
err = a.insertPool(pool)
@@ -52,50 +60,78 @@ func (a *App) InitPools(conf *Conf) error {
5260
}
5361

5462
func (a *App) insertPool(p *Pool) error {
55-
if _, ok := a.interface2Pool[p.Interface]; ok {
56-
return errors.New("Interfaces may be used by only one pool")
63+
ipnet := HashableIpNet{
64+
IP: IpToFixedV4(p.Network),
65+
Mask: IpToFixedV4(p.Netmask),
66+
}
67+
68+
if _, ok := a.ipnet2pool[ipnet]; ok {
69+
return errors.New("Duplicate IP network between pools")
5770
}
5871

59-
a.interface2Pool[p.Interface] = p
60-
a.pools = append(a.pools, p)
72+
a.ipnet2pool[ipnet] = p
6173

6274
return nil
6375
}
6476

65-
func (a *App) oObToInterface(oob []byte) (string, error) {
77+
func (a *App) oObToInterface(oob []byte) (*net.Interface, error) {
6678
cm := &ipv4.ControlMessage{}
6779

6880
if err := cm.Parse(oob); err != nil {
69-
return "", err
81+
return nil, err
7082
}
7183

72-
Interface, err := net.InterfaceByIndex(cm.IfIndex)
84+
iface, err := net.InterfaceByIndex(cm.IfIndex)
7385

7486
if err != nil {
75-
return "", err
87+
return nil, err
7688
}
7789

78-
return Interface.Name, nil
90+
return iface, nil
7991
}
8092

81-
func (a *App) findPoolByInterface(Interface string) (*Pool, error) {
82-
pool, ok := a.interface2Pool[Interface]
83-
if !ok {
84-
return nil, errors.New("Unconfigured")
93+
func (a *App) findPoolByInterface(iface *net.Interface) (*Pool, error) {
94+
addrs, err := iface.Addrs()
95+
96+
if err != nil {
97+
return nil, err
8598
}
86-
return pool, nil
99+
100+
for _, addr := range addrs {
101+
// FIXME: should we verify addr.Network() is first "ip+net" ?
102+
_, ipnet, err := net.ParseCIDR(addr.String())
103+
if err != nil {
104+
continue
105+
}
106+
107+
hipnet, err := IpNet2HashableIpNet(ipnet)
108+
if err != nil {
109+
continue
110+
}
111+
112+
if pool, ok := a.ipnet2pool[hipnet]; ok {
113+
return pool, nil
114+
}
115+
}
116+
117+
return nil, errors.New("Not found")
87118
}
88119

89120
func (a *App) DispatchMessage(myBuf, myOob []byte, remote *net.UDPAddr, localSocket *net.UDPConn) {
90-
Interface, err := a.oObToInterface(myOob)
121+
iface, err := a.oObToInterface(myOob)
91122
if err != nil {
92123
log.Printf("Failed parsing interface out of OOB: %v", err)
93124
return
94125
}
95126

96-
pool, err := a.findPoolByInterface(Interface)
127+
if _, ok := a.interfaces[iface.Name]; !ok {
128+
log.Printf("Ignoring DHCP traffic on unconfigured interface %v", iface.Name)
129+
return
130+
}
131+
132+
pool, err := a.findPoolByInterface(iface)
97133
if err != nil {
98-
log.Printf("Ignoring DHCP traffic on unconfigured interface %v", Interface)
134+
log.Printf("Can't find pool based on IPs bound to %v", iface.Name)
99135
return
100136
}
101137

conf.go

+5-6
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ import (
1010
)
1111

1212
type PoolConf struct {
13-
Name string `yaml:"name"`
14-
Interface string `yaml:"interface"`
15-
MyIp string `yaml:"myip"`
13+
Name string `yaml:"name"`
14+
MyIp string `yaml:"myip"`
1615

1716
Network string `yaml:"network"`
1817
Subnet string `yaml:"subnet"`
@@ -43,7 +42,6 @@ func (pc PoolConf) ToPool() (*Pool, error) {
4342
pool.End = net.ParseIP(pc.End)
4443
pool.MyIp = IpToFixedV4(net.ParseIP(pc.MyIp))
4544
pool.LeaseTime = time.Second * time.Duration(pc.LeaseTime)
46-
pool.Interface = pc.Interface
4745

4846
pool.Broadcast = calcBroadcast(pool.Network, pool.Netmask)
4947

@@ -65,8 +63,9 @@ type HostConf struct {
6563
}
6664

6765
type Conf struct {
68-
Pools []PoolConf `yaml:"pools"`
69-
Leasedir string `yaml:"leasedir"`
66+
Pools []PoolConf `yaml:"pools"`
67+
Leasedir string `yaml:"leasedir"`
68+
Interfaces []string `yaml:"interfaces`
7069
}
7170

7271
func ParseConf(path string) (*Conf, error) {

conf.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
pools:
22
- name: vm testing
3-
interface: eth1
43
network: 172.17.0.0
54
mask: 255.255.255.0
65
start: 172.17.0.100
@@ -11,6 +10,7 @@ pools:
1110
dns: [ 1.1.1.1, 8.8.8.8 ]
1211

1312
leasedir: .
13+
interfaces: [ eth1 ]
1414

1515
# The following makes more sense
1616
#leasedir: /var/lib/godhcpd

header.go

-70
Original file line numberDiff line numberDiff line change
@@ -6,79 +6,9 @@ package main
66
import (
77
"bytes"
88
"encoding/binary"
9-
"errors"
109
"fmt"
11-
"net"
12-
"strconv"
13-
"strings"
1410
)
1511

16-
//
17-
// Fixed-width big-endian integer to keep track of IPv4 IPs, as they appear over the wire
18-
//
19-
type FixedV4 uint32
20-
21-
func (v4 FixedV4) String() string {
22-
ip := long2ip(uint32(v4))
23-
return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
24-
}
25-
26-
func (v4 FixedV4) Bytes() []byte {
27-
return long2bytes(uint32(v4))
28-
}
29-
30-
func (v4 FixedV4) Long() uint32 {
31-
return uint32(v4)
32-
}
33-
34-
func IpToFixedV4(ip net.IP) FixedV4 {
35-
b := ip.To4()
36-
return FixedV4(binary.BigEndian.Uint32(b[0:4]))
37-
}
38-
39-
func BytesToFixedV4(b []byte) (FixedV4, error) {
40-
if len(b) != 4 {
41-
return 0, errors.New("Incorrect length")
42-
}
43-
return FixedV4(binary.BigEndian.Uint32(b[0:4])), nil
44-
}
45-
46-
//
47-
// Fixed-width byte array for mac addresses, as they appear over the wire
48-
//
49-
type MacAddress [6]byte
50-
51-
func (m MacAddress) String() string {
52-
return fmt.Sprintf("%x:%x:%x:%x:%x:%x", m[0], m[1], m[2], m[3], m[4], m[5])
53-
}
54-
55-
func StrToMac(str string) MacAddress {
56-
var m MacAddress
57-
58-
parts := strings.Split(str, ":")
59-
if len(parts) != 6 {
60-
return m
61-
}
62-
63-
parsePart := func(s string) byte {
64-
if n, err := strconv.ParseUint(s, 16, 8); err == nil {
65-
return byte(n)
66-
}
67-
return 0
68-
}
69-
70-
m = MacAddress{
71-
parsePart(parts[0]),
72-
parsePart(parts[1]),
73-
parsePart(parts[2]),
74-
parsePart(parts[3]),
75-
parsePart(parts[4]),
76-
parsePart(parts[5]),
77-
}
78-
79-
return m
80-
}
81-
8212
//
8313
// Header of a DHCP payload
8414
//

main.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ func main() {
2929

3030
app := NewApp()
3131

32-
err = app.InitPools(conf)
32+
err = app.InitConf(conf)
3333

3434
if err != nil {
35-
log.Fatalf("Failed initializing pools: %v", err)
35+
log.Fatalf("Failed initializing: %v", err)
3636
}
3737

3838
addr := net.UDPAddr{

pool.go

-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ type Pool struct {
3535
Router []net.IP
3636
Dns []net.IP
3737
LeaseTime time.Duration
38-
Interface string
3938
Persistence Persistence
4039

4140
leasesByMac map[MacAddress]*Lease

0 commit comments

Comments
 (0)