mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-12-15 19:31:45 +03:00
ea8d634f65
Merge in DNS/adguard-home from dhcpd-imp-code to master Squashed commit of the following: commit 413403c169bd3f6b5f2ed63b783d078dbb15e054 Merge: eed1838500fec990bc
Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri May 26 12:46:25 2023 +0300 Merge remote-tracking branch 'origin/master' into dhcpd-imp-code commit eed1838502add1e16e5d3ada03778f21913fd5e5 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri May 26 12:16:46 2023 +0300 dhcpd: imp docs commit fa4fe036f7b1f2b49201bf0b5b904f99989082f0 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Thu May 25 11:32:34 2023 +0300 all: lint script commit a4022b3d4bbfa709e5096397bbe64ea406c8a366 Merge: e08ff3a26cbc7985e7
Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Thu May 25 11:29:57 2023 +0300 Merge remote-tracking branch 'origin/master' into dhcpd-imp-code # Conflicts: # scripts/make/go-lint.sh commit e08ff3a26414e201d6e75608363db941fa2f5b39 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Wed May 24 15:43:11 2023 +0300 dhcpd: imp code commit 970b538f8ea94d3732d77bfb648e402a1d28ab06 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Wed May 24 15:40:36 2023 +0300 dhcpd: imp code commit 0e5916ddd7514af983e8557080d55d6aeb6fbbc0 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Wed May 24 15:37:17 2023 +0300 dhcpd: imp code commit e06a6c6031b232e76ae2be3e3b8fe1a2ffa715e0 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 16:40:09 2023 +0300 dhcpd: imp code commit eed4ff10ff1b29c71d70fb7978706efde89afee1 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 15:45:06 2023 +0300 all: lint script commit 87f84ace5f6f34dbc28befa8257d1d2492c5e0a4 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 15:44:23 2023 +0300 dhcpd: imp code commit a54c9929d51de1f1e6807d650fd08dd80ddbf147 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 14:29:42 2023 +0300 dhcpd: imp code commit 1bbea342f7f55587724aa9a29d9657e5ce75f5d8 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 14:12:09 2023 +0300 dhcpd: imp code commit 48fb4eff73683e799ddb3076afdcf7b067ca24b6 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 13:57:59 2023 +0300 dhcpd: imp code commit f6cd7fcb8d4c1c815a20875d777ea1eca2c8ea89 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 13:17:54 2023 +0300 dhcpd: imp code commit 2b91dc25bbaa16dba6d9389a4e2224cf91eb4554 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 12:57:46 2023 +0300 dhcpd: imp code commit 34f5dd58764080f6202fc9a1abd751a15dbf7090 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon May 22 15:31:39 2023 +0300 dhcpd: imp code commit 12ef0d225064a1b78adf7b2cfca21a8dba92ca8e Merge: 6b62a766524b41100c
Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon May 22 13:03:41 2023 +0300 Merge remote-tracking branch 'origin/master' into dhcpd-imp-code commit 6b62a7665720b85398d65a1926518a63e6bb6403 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon May 22 12:55:43 2023 +0300 dhcpd: imp code commit 18c5cdf0480fac7711282027a64d58704c75af5f Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon May 22 12:48:30 2023 +0300 dhcpd: imp code commit e7c1f4324cba3fe86cf56df6b971791a5a8790de Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon May 22 12:37:15 2023 +0300 dhcpd: imp code ... and 1 more commit
743 lines
16 KiB
Go
743 lines
16 KiB
Go
//go:build darwin || freebsd || linux || openbsd
|
|
|
|
package dhcpd
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
|
"github.com/AdguardTeam/golibs/errors"
|
|
"github.com/AdguardTeam/golibs/log"
|
|
"github.com/AdguardTeam/golibs/netutil"
|
|
"github.com/AdguardTeam/golibs/timeutil"
|
|
"github.com/insomniacslk/dhcp/dhcpv6"
|
|
"github.com/insomniacslk/dhcp/dhcpv6/server6"
|
|
"github.com/insomniacslk/dhcp/iana"
|
|
)
|
|
|
|
const valueIAID = "ADGH" // value for IANA.ID
|
|
|
|
// v6Server is a DHCPv6 server.
|
|
//
|
|
// TODO(a.garipov): Think about unifying this and v4Server.
|
|
type v6Server struct {
|
|
srv *server6.Server
|
|
leasesLock sync.Mutex
|
|
leases []*Lease
|
|
ipAddrs [256]byte
|
|
sid dhcpv6.DUID
|
|
|
|
ra raCtx // RA module
|
|
|
|
conf V6ServerConf
|
|
}
|
|
|
|
// WriteDiskConfig4 - write configuration
|
|
func (s *v6Server) WriteDiskConfig4(c *V4ServerConf) {
|
|
}
|
|
|
|
// WriteDiskConfig6 - write configuration
|
|
func (s *v6Server) WriteDiskConfig6(c *V6ServerConf) {
|
|
*c = s.conf
|
|
}
|
|
|
|
// Return TRUE if IP address is within range [start..0xff]
|
|
func ip6InRange(start, ip net.IP) bool {
|
|
if len(start) != 16 {
|
|
return false
|
|
}
|
|
//lint:ignore SA1021 TODO(e.burkov): Ignore this for now, think about
|
|
// using masks.
|
|
if !bytes.Equal(start[:15], ip[:15]) {
|
|
return false
|
|
}
|
|
return start[15] <= ip[15]
|
|
}
|
|
|
|
// ResetLeases resets leases.
|
|
func (s *v6Server) ResetLeases(leases []*Lease) (err error) {
|
|
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
|
|
|
|
s.leases = nil
|
|
for _, l := range leases {
|
|
ip := net.IP(l.IP.AsSlice())
|
|
if !l.IsStatic && !ip6InRange(s.conf.ipStart, ip) {
|
|
|
|
log.Debug("dhcpv6: skipping a lease with IP %v: not within current IP range", l.IP)
|
|
|
|
continue
|
|
}
|
|
|
|
s.addLease(l)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetLeases returns the list of current DHCP leases. It is safe for concurrent
|
|
// use.
|
|
func (s *v6Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) {
|
|
// The function shouldn't return nil value because zero-length slice
|
|
// behaves differently in cases like marshalling. Our front-end also
|
|
// requires non-nil value in the response.
|
|
leases = []*Lease{}
|
|
s.leasesLock.Lock()
|
|
for _, l := range s.leases {
|
|
if l.IsStatic {
|
|
if (flags & LeasesStatic) != 0 {
|
|
leases = append(leases, l.Clone())
|
|
}
|
|
} else {
|
|
if (flags & LeasesDynamic) != 0 {
|
|
leases = append(leases, l.Clone())
|
|
}
|
|
}
|
|
}
|
|
s.leasesLock.Unlock()
|
|
return leases
|
|
}
|
|
|
|
// getLeasesRef returns the actual leases slice. For internal use only.
|
|
func (s *v6Server) getLeasesRef() []*Lease {
|
|
return s.leases
|
|
}
|
|
|
|
// FindMACbyIP implements the [Interface] for *v6Server.
|
|
func (s *v6Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
|
now := time.Now()
|
|
|
|
s.leasesLock.Lock()
|
|
defer s.leasesLock.Unlock()
|
|
|
|
if !ip.Is6() {
|
|
return nil
|
|
}
|
|
|
|
for _, l := range s.leases {
|
|
if l.IP == ip {
|
|
if l.IsStatic || l.Expiry.After(now) {
|
|
return l.HWAddr
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Remove (swap) lease by index
|
|
func (s *v6Server) leaseRemoveSwapByIndex(i int) {
|
|
leaseIP := s.leases[i].IP.As16()
|
|
s.ipAddrs[leaseIP[15]] = 0
|
|
log.Debug("dhcpv6: removed lease %s", s.leases[i].HWAddr)
|
|
|
|
n := len(s.leases)
|
|
if i != n-1 {
|
|
s.leases[i] = s.leases[n-1] // swap with the last element
|
|
}
|
|
s.leases = s.leases[:n-1]
|
|
}
|
|
|
|
// Remove a dynamic lease with the same properties
|
|
// Return error if a static lease is found
|
|
func (s *v6Server) rmDynamicLease(lease *Lease) (err error) {
|
|
for i := 0; i < len(s.leases); i++ {
|
|
l := s.leases[i]
|
|
|
|
if bytes.Equal(l.HWAddr, lease.HWAddr) {
|
|
if l.IsStatic {
|
|
return fmt.Errorf("static lease already exists")
|
|
}
|
|
|
|
s.leaseRemoveSwapByIndex(i)
|
|
if i == len(s.leases) {
|
|
break
|
|
}
|
|
|
|
l = s.leases[i]
|
|
}
|
|
|
|
if l.IP == lease.IP {
|
|
if l.IsStatic {
|
|
return fmt.Errorf("static lease already exists")
|
|
}
|
|
|
|
s.leaseRemoveSwapByIndex(i)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddStaticLease adds a static lease. It is safe for concurrent use.
|
|
func (s *v6Server) AddStaticLease(l *Lease) (err error) {
|
|
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
|
|
|
|
if !l.IP.Is6() {
|
|
return fmt.Errorf("invalid IP")
|
|
}
|
|
|
|
err = netutil.ValidateMAC(l.HWAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("validating lease: %w", err)
|
|
}
|
|
|
|
l.IsStatic = true
|
|
|
|
s.leasesLock.Lock()
|
|
err = s.rmDynamicLease(l)
|
|
if err != nil {
|
|
s.leasesLock.Unlock()
|
|
|
|
return err
|
|
}
|
|
|
|
s.addLease(l)
|
|
s.conf.notify(LeaseChangedDBStore)
|
|
s.leasesLock.Unlock()
|
|
|
|
s.conf.notify(LeaseChangedAddedStatic)
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveStaticLease removes a static lease. It is safe for concurrent use.
|
|
func (s *v6Server) RemoveStaticLease(l *Lease) (err error) {
|
|
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
|
|
|
|
if !l.IP.Is6() {
|
|
return fmt.Errorf("invalid IP")
|
|
}
|
|
|
|
err = netutil.ValidateMAC(l.HWAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("validating lease: %w", err)
|
|
}
|
|
|
|
s.leasesLock.Lock()
|
|
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
|
|
}
|
|
|
|
// Add a lease
|
|
func (s *v6Server) addLease(l *Lease) {
|
|
s.leases = append(s.leases, l)
|
|
ip := l.IP.As16()
|
|
s.ipAddrs[ip[15]] = 1
|
|
log.Debug("dhcpv6: added lease %s <-> %s", l.IP, l.HWAddr)
|
|
}
|
|
|
|
// Remove a lease with the same properties
|
|
func (s *v6Server) rmLease(lease *Lease) (err error) {
|
|
for i, l := range s.leases {
|
|
if l.IP == lease.IP {
|
|
if !bytes.Equal(l.HWAddr, lease.HWAddr) ||
|
|
l.Hostname != lease.Hostname {
|
|
return fmt.Errorf("lease not found")
|
|
}
|
|
|
|
s.leaseRemoveSwapByIndex(i)
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("lease not found")
|
|
}
|
|
|
|
// Find lease by MAC
|
|
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
|
|
}
|
|
|
|
// Find an expired lease and return its index or -1
|
|
func (s *v6Server) findExpiredLease() int {
|
|
now := time.Now().Unix()
|
|
for i, lease := range s.leases {
|
|
if !lease.IsStatic && lease.Expiry.Unix() <= now {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// Get next free IP
|
|
func (s *v6Server) findFreeIP() net.IP {
|
|
for i := s.conf.ipStart[15]; ; i++ {
|
|
if s.ipAddrs[i] == 0 {
|
|
ip := make([]byte, 16)
|
|
copy(ip, s.conf.ipStart)
|
|
ip[15] = i
|
|
return ip
|
|
}
|
|
if i == 0xff {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Reserve lease for MAC
|
|
func (s *v6Server) reserveLease(mac net.HardwareAddr) *Lease {
|
|
l := Lease{
|
|
HWAddr: make([]byte, len(mac)),
|
|
}
|
|
|
|
copy(l.HWAddr, mac)
|
|
|
|
s.leasesLock.Lock()
|
|
defer s.leasesLock.Unlock()
|
|
|
|
ip := s.findFreeIP()
|
|
if ip == nil {
|
|
i := s.findExpiredLease()
|
|
if i < 0 {
|
|
return nil
|
|
}
|
|
|
|
copy(s.leases[i].HWAddr, mac)
|
|
|
|
return s.leases[i]
|
|
}
|
|
|
|
netIP, ok := netip.AddrFromSlice(ip)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
l.IP = netIP
|
|
|
|
s.addLease(&l)
|
|
|
|
return &l
|
|
}
|
|
|
|
func (s *v6Server) commitDynamicLease(l *Lease) {
|
|
l.Expiry = time.Now().Add(s.conf.leaseTime)
|
|
|
|
s.leasesLock.Lock()
|
|
s.conf.notify(LeaseChangedDBStore)
|
|
s.leasesLock.Unlock()
|
|
s.conf.notify(LeaseChangedAdded)
|
|
}
|
|
|
|
// Check Client ID
|
|
func (s *v6Server) checkCID(msg *dhcpv6.Message) error {
|
|
if msg.Options.ClientID() == nil {
|
|
return fmt.Errorf("dhcpv6: no ClientID option in request")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Check ServerID policy
|
|
func (s *v6Server) checkSID(msg *dhcpv6.Message) error {
|
|
sid := msg.Options.ServerID()
|
|
|
|
switch msg.Type() {
|
|
case dhcpv6.MessageTypeSolicit,
|
|
dhcpv6.MessageTypeConfirm,
|
|
dhcpv6.MessageTypeRebind:
|
|
|
|
if sid != nil {
|
|
return fmt.Errorf("dhcpv6: drop packet: ServerID option in message %s", msg.Type().String())
|
|
}
|
|
case dhcpv6.MessageTypeRequest,
|
|
dhcpv6.MessageTypeRenew,
|
|
dhcpv6.MessageTypeRelease,
|
|
dhcpv6.MessageTypeDecline:
|
|
if sid == nil {
|
|
return fmt.Errorf("dhcpv6: drop packet: no ServerID option in message %s", msg.Type().String())
|
|
}
|
|
|
|
if !sid.Equal(s.sid) {
|
|
return fmt.Errorf("dhcpv6: drop packet: mismatched ServerID option in message %s: %s",
|
|
msg.Type().String(), sid.String())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// . IAAddress must be equal to the lease's IP
|
|
func (s *v6Server) checkIA(msg *dhcpv6.Message, lease *Lease) error {
|
|
switch msg.Type() {
|
|
case dhcpv6.MessageTypeRequest,
|
|
dhcpv6.MessageTypeConfirm,
|
|
dhcpv6.MessageTypeRenew,
|
|
dhcpv6.MessageTypeRebind:
|
|
|
|
oia := msg.Options.OneIANA()
|
|
if oia == nil {
|
|
return fmt.Errorf("no IANA option in %s", msg.Type().String())
|
|
}
|
|
|
|
oiaAddr := oia.Options.OneAddress()
|
|
if oiaAddr == nil {
|
|
return fmt.Errorf("no IANA.Addr option in %s", msg.Type().String())
|
|
}
|
|
|
|
leaseIP := net.IP(lease.IP.AsSlice())
|
|
if !oiaAddr.IPv6Addr.Equal(leaseIP) {
|
|
return fmt.Errorf("invalid IANA.Addr option in %s", msg.Type().String())
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Store lease in DB (if necessary) and return lease life time
|
|
func (s *v6Server) commitLease(msg *dhcpv6.Message, lease *Lease) time.Duration {
|
|
lifetime := s.conf.leaseTime
|
|
|
|
switch msg.Type() {
|
|
case dhcpv6.MessageTypeSolicit:
|
|
//
|
|
|
|
case dhcpv6.MessageTypeConfirm:
|
|
lifetime = time.Until(lease.Expiry)
|
|
|
|
case dhcpv6.MessageTypeRequest,
|
|
dhcpv6.MessageTypeRenew,
|
|
dhcpv6.MessageTypeRebind:
|
|
|
|
if !lease.IsStatic {
|
|
s.commitDynamicLease(lease)
|
|
}
|
|
}
|
|
return lifetime
|
|
}
|
|
|
|
// Find a lease associated with MAC and prepare response
|
|
func (s *v6Server) process(msg *dhcpv6.Message, req, resp dhcpv6.DHCPv6) bool {
|
|
switch msg.Type() {
|
|
case dhcpv6.MessageTypeSolicit,
|
|
dhcpv6.MessageTypeRequest,
|
|
dhcpv6.MessageTypeConfirm,
|
|
dhcpv6.MessageTypeRenew,
|
|
dhcpv6.MessageTypeRebind:
|
|
// continue
|
|
|
|
default:
|
|
return false
|
|
}
|
|
|
|
mac, err := dhcpv6.ExtractMAC(req)
|
|
if err != nil {
|
|
log.Debug("dhcpv6: dhcpv6.ExtractMAC: %s", err)
|
|
|
|
return false
|
|
}
|
|
|
|
lease := s.findLease(mac)
|
|
if lease == nil {
|
|
log.Debug("dhcpv6: no lease for: %s", mac)
|
|
|
|
switch msg.Type() {
|
|
|
|
case dhcpv6.MessageTypeSolicit:
|
|
lease = s.reserveLease(mac)
|
|
if lease == nil {
|
|
return false
|
|
}
|
|
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
err = s.checkIA(msg, lease)
|
|
if err != nil {
|
|
log.Debug("dhcpv6: %s", err)
|
|
|
|
return false
|
|
}
|
|
|
|
lifetime := s.commitLease(msg, lease)
|
|
|
|
oia := &dhcpv6.OptIANA{
|
|
T1: lifetime / 2,
|
|
T2: time.Duration(float32(lifetime) / 1.5),
|
|
}
|
|
roia := msg.Options.OneIANA()
|
|
if roia != nil {
|
|
copy(oia.IaId[:], roia.IaId[:])
|
|
} else {
|
|
copy(oia.IaId[:], []byte(valueIAID))
|
|
}
|
|
oiaAddr := &dhcpv6.OptIAAddress{
|
|
IPv6Addr: net.IP(lease.IP.AsSlice()),
|
|
PreferredLifetime: lifetime,
|
|
ValidLifetime: lifetime,
|
|
}
|
|
oia.Options = dhcpv6.IdentityOptions{
|
|
Options: []dhcpv6.Option{oiaAddr},
|
|
}
|
|
resp.AddOption(oia)
|
|
|
|
if msg.IsOptionRequested(dhcpv6.OptionDNSRecursiveNameServer) {
|
|
resp.UpdateOption(dhcpv6.OptDNS(s.conf.dnsIPAddrs...))
|
|
}
|
|
|
|
fqdn := msg.GetOneOption(dhcpv6.OptionFQDN)
|
|
if fqdn != nil {
|
|
resp.AddOption(fqdn)
|
|
}
|
|
|
|
resp.AddOption(&dhcpv6.OptStatusCode{
|
|
StatusCode: iana.StatusSuccess,
|
|
StatusMessage: "success",
|
|
})
|
|
return true
|
|
}
|
|
|
|
// 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())
|
|
|
|
err = s.checkCID(msg)
|
|
if err != nil {
|
|
log.Debug("%s", err)
|
|
return
|
|
}
|
|
|
|
err = s.checkSID(msg)
|
|
if err != nil {
|
|
log.Debug("%s", err)
|
|
return
|
|
}
|
|
|
|
var resp dhcpv6.DHCPv6
|
|
|
|
switch msg.Type() {
|
|
case dhcpv6.MessageTypeSolicit:
|
|
if msg.GetOneOption(dhcpv6.OptionRapidCommit) == nil {
|
|
resp, err = dhcpv6.NewAdvertiseFromSolicit(msg)
|
|
|
|
break
|
|
}
|
|
|
|
resp, err = dhcpv6.NewReplyFromMessage(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.sid))
|
|
|
|
_ = s.process(msg, 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
|
|
}
|
|
}
|
|
|
|
// configureDNSIPAddrs updates v6Server configuration with the slice of DNS IP
|
|
// addresses of provided interface iface. Initializes RA module.
|
|
func (s *v6Server) configureDNSIPAddrs(iface *net.Interface) (ok bool, err error) {
|
|
dnsIPAddrs, err := aghnet.IfaceDNSIPAddrs(
|
|
iface,
|
|
aghnet.IPVersion6,
|
|
defaultMaxAttempts,
|
|
defaultBackoff,
|
|
)
|
|
if err != nil {
|
|
return false, fmt.Errorf("interface %s: %w", iface.Name, err)
|
|
}
|
|
|
|
if len(dnsIPAddrs) == 0 {
|
|
return false, nil
|
|
}
|
|
|
|
s.conf.dnsIPAddrs = dnsIPAddrs
|
|
|
|
return true, s.initRA(iface)
|
|
}
|
|
|
|
// initRA initializes RA module.
|
|
func (s *v6Server) initRA(iface *net.Interface) (err error) {
|
|
// Choose the source IP address - should be link-local-unicast.
|
|
s.ra.ipAddr = s.conf.dnsIPAddrs[0]
|
|
for _, ip := range s.conf.dnsIPAddrs {
|
|
if ip.IsLinkLocalUnicast() {
|
|
s.ra.ipAddr = ip
|
|
break
|
|
}
|
|
}
|
|
|
|
s.ra.raAllowSLAAC = s.conf.RAAllowSLAAC
|
|
s.ra.raSLAACOnly = s.conf.RASLAACOnly
|
|
s.ra.dnsIPAddr = s.ra.ipAddr
|
|
s.ra.prefixIPAddr = s.conf.ipStart
|
|
s.ra.ifaceName = s.conf.InterfaceName
|
|
s.ra.iface = iface
|
|
s.ra.packetSendPeriod = 1 * time.Second
|
|
|
|
return s.ra.Init()
|
|
}
|
|
|
|
// Start starts the IPv6 DHCP server.
|
|
func (s *v6Server) Start() (err error) {
|
|
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
|
|
|
|
if !s.conf.Enabled {
|
|
return nil
|
|
}
|
|
|
|
ifaceName := s.conf.InterfaceName
|
|
iface, err := net.InterfaceByName(ifaceName)
|
|
if err != nil {
|
|
return fmt.Errorf("finding interface %s by name: %w", ifaceName, err)
|
|
}
|
|
|
|
log.Debug("dhcpv6: starting...")
|
|
|
|
ok, err := s.configureDNSIPAddrs(iface)
|
|
if err != nil {
|
|
// Don't wrap the error, because it's informative enough as is.
|
|
return err
|
|
}
|
|
|
|
if !ok {
|
|
// No available IP addresses which may appear later.
|
|
return nil
|
|
}
|
|
|
|
// Don't initialize DHCPv6 server if we must force the clients to use SLAAC.
|
|
if s.conf.RASLAACOnly {
|
|
log.Debug("not starting dhcpv6 server due to ra_slaac_only=true")
|
|
|
|
return nil
|
|
}
|
|
|
|
err = netutil.ValidateMAC(iface.HardwareAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("validating interface %s: %w", iface.Name, err)
|
|
}
|
|
|
|
s.sid = &dhcpv6.DUIDLLT{
|
|
HWType: iana.HWTypeEthernet,
|
|
LinkLayerAddr: iface.HardwareAddr,
|
|
Time: dhcpv6.GetTime(),
|
|
}
|
|
|
|
s.srv, err = server6.NewServer(iface.Name, nil, s.packetHandler, server6.WithDebugLogger())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debug("dhcpv6: listening...")
|
|
|
|
go func() {
|
|
if sErr := s.srv.Serve(); errors.Is(sErr, net.ErrClosed) {
|
|
log.Info("dhcpv6: server is closed")
|
|
} else if sErr != nil {
|
|
log.Error("dhcpv6: srv.Serve: %s", sErr)
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop - stop server
|
|
func (s *v6Server) Stop() (err error) {
|
|
err = s.ra.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("closing ra ctx: %w", err)
|
|
}
|
|
|
|
// DHCPv6 server may not be initialized if ra_slaac_only=true
|
|
if s.srv == nil {
|
|
return
|
|
}
|
|
|
|
log.Debug("dhcpv6: stopping")
|
|
err = s.srv.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("closing dhcpv6 srv: %w", err)
|
|
}
|
|
|
|
// now server.Serve() will return
|
|
s.srv = nil
|
|
|
|
return nil
|
|
}
|
|
|
|
// Create DHCPv6 server
|
|
func v6Create(conf V6ServerConf) (DHCPServer, error) {
|
|
s := &v6Server{}
|
|
s.conf = conf
|
|
|
|
if !conf.Enabled {
|
|
return s, nil
|
|
}
|
|
|
|
s.conf.ipStart = conf.RangeStart
|
|
if s.conf.ipStart == nil || s.conf.ipStart.To16() == nil {
|
|
return s, fmt.Errorf("dhcpv6: invalid range-start IP: %s", conf.RangeStart)
|
|
}
|
|
|
|
if conf.LeaseDuration == 0 {
|
|
s.conf.leaseTime = timeutil.Day
|
|
s.conf.LeaseDuration = uint32(s.conf.leaseTime.Seconds())
|
|
} else {
|
|
s.conf.leaseTime = time.Second * time.Duration(conf.LeaseDuration)
|
|
}
|
|
|
|
return s, nil
|
|
}
|