mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
internal/clientinfo: watch NDP table changes on Linux
So with clients which only use SLAAC, ctrld could see client's new ip as soon as its state changes to REACHABLE. Moreover, the NDP listener is also changed to listen on all possible ipv6 link local interfaces. That would allow ctrld to get all NDP events happening in local network. SLAAC RFC: https://datatracker.ietf.org/doc/html/rfc4862
This commit is contained in:
committed by
Cuong Manh Le
parent
1a8c1ec73d
commit
c1e6f5126a
@@ -224,6 +224,7 @@ func (t *Table) init() {
|
||||
cancel()
|
||||
}()
|
||||
go t.ndp.listen(ctx)
|
||||
go t.ndp.subscribe(ctx)
|
||||
}
|
||||
// PTR lookup.
|
||||
if t.discoverPTR() {
|
||||
|
||||
@@ -70,16 +70,20 @@ func (nd *ndpDiscover) List() []string {
|
||||
}
|
||||
|
||||
// saveInfo saves ip and mac info to mapping table.
|
||||
// Last seen ip address will override the old one,
|
||||
func (nd *ndpDiscover) saveInfo(ip, mac string) {
|
||||
// If force is true, old ip will be removed before saving.
|
||||
func (nd *ndpDiscover) saveInfo(ip, mac string, force bool) {
|
||||
ip = normalizeIP(ip)
|
||||
// Store ip => map mapping,
|
||||
nd.mac.Store(ip, mac)
|
||||
// If there is old ip => mac mapping, delete it.
|
||||
old, ok := nd.ip.Load(mac)
|
||||
if ok {
|
||||
oldIP := old.(string)
|
||||
nd.mac.Delete(oldIP)
|
||||
|
||||
if force {
|
||||
// If there is old ip => mac mapping, delete it.
|
||||
if old, ok := nd.ip.Load(mac); ok {
|
||||
oldIP := old.(string)
|
||||
nd.mac.Delete(oldIP)
|
||||
}
|
||||
}
|
||||
|
||||
// Store mac => ip mapping.
|
||||
nd.ip.Store(mac, ip)
|
||||
}
|
||||
@@ -87,12 +91,20 @@ func (nd *ndpDiscover) saveInfo(ip, mac string) {
|
||||
// listen listens on ipv6 link local for Neighbor Solicitation message
|
||||
// to update new neighbors information to ndp table.
|
||||
func (nd *ndpDiscover) listen(ctx context.Context) {
|
||||
ifi, err := firstInterfaceWithV6LinkLocal()
|
||||
ifis, err := allInterfacesWithV6LinkLocal()
|
||||
if err != nil {
|
||||
ctrld.ProxyLogger.Load().Debug().Err(err).Msg("failed to find valid ipv6")
|
||||
ctrld.ProxyLogger.Load().Debug().Err(err).Msg("failed to find valid ipv6 interfaces")
|
||||
return
|
||||
}
|
||||
c, ip, err := ndp.Listen(ifi, ndp.LinkLocal)
|
||||
for _, ifi := range ifis {
|
||||
go func(ifi *net.Interface) {
|
||||
nd.listenOnInterface(ctx, ifi)
|
||||
}(ifi)
|
||||
}
|
||||
}
|
||||
|
||||
func (nd *ndpDiscover) listenOnInterface(ctx context.Context, ifi *net.Interface) {
|
||||
c, ip, err := ndp.Listen(ifi, ndp.Unspecified)
|
||||
if err != nil {
|
||||
ctrld.ProxyLogger.Load().Debug().Err(err).Msg("ndp listen failed")
|
||||
return
|
||||
@@ -126,7 +138,7 @@ func (nd *ndpDiscover) listen(ctx context.Context) {
|
||||
for _, opt := range am.Options {
|
||||
if lla, ok := opt.(*ndp.LinkLayerAddress); ok {
|
||||
mac := lla.Addr.String()
|
||||
nd.saveInfo(fromIP, mac)
|
||||
nd.saveInfo(fromIP, mac, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +153,7 @@ func (nd *ndpDiscover) scanWindows(r io.Reader) {
|
||||
continue
|
||||
}
|
||||
if mac := parseMAC(fields[1]); mac != "" {
|
||||
nd.saveInfo(fields[0], mac)
|
||||
nd.saveInfo(fields[0], mac, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,7 +172,7 @@ func (nd *ndpDiscover) scanUnix(r io.Reader) {
|
||||
if idx := strings.IndexByte(ip, '%'); idx != -1 {
|
||||
ip = ip[:idx]
|
||||
}
|
||||
nd.saveInfo(ip, mac)
|
||||
nd.saveInfo(ip, mac, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,14 +207,15 @@ func parseMAC(mac string) string {
|
||||
return hw.String()
|
||||
}
|
||||
|
||||
// firstInterfaceWithV6LinkLocal returns the first interface which is capable of using NDP.
|
||||
func firstInterfaceWithV6LinkLocal() (*net.Interface, error) {
|
||||
// allInterfacesWithV6LinkLocal returns all interfaces which is capable of using NDP.
|
||||
func allInterfacesWithV6LinkLocal() ([]*net.Interface, error) {
|
||||
ifis, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]*net.Interface, 0, len(ifis))
|
||||
for _, ifi := range ifis {
|
||||
ifi := ifi
|
||||
// Skip if iface is down/loopback/non-multicast.
|
||||
if ifi.Flags&net.FlagUp == 0 || ifi.Flags&net.FlagLoopback != 0 || ifi.Flags&net.FlagMulticast == 0 {
|
||||
continue
|
||||
@@ -223,9 +236,10 @@ func firstInterfaceWithV6LinkLocal() (*net.Interface, error) {
|
||||
return nil, fmt.Errorf("invalid ip address: %s", ipNet.String())
|
||||
}
|
||||
if ip.Is6() && !ip.Is4In6() {
|
||||
return &ifi, nil
|
||||
res = append(res, &ifi)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New("no interface can be used")
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package clientinfo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
)
|
||||
@@ -21,6 +24,41 @@ func (nd *ndpDiscover) scan() {
|
||||
}
|
||||
ip := n.IP.String()
|
||||
mac := n.HardwareAddr.String()
|
||||
nd.saveInfo(ip, mac)
|
||||
nd.saveInfo(ip, mac, false)
|
||||
}
|
||||
}
|
||||
|
||||
// subscribe watches NDP table changes and update new information to local table.
|
||||
func (nd *ndpDiscover) subscribe(ctx context.Context) {
|
||||
ch := make(chan netlink.NeighUpdate)
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
if err := netlink.NeighSubscribe(ch, done); err != nil {
|
||||
ctrld.ProxyLogger.Load().Err(err).Msg("could not perform neighbor subscribing")
|
||||
return
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case nu := <-ch:
|
||||
if nu.Family != netlink.FAMILY_V6 {
|
||||
continue
|
||||
}
|
||||
ip := normalizeIP(nu.IP.String())
|
||||
if nu.Type == unix.RTM_DELNEIGH {
|
||||
ctrld.ProxyLogger.Load().Debug().Msgf("removing NDP neighbor: %s", ip)
|
||||
nd.mac.Delete(ip)
|
||||
continue
|
||||
}
|
||||
mac := nu.HardwareAddr.String()
|
||||
switch nu.State {
|
||||
case netlink.NUD_REACHABLE:
|
||||
nd.saveInfo(ip, mac, false)
|
||||
case netlink.NUD_FAILED:
|
||||
ctrld.ProxyLogger.Load().Debug().Msgf("removing NDP neighbor with failed state: %s", ip)
|
||||
nd.mac.Delete(ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package clientinfo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
@@ -29,3 +30,7 @@ func (nd *ndpDiscover) scan() {
|
||||
nd.scanUnix(bytes.NewReader(data))
|
||||
}
|
||||
}
|
||||
|
||||
// subscribe watches NDP table changes and update new information to local table.
|
||||
// This is a stub method, and only works on Linux at this moment.
|
||||
func (nd *ndpDiscover) subscribe(ctx context.Context) {}
|
||||
|
||||
Reference in New Issue
Block a user