mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2025-03-21 09:58:21 +03:00
356 lines
7.7 KiB
Go
356 lines
7.7 KiB
Go
package dhcpd
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/AdguardTeam/golibs/log"
|
|
"github.com/insomniacslk/dhcp/dhcpv6"
|
|
"github.com/insomniacslk/dhcp/dhcpv6/server6"
|
|
"github.com/insomniacslk/dhcp/iana"
|
|
)
|
|
|
|
const valIAID = "ADGH"
|
|
|
|
// V6Server - DHCPv6 server
|
|
type V6Server struct {
|
|
s4 *Server // for dbStore()
|
|
srv *server6.Server
|
|
leases []*Lease
|
|
leasesLock sync.Mutex
|
|
|
|
conf V6ServerConf
|
|
}
|
|
|
|
// V6ServerConf - server configuration
|
|
type V6ServerConf struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
RangeStart string `yaml:"range_start"`
|
|
LeaseDuration uint32 `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
|
|
}
|
|
|
|
// WriteDiskConfig - write configuration
|
|
func (s *V6Server) WriteDiskConfig(c *V6ServerConf) {
|
|
*c = s.conf
|
|
}
|
|
|
|
// GetLeases - get current leases
|
|
func (s *V6Server) GetLeases(flags int) []Lease {
|
|
var result []Lease
|
|
s.leasesLock.Lock()
|
|
for _, lease := range s.leases {
|
|
if (flags&LeasesStatic) != 0 && lease.Expiry.Unix() == leaseExpireStatic {
|
|
result = append(result, *lease)
|
|
}
|
|
}
|
|
s.leasesLock.Unlock()
|
|
return result
|
|
}
|
|
|
|
// AddStaticLease - add a static lease
|
|
func (s *V6Server) AddStaticLease(l Lease) error {
|
|
if len(l.IP) != 16 {
|
|
return fmt.Errorf("invalid IP")
|
|
}
|
|
if len(l.HWAddr) != 6 {
|
|
return fmt.Errorf("invalid MAC")
|
|
}
|
|
|
|
l.Expiry = time.Unix(leaseExpireStatic, 0)
|
|
|
|
s.leasesLock.Lock()
|
|
err := s.addLease(l)
|
|
if err != nil {
|
|
s.leasesLock.Unlock()
|
|
return err
|
|
}
|
|
s.s4.dbStore()
|
|
s.leasesLock.Unlock()
|
|
// s.notify(LeaseChangedAddedStatic)
|
|
return nil
|
|
}
|
|
|
|
// RemoveStaticLease - remove a static lease
|
|
func (s *V6Server) RemoveStaticLease(l Lease) error {
|
|
if len(l.IP) != 16 {
|
|
return fmt.Errorf("invalid IP")
|
|
}
|
|
if len(l.HWAddr) != 6 {
|
|
return fmt.Errorf("invalid MAC")
|
|
}
|
|
|
|
s.leasesLock.Lock()
|
|
err := s.rmLease(l)
|
|
if err != nil {
|
|
s.leasesLock.Unlock()
|
|
return err
|
|
}
|
|
s.s4.dbStore()
|
|
s.leasesLock.Unlock()
|
|
// s.notify(LeaseChangedRemovedStatic)
|
|
return nil
|
|
}
|
|
|
|
// Add a lease
|
|
func (s *V6Server) addLease(l Lease) error {
|
|
s.leases = append(s.leases, &l)
|
|
return nil
|
|
}
|
|
|
|
// 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) {
|
|
return fmt.Errorf("Lease not found")
|
|
}
|
|
continue
|
|
}
|
|
newLeases = append(newLeases, lease)
|
|
}
|
|
|
|
if len(newLeases) == len(s.leases) {
|
|
return fmt.Errorf("Lease not found: %s", l.IP)
|
|
}
|
|
|
|
s.leases = newLeases
|
|
return nil
|
|
}
|
|
|
|
func (s *V6Server) findLease(mac net.HardwareAddr) *Lease {
|
|
s.leasesLock.Lock()
|
|
defer s.leasesLock.Unlock()
|
|
|
|
for i := range s.leases {
|
|
if bytes.Equal(mac, s.leases[i].HWAddr) {
|
|
return s.leases[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *V6Server) v6Process(req dhcpv6.DHCPv6, resp dhcpv6.DHCPv6) {
|
|
mac, err := dhcpv6.ExtractMAC(req)
|
|
if err != nil {
|
|
log.Debug("DHCPv6: dhcpv6.ExtractMAC: %s", err)
|
|
return
|
|
}
|
|
|
|
lease := s.findLease(mac)
|
|
if lease == nil {
|
|
log.Debug("DHCPv6: no lease for: %s", mac)
|
|
return
|
|
}
|
|
|
|
oia := &dhcpv6.OptIANA{}
|
|
copy(oia.IaId[:], []byte(valIAID))
|
|
oia.Options = dhcpv6.IdentityOptions{Options: []dhcpv6.Option{
|
|
&dhcpv6.OptIAAddress{
|
|
IPv6Addr: lease.IP,
|
|
PreferredLifetime: s.conf.leaseTime,
|
|
ValidLifetime: s.conf.leaseTime,
|
|
},
|
|
}}
|
|
resp.AddOption(oia)
|
|
|
|
msg, _ := req.GetInnerMessage()
|
|
if msg.IsOptionRequested(dhcpv6.OptionDNSRecursiveNameServer) {
|
|
resp.UpdateOption(dhcpv6.OptDNS(s.conf.dnsIPAddrs...))
|
|
}
|
|
}
|
|
|
|
// 1.
|
|
// fe80::* (client) --(Solicit + ClientID+IANA())-> ff02::1:2
|
|
// server -(Advertise + ClientID+ServerID+IANA(IAAddress)> fe80::*
|
|
// fe80::* --(Request + ClientID+ServerID+IANA(IAAddress))-> ff02::1:2
|
|
// server -(Reply + ClientID+ServerID+IANA(IAAddress)+DNS)> fe80::*
|
|
//
|
|
// 2.
|
|
// fe80::* --(Confirm|Renew|Rebind + ClientID+IANA(IAAddress))-> ff02::1:2
|
|
// server -(Reply + ClientID+ServerID+IANA(IAAddress)+DNS)> fe80::*
|
|
//
|
|
// 3.
|
|
// fe80::* --(Release + ClientID+ServerID+IANA(IAAddress))-> ff02::1:2
|
|
func (s *V6Server) packetHandler(conn net.PacketConn, peer net.Addr, req dhcpv6.DHCPv6) {
|
|
msg, err := req.GetInnerMessage()
|
|
if err != nil {
|
|
log.Error("DHCPv6: %s", err)
|
|
return
|
|
}
|
|
|
|
log.Debug("DHCPv6: received: %s", req.Summary())
|
|
|
|
if msg.GetOneOption(dhcpv6.OptionClientID) == nil {
|
|
log.Error("DHCPv6: no ClientID option in request")
|
|
return
|
|
}
|
|
|
|
// ServerID policy
|
|
sid := msg.Options.ServerID()
|
|
switch msg.Type() {
|
|
case dhcpv6.MessageTypeSolicit,
|
|
dhcpv6.MessageTypeConfirm,
|
|
dhcpv6.MessageTypeRebind:
|
|
|
|
if sid != nil {
|
|
log.Debug("DHCPv6: drop packet: ServerID option in message %s", msg.Type().String)
|
|
return
|
|
}
|
|
|
|
case dhcpv6.MessageTypeRequest,
|
|
dhcpv6.MessageTypeRenew,
|
|
dhcpv6.MessageTypeRelease,
|
|
dhcpv6.MessageTypeDecline:
|
|
|
|
if sid == nil {
|
|
log.Debug("DHCPv6: drop packet: no ServerID option in message %s", msg.Type().String)
|
|
return
|
|
}
|
|
if !sid.Equal(s.conf.sid) {
|
|
log.Debug("DHCPv6: drop packet: mismatched ServerID option in message %s: %s",
|
|
msg.Type().String, sid.String())
|
|
return
|
|
}
|
|
}
|
|
|
|
var resp dhcpv6.DHCPv6
|
|
|
|
switch msg.Type() {
|
|
case dhcpv6.MessageTypeSolicit:
|
|
if msg.GetOneOption(dhcpv6.OptionRapidCommit) != nil {
|
|
resp, err = dhcpv6.NewReplyFromMessage(msg)
|
|
} else {
|
|
resp, err = dhcpv6.NewAdvertiseFromSolicit(msg)
|
|
}
|
|
|
|
case dhcpv6.MessageTypeRequest,
|
|
dhcpv6.MessageTypeConfirm,
|
|
dhcpv6.MessageTypeRenew,
|
|
dhcpv6.MessageTypeRebind,
|
|
dhcpv6.MessageTypeRelease,
|
|
dhcpv6.MessageTypeInformationRequest:
|
|
resp, err = dhcpv6.NewReplyFromMessage(msg)
|
|
|
|
default:
|
|
log.Error("DHCPv6: message type %d not supported", msg.Type())
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
log.Error("DHCPv6: %s", err)
|
|
return
|
|
}
|
|
|
|
resp.AddOption(dhcpv6.OptServerID(s.conf.sid))
|
|
|
|
s.v6Process(req, resp)
|
|
|
|
log.Debug("DHCPv6: sending: %s", resp.Summary())
|
|
|
|
_, err = conn.WriteTo(resp.ToBytes(), peer)
|
|
if err != nil {
|
|
log.Error("DHCPv6: conn.Write to %s failed: %s", peer, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func getIfaceIPv6(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 *V6Server) Start(iface net.Interface) error {
|
|
if !s.conf.Enabled {
|
|
return nil
|
|
}
|
|
|
|
log.Debug("DHCPv6: starting...")
|
|
s.conf.dnsIPAddrs = getIfaceIPv6(iface)
|
|
if len(s.conf.dnsIPAddrs) == 0 {
|
|
return fmt.Errorf("DHCPv6: no IPv6 address for interface %s", iface.Name)
|
|
}
|
|
|
|
if len(iface.HardwareAddr) != 6 {
|
|
return fmt.Errorf("DHCPv6: invalid MAC %s", iface.HardwareAddr)
|
|
}
|
|
s.conf.sid = dhcpv6.Duid{
|
|
Type: dhcpv6.DUID_LLT,
|
|
HwType: iana.HWTypeEthernet,
|
|
LinkLayerAddr: iface.HardwareAddr,
|
|
}
|
|
|
|
laddr := &net.UDPAddr{
|
|
IP: net.ParseIP("::"),
|
|
Port: dhcpv6.DefaultServerPort,
|
|
}
|
|
server, err := server6.NewServer(iface.Name, laddr, s.packetHandler, server6.WithDebugLogger())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
err = server.Serve()
|
|
log.Error("DHCPv6: %s", err)
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
// Stop - stop server
|
|
func (s *V6Server) Stop() {
|
|
if s.srv == nil {
|
|
return
|
|
}
|
|
|
|
err := s.srv.Close()
|
|
if err != nil {
|
|
log.Error("DHCPv6: srv.Close: %s", err)
|
|
}
|
|
// now server.Serve() will return
|
|
}
|
|
|
|
func v6Create(conf V6ServerConf) (*V6Server, error) {
|
|
s := &V6Server{}
|
|
s.conf = conf
|
|
|
|
if !conf.Enabled {
|
|
return s, nil
|
|
}
|
|
|
|
s.conf.ipStart = net.ParseIP(conf.RangeStart)
|
|
if s.conf.ipStart == nil {
|
|
return nil, fmt.Errorf("DHCPv6: invalid range-start IP: %s", conf.RangeStart)
|
|
}
|
|
|
|
if conf.LeaseDuration == 0 {
|
|
s.conf.leaseTime = time.Hour * 2
|
|
s.conf.LeaseDuration = uint32(s.conf.leaseTime.Seconds())
|
|
} else {
|
|
s.conf.leaseTime = time.Second * time.Duration(conf.LeaseDuration)
|
|
}
|
|
|
|
return s, nil
|
|
}
|