wip
This commit is contained in:
354
dhcpd/v4.go
354
dhcpd/v4.go
@@ -1,13 +1,16 @@
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
||||
"github.com/krolaw/dhcp4"
|
||||
"github.com/sparrc/go-ping"
|
||||
)
|
||||
|
||||
// V4Server - DHCPv4 server
|
||||
@@ -23,44 +26,326 @@ type V4Server struct {
|
||||
|
||||
// V4ServerConf - server configuration
|
||||
type V4ServerConf struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
// RangeStart string `yaml:"range_start"`
|
||||
LeaseDuration uint32 `yaml:"lease_duration"` // in seconds
|
||||
Enabled bool `json:"enabled" yaml:"enabled"`
|
||||
InterfaceName string `json:"interface_name" yaml:"interface_name"` // eth0, en0 and so on
|
||||
GatewayIP string `json:"gateway_ip" yaml:"gateway_ip"`
|
||||
SubnetMask string `json:"subnet_mask" yaml:"subnet_mask"`
|
||||
RangeStart string `json:"range_start" yaml:"range_start"`
|
||||
RangeEnd string `json:"range_end" yaml:"range_end"`
|
||||
LeaseDuration uint32 `json:"lease_duration" yaml:"lease_duration"` // in seconds
|
||||
|
||||
// ipStart net.IP
|
||||
leaseTime time.Duration
|
||||
// dnsIPAddrs []net.IP // IPv6 addresses to return to DHCP clients as DNS server addresses
|
||||
// sid dhcpv6.Duid
|
||||
// IP conflict detector: time (ms) to wait for ICMP reply.
|
||||
// 0: disable
|
||||
ICMPTimeout uint32 `json:"icmp_timeout_msec" yaml:"icmp_timeout_msec"`
|
||||
|
||||
// notify func(uint32)
|
||||
ipStart net.IP
|
||||
ipStop net.IP
|
||||
leaseTime time.Duration
|
||||
dnsIPAddrs []net.IP // IPv4 addresses to return to DHCP clients as DNS server addresses
|
||||
|
||||
notify func(uint32)
|
||||
}
|
||||
|
||||
// WriteDiskConfig - write configuration
|
||||
func (s *V4Server) WriteDiskConfig(c *V4ServerConf) {
|
||||
*c = s.conf
|
||||
}
|
||||
|
||||
func ipInRange(start, stop, ip net.IP) bool {
|
||||
if len(start) != len(stop) ||
|
||||
len(start) != len(ip) {
|
||||
return false
|
||||
}
|
||||
// return dhcp4.IPInRange(start, stop, ip)
|
||||
return false
|
||||
}
|
||||
|
||||
// ResetLeases - reset leases
|
||||
func (s *V4Server) ResetLeases(ll []*Lease) {
|
||||
s.leases = nil
|
||||
s.IPpool = make(map[[4]byte]net.HardwareAddr)
|
||||
for _, l := range ll {
|
||||
|
||||
if l.Expiry.Unix() != leaseExpireStatic &&
|
||||
!ipInRange(s.conf.ipStart, s.conf.ipStop, l.IP) {
|
||||
|
||||
log.Tracef("DHCPv4: skipping a lease with IP %v: not within current IP range", l.IP)
|
||||
continue
|
||||
}
|
||||
|
||||
s.leases = append(s.leases, l)
|
||||
s.reserveIP(l.IP, l.HWAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// GetLeases returns the list of current DHCP leases (thread-safe)
|
||||
func (s *V4Server) GetLeases(flags int) []Lease {
|
||||
var result []Lease
|
||||
now := time.Now().Unix()
|
||||
s.leasesLock.Lock()
|
||||
for _, lease := range s.leases {
|
||||
if ((flags&LeasesDynamic) != 0 && lease.Expiry.Unix() > now) ||
|
||||
((flags&LeasesStatic) != 0 && lease.Expiry.Unix() == leaseExpireStatic) {
|
||||
result = append(result, *lease)
|
||||
}
|
||||
}
|
||||
s.leasesLock.Unlock()
|
||||
return result
|
||||
}
|
||||
|
||||
// FindMACbyIP4 - find a MAC address by IP address in the currently active DHCP leases
|
||||
func (s *V4Server) FindMACbyIP4(ip net.IP) net.HardwareAddr {
|
||||
now := time.Now().Unix()
|
||||
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
ip4 := ip.To4()
|
||||
if ip4 == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, l := range s.leases {
|
||||
if l.IP.Equal(ip4) {
|
||||
unix := l.Expiry.Unix()
|
||||
if unix > now || unix == leaseExpireStatic {
|
||||
return l.HWAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *V4Server) reserveIP(ip net.IP, hwaddr net.HardwareAddr) {
|
||||
rawIP := []byte(ip)
|
||||
IP4 := [4]byte{rawIP[0], rawIP[1], rawIP[2], rawIP[3]}
|
||||
s.IPpool[IP4] = hwaddr
|
||||
}
|
||||
|
||||
func (s *V4Server) unreserveIP(ip net.IP) {
|
||||
rawIP := []byte(ip)
|
||||
IP4 := [4]byte{rawIP[0], rawIP[1], rawIP[2], rawIP[3]}
|
||||
delete(s.IPpool, IP4)
|
||||
}
|
||||
|
||||
func (s *V4Server) findReservedHWaddr(ip net.IP) net.HardwareAddr {
|
||||
rawIP := []byte(ip)
|
||||
IP4 := [4]byte{rawIP[0], rawIP[1], rawIP[2], rawIP[3]}
|
||||
return s.IPpool[IP4]
|
||||
}
|
||||
|
||||
// Add the specified IP to the black list for a time period
|
||||
func (s *V4Server) blacklistLease(lease *Lease) {
|
||||
hw := make(net.HardwareAddr, 6)
|
||||
s.leasesLock.Lock()
|
||||
s.reserveIP(lease.IP, hw)
|
||||
lease.HWAddr = hw
|
||||
lease.Hostname = ""
|
||||
lease.Expiry = time.Now().Add(s.conf.leaseTime)
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
s.leasesLock.Unlock()
|
||||
s.conf.notify(LeaseChangedBlacklisted)
|
||||
}
|
||||
|
||||
// Remove a dynamic lease by IP address
|
||||
func (s *V4Server) rmDynamicLeaseWithIP(ip net.IP) error {
|
||||
var newLeases []*Lease
|
||||
for _, lease := range s.leases {
|
||||
if net.IP.Equal(lease.IP.To4(), ip) {
|
||||
if lease.Expiry.Unix() == leaseExpireStatic {
|
||||
return fmt.Errorf("static lease with the same IP already exists")
|
||||
}
|
||||
continue
|
||||
}
|
||||
newLeases = append(newLeases, lease)
|
||||
}
|
||||
s.leases = newLeases
|
||||
s.unreserveIP(ip)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove a dynamic lease by IP address
|
||||
func (s *V4Server) rmDynamicLeaseWithMAC(mac net.HardwareAddr) error {
|
||||
var newLeases []*Lease
|
||||
for _, lease := range s.leases {
|
||||
if bytes.Equal(lease.HWAddr, mac) {
|
||||
if lease.Expiry.Unix() == leaseExpireStatic {
|
||||
return fmt.Errorf("static lease with the same IP already exists")
|
||||
}
|
||||
s.unreserveIP(lease.IP)
|
||||
continue
|
||||
}
|
||||
newLeases = append(newLeases, lease)
|
||||
}
|
||||
s.leases = newLeases
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove a lease
|
||||
func (s *V4Server) rmLease(l Lease) error {
|
||||
var newLeases []*Lease
|
||||
for _, lease := range s.leases {
|
||||
if net.IP.Equal(lease.IP.To4(), l.IP) {
|
||||
if !bytes.Equal(lease.HWAddr, l.HWAddr) ||
|
||||
lease.Hostname != l.Hostname {
|
||||
return fmt.Errorf("Lease not found")
|
||||
}
|
||||
continue
|
||||
}
|
||||
newLeases = append(newLeases, lease)
|
||||
}
|
||||
s.leases = newLeases
|
||||
s.unreserveIP(l.IP)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddStaticLease adds a static lease (thread-safe)
|
||||
func (s *V4Server) AddStaticLease(l Lease) error {
|
||||
if len(l.IP) != 4 {
|
||||
return fmt.Errorf("invalid IP")
|
||||
}
|
||||
if len(l.HWAddr) != 6 {
|
||||
return fmt.Errorf("invalid MAC")
|
||||
}
|
||||
l.Expiry = time.Unix(leaseExpireStatic, 0)
|
||||
|
||||
s.leasesLock.Lock()
|
||||
|
||||
if s.findReservedHWaddr(l.IP) != nil {
|
||||
err := s.rmDynamicLeaseWithIP(l.IP)
|
||||
if err != nil {
|
||||
s.leasesLock.Unlock()
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := s.rmDynamicLeaseWithMAC(l.HWAddr)
|
||||
if err != nil {
|
||||
s.leasesLock.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.leases = append(s.leases, &l)
|
||||
s.reserveIP(l.IP, l.HWAddr)
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
s.leasesLock.Unlock()
|
||||
s.conf.notify(LeaseChangedAddedStatic)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveStaticLease removes a static lease (thread-safe)
|
||||
func (s *V4Server) RemoveStaticLease(l Lease) error {
|
||||
if len(l.IP) != 4 {
|
||||
return fmt.Errorf("invalid IP")
|
||||
}
|
||||
if len(l.HWAddr) != 6 {
|
||||
return fmt.Errorf("invalid MAC")
|
||||
}
|
||||
|
||||
s.leasesLock.Lock()
|
||||
|
||||
if s.findReservedHWaddr(l.IP) == nil {
|
||||
s.leasesLock.Unlock()
|
||||
return fmt.Errorf("lease not found")
|
||||
}
|
||||
|
||||
err := s.rmLease(l)
|
||||
if err != nil {
|
||||
s.leasesLock.Unlock()
|
||||
return err
|
||||
}
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
s.leasesLock.Unlock()
|
||||
s.conf.notify(LeaseChangedRemovedStatic)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send ICMP to the specified machine
|
||||
// Return TRUE if it doesn't reply, which probably means that the IP is available
|
||||
func (s *V4Server) addrAvailable(target net.IP) bool {
|
||||
|
||||
if s.conf.ICMPTimeout == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
pinger, err := ping.NewPinger(target.String())
|
||||
if err != nil {
|
||||
log.Error("ping.NewPinger(): %v", err)
|
||||
return true
|
||||
}
|
||||
|
||||
pinger.SetPrivileged(true)
|
||||
pinger.Timeout = time.Duration(s.conf.ICMPTimeout) * time.Millisecond
|
||||
pinger.Count = 1
|
||||
reply := false
|
||||
pinger.OnRecv = func(pkt *ping.Packet) {
|
||||
// log.Tracef("Received ICMP Reply from %v", target)
|
||||
reply = true
|
||||
}
|
||||
log.Tracef("Sending ICMP Echo to %v", target)
|
||||
pinger.Run()
|
||||
|
||||
if reply {
|
||||
log.Info("DHCP: IP conflict: %v is already used by another device", target)
|
||||
return false
|
||||
}
|
||||
|
||||
log.Tracef("ICMP procedure is complete: %v", target)
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *V4Server) packetHandler(conn net.PacketConn, peer net.Addr, m *dhcpv4.DHCPv4) {
|
||||
}
|
||||
|
||||
// Get IPv4 address list
|
||||
func getIfaceIPv4(iface net.Interface) []net.IP {
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var res []net.IP
|
||||
for _, a := range addrs {
|
||||
ipnet, ok := a.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if ipnet.IP.To4() != nil {
|
||||
res = append(res, ipnet.IP)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Start - start server
|
||||
func (s *V4Server) Start(iface net.Interface) error {
|
||||
if s.conn != nil {
|
||||
_ = s.closeConn()
|
||||
}
|
||||
|
||||
c, err := newFilterConn(iface, ":67") // it has to be bound to 0.0.0.0:67, otherwise it won't see DHCP discover/request packets
|
||||
func (s *V4Server) Start() error {
|
||||
iface, err := net.InterfaceByName(s.conf.InterfaceName)
|
||||
if err != nil {
|
||||
return wrapErrPrint(err, "Couldn't start listening socket on 0.0.0.0:67")
|
||||
return wrapErrPrint(err, "DHCPv4: Couldn't find interface by name %s", s.conf.InterfaceName)
|
||||
}
|
||||
log.Info("DHCP: listening on 0.0.0.0:67")
|
||||
|
||||
s.conn = c
|
||||
s.cond = sync.NewCond(&s.mutex)
|
||||
log.Debug("DHCPv4: starting...")
|
||||
s.conf.dnsIPAddrs = getIfaceIPv4(*iface)
|
||||
if len(s.conf.dnsIPAddrs) == 0 {
|
||||
return fmt.Errorf("DHCPv4: no IPv4 address for interface %s", iface.Name)
|
||||
}
|
||||
|
||||
laddr := &net.UDPAddr{
|
||||
IP: net.ParseIP("0.0.0.0"),
|
||||
Port: dhcpv4.ServerPort,
|
||||
}
|
||||
|
||||
server, err := server4.NewServer(iface.Name, laddr, s.packetHandler, server4.WithDebugLogger())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("DHCPv4: listening")
|
||||
|
||||
s.running = true
|
||||
go func() {
|
||||
// operate on c instead of c.conn because c.conn can change over time
|
||||
err := dhcp4.Serve(c, s)
|
||||
if err != nil && !s.stopping {
|
||||
log.Printf("dhcp4.Serve() returned with error: %s", err)
|
||||
}
|
||||
_ = c.Close() // in case Serve() exits for other reason than listening socket closure
|
||||
s.running = false
|
||||
s.cond.Signal()
|
||||
err = server.Serve()
|
||||
log.Error("DHCPv4: %s", err)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -74,6 +359,15 @@ func (s *V4Server) Reset() {
|
||||
|
||||
// Stop - stop server
|
||||
func (s *V4Server) Stop() {
|
||||
if s.srv == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := s.srv.Close()
|
||||
if err != nil {
|
||||
log.Error("DHCPv4: srv.Close: %s", err)
|
||||
}
|
||||
// now server.Serve() will return
|
||||
}
|
||||
|
||||
// Create DHCPv6 server
|
||||
@@ -90,8 +384,10 @@ func v4Create(conf V4ServerConf) (*V4Server, error) {
|
||||
// return nil, fmt.Errorf("DHCPv6: invalid range-start IP: %s", conf.RangeStart)
|
||||
// }
|
||||
|
||||
// s.conf.ICMPTimeout = 1000
|
||||
|
||||
if conf.LeaseDuration == 0 {
|
||||
s.conf.leaseTime = time.Hour * 2
|
||||
s.conf.leaseTime = time.Hour * 24
|
||||
s.conf.LeaseDuration = uint32(s.conf.leaseTime.Seconds())
|
||||
} else {
|
||||
s.conf.leaseTime = time.Second * time.Duration(conf.LeaseDuration)
|
||||
|
||||
Reference in New Issue
Block a user