81 lines
2.1 KiB
Go
81 lines
2.1 KiB
Go
package main
|
|
|
|
import (
|
|
"log"
|
|
"strconv"
|
|
"unsafe"
|
|
|
|
"github.com/mdlayher/ndp"
|
|
"github.com/vishvananda/netlink/nl"
|
|
"golang.org/x/net/ipv6"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func DeserializeNdUserOptMsg(b []byte) *unix.NdUseroptmsg {
|
|
return (*unix.NdUseroptmsg)(unsafe.Pointer(&b[0:unix.SizeofNdUseroptmsg][0]))
|
|
}
|
|
|
|
func getRDNSS(m []byte, optsEnd int, linkIndex int) (*ndp.RecursiveDNSServer, error) {
|
|
opts := m[unix.SizeofNdUseroptmsg:optsEnd]
|
|
// Hack together a fake icmp packet so that we can use parsing from ndp
|
|
// unix.SizeofNdUseroptmsg == 16 but I don't feel like mangling it right now
|
|
buf := make([]byte, 16, 16+len(opts))
|
|
buf[0] = byte(ipv6.ICMPTypeRouterAdvertisement)
|
|
buf = append(buf, opts...)
|
|
|
|
pm, err := ndp.ParseMessage(buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var (
|
|
r *ndp.RecursiveDNSServer
|
|
ok bool
|
|
)
|
|
for _, o := range pm.(*ndp.RouterAdvertisement).Options {
|
|
if r, ok = o.(*ndp.RecursiveDNSServer); ok {
|
|
for i, addr := range r.Servers {
|
|
if addr.IsLinkLocalUnicast() {
|
|
// We add the zone here for link-local because the address won't work without the zones
|
|
r.Servers[i] = addr.WithZone(strconv.Itoa(linkIndex))
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
func ListenForSLAACDNS(slaacChannel chan *ndp.RecursiveDNSServer) {
|
|
s, err := nl.Subscribe(unix.NETLINK_ROUTE, unix.RTNLGRP_ND_USEROPT)
|
|
if err != nil {
|
|
log.Printf("Unable to listen for SLAAC DNS changes %v", err)
|
|
}
|
|
defer s.Close()
|
|
|
|
for {
|
|
msgs, from, err := s.Receive()
|
|
if err != nil {
|
|
log.Printf("Failed to recieve SLAAC DNS changes. Restart to try again: %v", err)
|
|
return
|
|
}
|
|
if from.Pid != nl.PidKernel {
|
|
continue
|
|
}
|
|
for _, m := range msgs {
|
|
if m.Header.Type != unix.RTM_NEWNDUSEROPT {
|
|
continue
|
|
}
|
|
msg := DeserializeNdUserOptMsg(m.Data)
|
|
if msg.Family != unix.AF_INET6 || msg.Icmp_type != byte(ipv6.ICMPTypeRouterAdvertisement) {
|
|
continue
|
|
}
|
|
slaacDNSServers, err := getRDNSS(m.Data, unix.SizeofNdUseroptmsg+int(msg.Opts_len), int(msg.Ifindex))
|
|
if err == nil && slaacDNSServers != nil && len(slaacDNSServers.Servers) > 0 {
|
|
slaacChannel <- slaacDNSServers
|
|
} else {
|
|
log.Printf("Failed to parse slaac options: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|