mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
all: watch lease files if send client info enabled
So users who run ctrld in Linux can still see clients info, even though it's not an router platform that ctrld supports.
This commit is contained in:
committed by
Cuong Manh Le
parent
472bb05e95
commit
9fe6af684f
@@ -9,3 +9,11 @@ type ClientInfo struct {
|
||||
IP string
|
||||
Hostname string
|
||||
}
|
||||
|
||||
// LeaseFileFormat specifies the format of DHCP lease file.
|
||||
type LeaseFileFormat string
|
||||
|
||||
const (
|
||||
Dnsmasq LeaseFileFormat = "dnsmasq"
|
||||
IscDhcpd = "isc-dhcpd"
|
||||
)
|
||||
|
||||
@@ -219,9 +219,6 @@ func initCLI() {
|
||||
if err := router.Cleanup(svcConfig); err != nil {
|
||||
mainLog.Error().Err(err).Msg("could not cleanup router")
|
||||
}
|
||||
if err := router.Stop(); err != nil {
|
||||
mainLog.Error().Err(err).Msg("problem occurred while stopping router")
|
||||
}
|
||||
p.resetDNS()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
"github.com/Control-D-Inc/ctrld/internal/dnscache"
|
||||
ctrldnet "github.com/Control-D-Inc/ctrld/internal/net"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -56,7 +55,7 @@ func (p *prog) serveDNS(listenerNum string) error {
|
||||
q := m.Question[0]
|
||||
domain := canonicalName(q.Name)
|
||||
reqId := requestID()
|
||||
remoteAddr := spoofRemoteAddr(w.RemoteAddr(), router.GetClientInfoByMac(macFromMsg(m)))
|
||||
remoteAddr := spoofRemoteAddr(w.RemoteAddr(), p.mt.GetClientInfoByMac(macFromMsg(m)))
|
||||
fmtSrcToDest := fmtRemoteToLocal(listenerNum, remoteAddr.String(), w.LocalAddr().String())
|
||||
t := time.Now()
|
||||
ctx := context.WithValue(context.Background(), ctrld.ReqIdCtxKey{}, reqId)
|
||||
@@ -247,7 +246,7 @@ func (p *prog) proxy(ctx context.Context, upstreams []string, failoverRcodes []i
|
||||
}
|
||||
resolve := func(n int, upstreamConfig *ctrld.UpstreamConfig, msg *dns.Msg) *dns.Msg {
|
||||
if upstreamConfig.UpstreamSendClientInfo() {
|
||||
ci := router.GetClientInfoByMac(macFromMsg(msg))
|
||||
ci := p.mt.GetClientInfoByMac(macFromMsg(msg))
|
||||
if ci != nil {
|
||||
ctrld.Log(ctx, mainLog.Debug(), "including client info with the request")
|
||||
ctx = context.WithValue(ctx, ctrld.ClientInfoCtxKey{}, ci)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/kardianos/service"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
"github.com/Control-D-Inc/ctrld/internal/clientinfo"
|
||||
"github.com/Control-D-Inc/ctrld/internal/dnscache"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router"
|
||||
)
|
||||
@@ -39,6 +40,7 @@ type prog struct {
|
||||
cfg *ctrld.Config
|
||||
cache dnscache.Cacher
|
||||
sema semaphore
|
||||
mt *clientinfo.MacTable
|
||||
|
||||
started chan struct{}
|
||||
onStarted []func()
|
||||
@@ -100,6 +102,16 @@ func (p *prog) run() {
|
||||
go uc.Ping()
|
||||
}
|
||||
|
||||
p.mt = clientinfo.NewMacTable()
|
||||
if p.cfg.HasUpstreamSendClientInfo() {
|
||||
mainLog.Debug().Msg("Sending client info enabled")
|
||||
if err := p.mt.Init(); err == nil {
|
||||
mainLog.Debug().Msg("Start watching client info changes")
|
||||
go p.mt.WatchLeaseFiles()
|
||||
} else {
|
||||
mainLog.Warn().Err(err).Msg("could not record client info")
|
||||
}
|
||||
}
|
||||
go p.watchLinkState()
|
||||
|
||||
for listenerNum := range p.cfg.Listener {
|
||||
|
||||
219
internal/clientinfo/client_info.go
Normal file
219
internal/clientinfo/client_info.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package clientinfo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"tailscale.com/util/lineread"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
)
|
||||
|
||||
// clientInfoFiles specifies client info files and how to read them on supported platforms.
|
||||
var clientInfoFiles = map[string]ctrld.LeaseFileFormat{
|
||||
"/tmp/dnsmasq.leases": ctrld.Dnsmasq, // ddwrt
|
||||
"/tmp/dhcp.leases": ctrld.Dnsmasq, // openwrt
|
||||
"/var/lib/misc/dnsmasq.leases": ctrld.Dnsmasq, // merlin
|
||||
"/mnt/data/udapi-config/dnsmasq.lease": ctrld.Dnsmasq, // UDM Pro
|
||||
"/data/udapi-config/dnsmasq.lease": ctrld.Dnsmasq, // UDR
|
||||
"/etc/dhcpd/dhcpd-leases.log": ctrld.Dnsmasq, // Synology
|
||||
"/tmp/var/lib/misc/dnsmasq.leases": ctrld.Dnsmasq, // Tomato
|
||||
"/run/dnsmasq-dhcp.leases": ctrld.Dnsmasq, // EdgeOS
|
||||
"/run/dhcpd.leases": ctrld.IscDhcpd, // EdgeOS
|
||||
"/var/dhcpd/var/db/dhcpd.leases": ctrld.IscDhcpd, // Pfsense
|
||||
"/home/pi/.router/run/dhcp/dnsmasq.leases": ctrld.Dnsmasq, // Firewalla
|
||||
}
|
||||
|
||||
// NewMacTable returns new Mac table to record client information.
|
||||
func NewMacTable() *MacTable {
|
||||
return &MacTable{}
|
||||
}
|
||||
|
||||
// MacTable records clients information by MAC address.
|
||||
type MacTable struct {
|
||||
mac sync.Map
|
||||
watcher *fsnotify.Watcher
|
||||
}
|
||||
|
||||
// Init initializes recording client info.
|
||||
func (mt *MacTable) Init() error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mt.watcher = watcher
|
||||
for file, format := range clientInfoFiles {
|
||||
// Ignore errors for default lease files.
|
||||
_ = mt.AddLeaseFile(file, format)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddLeaseFile adds given lease file for reading/watching clients info.
|
||||
func (mt *MacTable) AddLeaseFile(name string, format ctrld.LeaseFileFormat) error {
|
||||
if err := mt.readLeaseFile(name, format); err != nil {
|
||||
return fmt.Errorf("could not read lease file: %w", err)
|
||||
}
|
||||
clientInfoFiles[name] = format
|
||||
return mt.watcher.Add(name)
|
||||
}
|
||||
|
||||
// GetClientInfoByMac returns ClientInfo for the client associated with the given MAC address.
|
||||
func (mt *MacTable) GetClientInfoByMac(mac string) *ctrld.ClientInfo {
|
||||
if mac == "" {
|
||||
return nil
|
||||
}
|
||||
val, ok := mt.mac.Load(mac)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return val.(*ctrld.ClientInfo)
|
||||
}
|
||||
|
||||
// WatchLeaseFiles watches changes happens in dnsmasq/dhcpd
|
||||
// lease files, perform updating to mac table if necessary.
|
||||
func (mt *MacTable) WatchLeaseFiles() {
|
||||
if mt.watcher == nil {
|
||||
return
|
||||
}
|
||||
timer := time.NewTicker(time.Minute * 5)
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
for _, name := range mt.watcher.WatchList() {
|
||||
format := clientInfoFiles[name]
|
||||
if err := mt.readLeaseFile(name, format); err != nil {
|
||||
ctrld.ProxyLog.Err(err).Str("file", name).Msg("failed to update lease file")
|
||||
}
|
||||
}
|
||||
case event, ok := <-mt.watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if event.Has(fsnotify.Write) {
|
||||
format := clientInfoFiles[event.Name]
|
||||
if err := mt.readLeaseFile(event.Name, format); err != nil && !os.IsNotExist(err) {
|
||||
ctrld.ProxyLog.Err(err).Str("file", event.Name).Msg("leases file changed but failed to update client info")
|
||||
}
|
||||
}
|
||||
case err, ok := <-mt.watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ctrld.ProxyLog.Err(err).Msg("could not watch client info file")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readLeaseFile reads the lease file with given format, saving client information to mac table.
|
||||
func (mt *MacTable) readLeaseFile(name string, format ctrld.LeaseFileFormat) error {
|
||||
switch format {
|
||||
case ctrld.Dnsmasq:
|
||||
return mt.dnsmasqReadClientInfoFile(name)
|
||||
case ctrld.IscDhcpd:
|
||||
return mt.iscDHCPReadClientInfoFile(name)
|
||||
}
|
||||
return fmt.Errorf("unsupported format: %s, file: %s", format, name)
|
||||
}
|
||||
|
||||
// dnsmasqReadClientInfoFile populates mac table with client info reading from dnsmasq lease file.
|
||||
func (mt *MacTable) dnsmasqReadClientInfoFile(name string) error {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return mt.dnsmasqReadClientInfoReader(f)
|
||||
|
||||
}
|
||||
|
||||
// dnsmasqReadClientInfoReader likes ctrld.Dnsmasq, but reading from an io.Reader instead of file.
|
||||
func (mt *MacTable) dnsmasqReadClientInfoReader(reader io.Reader) error {
|
||||
return lineread.Reader(reader, func(line []byte) error {
|
||||
fields := bytes.Fields(line)
|
||||
if len(fields) < 4 {
|
||||
return nil
|
||||
}
|
||||
mac := string(fields[1])
|
||||
if _, err := net.ParseMAC(mac); err != nil {
|
||||
// The second field is not a mac, skip.
|
||||
return nil
|
||||
}
|
||||
ip := normalizeIP(string(fields[2]))
|
||||
if net.ParseIP(ip) == nil {
|
||||
log.Printf("invalid ip address entry: %q", ip)
|
||||
ip = ""
|
||||
}
|
||||
hostname := string(fields[3])
|
||||
mt.mac.Store(mac, &ctrld.ClientInfo{Mac: mac, IP: ip, Hostname: hostname})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// iscDHCPReadClientInfoFile populates mac table with client info reading from isc-dhcpd lease file.
|
||||
func (mt *MacTable) iscDHCPReadClientInfoFile(name string) error {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return mt.iscDHCPReadClientInfoReader(f)
|
||||
}
|
||||
|
||||
// iscDHCPReadClientInfoReader likes ctrld.IscDhcpd, but reading from an io.Reader instead of file.
|
||||
func (mt *MacTable) iscDHCPReadClientInfoReader(reader io.Reader) error {
|
||||
s := bufio.NewScanner(reader)
|
||||
var ip, mac, hostname string
|
||||
for s.Scan() {
|
||||
line := s.Text()
|
||||
if strings.HasPrefix(line, "}") {
|
||||
if mac != "" {
|
||||
mt.mac.Store(mac, &ctrld.ClientInfo{Mac: mac, IP: ip, Hostname: hostname})
|
||||
ip, mac, hostname = "", "", ""
|
||||
}
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
switch fields[0] {
|
||||
case "lease":
|
||||
ip = normalizeIP(strings.ToLower(fields[1]))
|
||||
if net.ParseIP(ip) == nil {
|
||||
log.Printf("invalid ip address entry: %q", ip)
|
||||
ip = ""
|
||||
}
|
||||
case "hardware":
|
||||
if len(fields) >= 3 {
|
||||
mac = strings.ToLower(strings.TrimRight(fields[2], ";"))
|
||||
if _, err := net.ParseMAC(mac); err != nil {
|
||||
// Invalid mac, skip.
|
||||
mac = ""
|
||||
}
|
||||
}
|
||||
case "client-hostname":
|
||||
hostname = strings.Trim(fields[1], `";`)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeIP normalizes the ip parsed from dnsmasq/dhcpd lease file.
|
||||
func normalizeIP(in string) string {
|
||||
// dnsmasq may put ip with interface index in lease file, strip it here.
|
||||
ip, _, found := strings.Cut(in, "%")
|
||||
if found {
|
||||
return ip
|
||||
}
|
||||
return in
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package clientinfo
|
||||
|
||||
import (
|
||||
"io"
|
||||
@@ -31,6 +31,7 @@ func Test_normalizeIP(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_readClientInfoReader(t *testing.T) {
|
||||
mt := NewMacTable()
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
@@ -41,7 +42,7 @@ func Test_readClientInfoReader(t *testing.T) {
|
||||
"good dnsmasq",
|
||||
`1683329857 e6:20:59:b8:c1:6d 192.168.1.186 * 01:e6:20:59:b8:c1:6d
|
||||
`,
|
||||
dnsmasqReadClientInfoReader,
|
||||
mt.dnsmasqReadClientInfoReader,
|
||||
"e6:20:59:b8:c1:6d",
|
||||
},
|
||||
{
|
||||
@@ -50,7 +51,7 @@ func Test_readClientInfoReader(t *testing.T) {
|
||||
duid 00:01:00:01:2b:e4:2e:2c:52:52:14:26:dc:1c
|
||||
1683322985 117442354 2600:4040:b0e6:b700::111 ASDASD 00:01:00:01:2a:d0:b9:81:00:07:32:4c:1c:07
|
||||
`,
|
||||
dnsmasqReadClientInfoReader,
|
||||
mt.dnsmasqReadClientInfoReader,
|
||||
"e6:20:59:b8:c1:6e",
|
||||
},
|
||||
{
|
||||
@@ -60,7 +61,7 @@ duid 00:01:00:01:2b:e4:2e:2c:52:52:14:26:dc:1c
|
||||
client-hostname "host-1";
|
||||
}
|
||||
`,
|
||||
iscDHCPReadClientInfoReader,
|
||||
mt.iscDHCPReadClientInfoReader,
|
||||
"00:00:00:00:00:01",
|
||||
},
|
||||
{
|
||||
@@ -75,25 +76,24 @@ lease 192.168.1.2 {
|
||||
client-hostname "host-2";
|
||||
}
|
||||
`,
|
||||
iscDHCPReadClientInfoReader,
|
||||
mt.iscDHCPReadClientInfoReader,
|
||||
"00:00:00:00:00:02",
|
||||
},
|
||||
{
|
||||
"",
|
||||
`1685794060 00:00:00:00:00:04 192.168.0.209 cuonglm-ThinkPad-X1-Carbon-Gen-9 00:00:00:00:00:04 9`,
|
||||
dnsmasqReadClientInfoReader,
|
||||
mt.dnsmasqReadClientInfoReader,
|
||||
"00:00:00:00:00:04",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := routerPlatform.Load()
|
||||
r.mac.Delete(tc.mac)
|
||||
mt.mac.Delete(tc.mac)
|
||||
if err := tc.readFunc(strings.NewReader(tc.in)); err != nil {
|
||||
t.Errorf("readClientInfoReader() error = %v", err)
|
||||
}
|
||||
info, existed := r.mac.Load(tc.mac)
|
||||
info, existed := mt.mac.Load(tc.mac)
|
||||
if !existed {
|
||||
t.Error("client info missing")
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"tailscale.com/util/lineread"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
)
|
||||
|
||||
// readClientInfoFunc represents the function for reading client info.
|
||||
type readClientInfoFunc func(name string) error
|
||||
|
||||
// clientInfoFiles specifies client info files and how to read them on supported platforms.
|
||||
var clientInfoFiles = map[string]readClientInfoFunc{
|
||||
"/tmp/dnsmasq.leases": dnsmasqReadClientInfoFile, // ddwrt
|
||||
"/tmp/dhcp.leases": dnsmasqReadClientInfoFile, // openwrt
|
||||
"/var/lib/misc/dnsmasq.leases": dnsmasqReadClientInfoFile, // merlin
|
||||
"/mnt/data/udapi-config/dnsmasq.lease": dnsmasqReadClientInfoFile, // UDM Pro
|
||||
"/data/udapi-config/dnsmasq.lease": dnsmasqReadClientInfoFile, // UDR
|
||||
"/etc/dhcpd/dhcpd-leases.log": dnsmasqReadClientInfoFile, // Synology
|
||||
"/tmp/var/lib/misc/dnsmasq.leases": dnsmasqReadClientInfoFile, // Tomato
|
||||
"/run/dnsmasq-dhcp.leases": dnsmasqReadClientInfoFile, // EdgeOS
|
||||
"/run/dhcpd.leases": iscDHCPReadClientInfoFile, // EdgeOS
|
||||
"/var/dhcpd/var/db/dhcpd.leases": iscDHCPReadClientInfoFile, // Pfsense
|
||||
"/home/pi/.router/run/dhcp/dnsmasq.leases": dnsmasqReadClientInfoFile, // Firewalla
|
||||
}
|
||||
|
||||
// watchClientInfoTable watches changes happens in dnsmasq/dhcpd
|
||||
// lease files, perform updating to mac table if necessary.
|
||||
func (r *router) watchClientInfoTable() {
|
||||
if r.watcher == nil {
|
||||
return
|
||||
}
|
||||
timer := time.NewTicker(time.Minute * 5)
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
for _, name := range r.watcher.WatchList() {
|
||||
_ = clientInfoFiles[name](name)
|
||||
}
|
||||
case event, ok := <-r.watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if event.Has(fsnotify.Write) {
|
||||
readFunc := clientInfoFiles[event.Name]
|
||||
if readFunc == nil {
|
||||
log.Println("unknown file format:", event.Name)
|
||||
continue
|
||||
}
|
||||
if err := readFunc(event.Name); err != nil && !os.IsNotExist(err) {
|
||||
log.Println("could not read client info file:", err)
|
||||
}
|
||||
}
|
||||
case err, ok := <-r.watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Println("error:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop performs tasks need to be done before the router stopped.
|
||||
func Stop() error {
|
||||
if Name() == "" {
|
||||
return nil
|
||||
}
|
||||
r := routerPlatform.Load()
|
||||
if r.watcher != nil {
|
||||
if err := r.watcher.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetClientInfoByMac returns ClientInfo for the client associated with the given mac.
|
||||
func GetClientInfoByMac(mac string) *ctrld.ClientInfo {
|
||||
if mac == "" {
|
||||
return nil
|
||||
}
|
||||
_ = Name()
|
||||
r := routerPlatform.Load()
|
||||
val, ok := r.mac.Load(mac)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return val.(*ctrld.ClientInfo)
|
||||
}
|
||||
|
||||
// dnsmasqReadClientInfoFile populates mac table with client info reading from dnsmasq lease file.
|
||||
func dnsmasqReadClientInfoFile(name string) error {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return dnsmasqReadClientInfoReader(f)
|
||||
|
||||
}
|
||||
|
||||
// dnsmasqReadClientInfoReader likes dnsmasqReadClientInfoFile, but reading from an io.Reader instead of file.
|
||||
func dnsmasqReadClientInfoReader(reader io.Reader) error {
|
||||
r := routerPlatform.Load()
|
||||
return lineread.Reader(reader, func(line []byte) error {
|
||||
fields := bytes.Fields(line)
|
||||
if len(fields) < 4 {
|
||||
return nil
|
||||
}
|
||||
mac := string(fields[1])
|
||||
if _, err := net.ParseMAC(mac); err != nil {
|
||||
// The second field is not a mac, skip.
|
||||
return nil
|
||||
}
|
||||
ip := normalizeIP(string(fields[2]))
|
||||
if net.ParseIP(ip) == nil {
|
||||
log.Printf("invalid ip address entry: %q", ip)
|
||||
ip = ""
|
||||
}
|
||||
hostname := string(fields[3])
|
||||
r.mac.Store(mac, &ctrld.ClientInfo{Mac: mac, IP: ip, Hostname: hostname})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// iscDHCPReadClientInfoFile populates mac table with client info reading from isc-dhcpd lease file.
|
||||
func iscDHCPReadClientInfoFile(name string) error {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return iscDHCPReadClientInfoReader(f)
|
||||
}
|
||||
|
||||
// iscDHCPReadClientInfoReader likes iscDHCPReadClientInfoFile, but reading from an io.Reader instead of file.
|
||||
func iscDHCPReadClientInfoReader(reader io.Reader) error {
|
||||
r := routerPlatform.Load()
|
||||
s := bufio.NewScanner(reader)
|
||||
var ip, mac, hostname string
|
||||
for s.Scan() {
|
||||
line := s.Text()
|
||||
if strings.HasPrefix(line, "}") {
|
||||
if mac != "" {
|
||||
r.mac.Store(mac, &ctrld.ClientInfo{Mac: mac, IP: ip, Hostname: hostname})
|
||||
ip, mac, hostname = "", "", ""
|
||||
}
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
switch fields[0] {
|
||||
case "lease":
|
||||
ip = normalizeIP(strings.ToLower(fields[1]))
|
||||
if net.ParseIP(ip) == nil {
|
||||
log.Printf("invalid ip address entry: %q", ip)
|
||||
ip = ""
|
||||
}
|
||||
case "hardware":
|
||||
if len(fields) >= 3 {
|
||||
mac = strings.ToLower(strings.TrimRight(fields[2], ";"))
|
||||
if _, err := net.ParseMAC(mac); err != nil {
|
||||
// Invalid mac, skip.
|
||||
mac = ""
|
||||
}
|
||||
}
|
||||
case "client-hostname":
|
||||
hostname = strings.Trim(fields[1], `";`)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeIP normalizes the ip parsed from dnsmasq/dhcpd lease file.
|
||||
func normalizeIP(in string) string {
|
||||
// dnsmasq may put ip with interface index in lease file, strip it here.
|
||||
ip, _, found := strings.Cut(in, "%")
|
||||
if found {
|
||||
return ip
|
||||
}
|
||||
return in
|
||||
}
|
||||
@@ -7,11 +7,9 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/kardianos/service"
|
||||
"tailscale.com/logtail/backoff"
|
||||
|
||||
@@ -38,8 +36,6 @@ var routerPlatform atomic.Pointer[router]
|
||||
type router struct {
|
||||
name string
|
||||
sendClientInfo bool
|
||||
mac sync.Map
|
||||
watcher *fsnotify.Watcher
|
||||
}
|
||||
|
||||
// IsSupported reports whether the given platform is supported by ctrld.
|
||||
@@ -102,16 +98,6 @@ func Configure(c *ctrld.Config) error {
|
||||
if c.HasUpstreamSendClientInfo() {
|
||||
r := routerPlatform.Load()
|
||||
r.sendClientInfo = true
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.watcher = watcher
|
||||
go r.watchClientInfoTable()
|
||||
for file, readClienInfoFunc := range clientInfoFiles {
|
||||
_ = readClienInfoFunc(file)
|
||||
_ = r.watcher.Add(file)
|
||||
}
|
||||
}
|
||||
configure := configureFunc[name]
|
||||
if err := configure(); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user