Removing router platforms support

This commit is contained in:
Cuong Manh Le
2025-06-30 22:00:03 +07:00
committed by Cuong Manh Le
parent af1a6e9f3a
commit ba9057e466
46 changed files with 31 additions and 4724 deletions
-26
View File
@@ -82,8 +82,6 @@ type Table struct {
logger *ctrld.Logger
dhcp *dhcp
merlin *merlinDiscover
ubios *ubiosDiscover
arp *arpDiscover
ndp *ndpDiscover
ptr *ptrDiscover
@@ -206,30 +204,6 @@ func (t *Table) init() {
return
}
// Otherwise, process all possible sources in order, that means
// the first result of IP/MAC/Hostname lookup will be used.
//
// Routers custom clients:
// - Merlin
// - Ubios
if t.discoverDHCP() || t.discoverARP() {
t.merlin = &merlinDiscover{logger: t.logger}
t.ubios = &ubiosDiscover{}
discovers := map[string]interface {
refresher
HostnameResolver
}{
"Merlin": t.merlin,
"Ubios": t.ubios,
}
for platform, discover := range discovers {
if err := discover.refresh(); err != nil {
t.logger.Warn().Err(err).Msgf("failed to init %s discover", platform)
}
t.hostnameResolvers = append(t.hostnameResolvers, discover)
t.refreshers = append(t.refreshers, discover)
}
}
// Hosts file mapping.
if t.discoverHosts() {
t.hf = &hostsFile{logger: t.logger}
+1 -28
View File
@@ -18,7 +18,6 @@ import (
"tailscale.com/util/lineread"
"github.com/Control-D-Inc/ctrld"
"github.com/Control-D-Inc/ctrld/internal/router"
)
type dhcp struct {
@@ -39,10 +38,6 @@ func (d *dhcp) init() error {
}
d.addSelf()
d.watcher = watcher
for file, format := range clientInfoFiles {
// Ignore errors for default lease files.
_ = d.addLeaseFile(file, format)
}
return nil
}
@@ -50,11 +45,7 @@ func (d *dhcp) watchChanges() {
if d.watcher == nil {
return
}
if dir := router.LeaseFilesDir(); dir != "" {
if err := d.watcher.Add(dir); err != nil {
d.logger.Err(err).Str("dir", dir).Msg("could not watch lease dir")
}
}
for {
select {
case event, ok := <-d.watcher.Events:
@@ -390,22 +381,4 @@ func (d *dhcp) addSelf() {
}
}
})
for _, netIface := range router.SelfInterfaces() {
mac := netIface.HardwareAddr.String()
if mac == "" {
return
}
d.mac2name.Store(mac, hostname)
addrs, _ := netIface.Addrs()
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok {
continue
}
ip := ipNet.IP
d.mac.LoadOrStore(ip.String(), mac)
d.ip.LoadOrStore(mac, ip.String())
d.ip2name.Store(ip.String(), hostname)
}
}
}
+2 -14
View File
@@ -3,17 +3,5 @@ package clientinfo
import "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
"/var/lib/kea/dhcp4.leases": ctrld.KeaDHCP4, // Pfsense
}
// TODO: cleanup this after server support removal.
var clientInfoFiles = map[string]ctrld.LeaseFileFormat{}
-72
View File
@@ -1,72 +0,0 @@
package clientinfo
import (
"strings"
"sync"
"github.com/Control-D-Inc/ctrld/internal/router"
"github.com/Control-D-Inc/ctrld/internal/router/merlin"
"github.com/Control-D-Inc/ctrld"
"github.com/Control-D-Inc/ctrld/internal/router/nvram"
)
const merlinNvramCustomClientListKey = "custom_clientlist"
type merlinDiscover struct {
hostname sync.Map // mac => hostname
logger *ctrld.Logger
}
func (m *merlinDiscover) refresh() error {
if router.Name() != merlin.Name {
return nil
}
out, err := nvram.Run("get", merlinNvramCustomClientListKey)
if err != nil {
return err
}
m.logger.Debug().Msg("reading Merlin custom client list")
m.parseMerlinCustomClientList(out)
return nil
}
func (m *merlinDiscover) LookupHostnameByIP(ip string) string {
return ""
}
func (m *merlinDiscover) LookupHostnameByMac(mac string) string {
val, ok := m.hostname.Load(mac)
if !ok {
return ""
}
return val.(string)
}
// "nvram get custom_clientlist" output:
//
// <client 1>00:00:00:00:00:01>0>4>><client 2>00:00:00:00:00:02>0>24>>...
//
// So to parse it, do the following steps:
//
// - Split by "<" => entries
// - For each entry, split by ">" => parts
// - Empty parts => skip
// - Empty parts[0] => skip empty hostname
// - Empty parts[1] => skip empty MAC
func (m *merlinDiscover) parseMerlinCustomClientList(data string) {
entries := strings.Split(data, "<")
for _, entry := range entries {
parts := strings.SplitN(string(entry), ">", 3)
if len(parts) < 2 || len(parts[0]) == 0 || len(parts[1]) == 0 {
continue
}
hostname := normalizeHostname(parts[0])
mac := strings.ToLower(parts[1])
m.hostname.Store(mac, hostname)
}
}
func (m *merlinDiscover) String() string {
return "merlin"
}
-82
View File
@@ -1,82 +0,0 @@
package clientinfo
import (
"testing"
)
func TestParseMerlinCustomClientList(t *testing.T) {
tests := []struct {
name string
clientList string
macList []string
hostnameList []string
macNotPresentList []string
}{
{
"normal",
"<client1>00:00:00:00:00:01>0>4>>",
[]string{"00:00:00:00:00:01"},
[]string{"client1"},
nil,
},
{
"multiple clients",
"<client1>00:00:00:00:00:01>0>4>><client2>00:00:00:00:00:02>0>24>>",
[]string{"00:00:00:00:00:01", "00:00:00:00:00:02"},
[]string{"client1", "client2"},
nil,
},
{
"empty hostname",
"<client1>00:00:00:00:00:01>0>4>><>00:00:00:00:00:02>0>24>>",
[]string{"00:00:00:00:00:01"},
[]string{"client1"},
[]string{"00:00:00:00:00:02"},
},
{
"empty dhcp",
"<client1>00:00:00:00:00:01>0>4>><client 1>>>",
[]string{"00:00:00:00:00:01"},
[]string{"client1"},
[]string{""},
},
{
"invalid",
"qwerty",
nil,
nil,
nil,
},
{
"empty",
"",
nil,
nil,
nil,
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
m := &merlinDiscover{}
m.parseMerlinCustomClientList(tc.clientList)
for i, mac := range tc.macList {
val, ok := m.hostname.Load(mac)
if !ok {
t.Errorf("missing hostname: %s", mac)
}
hostname := val.(string)
if hostname != tc.hostnameList[i] {
t.Errorf("hostname mismatch, want: %q, got: %q", tc.hostnameList[i], hostname)
}
}
for _, mac := range tc.macNotPresentList {
if _, ok := m.hostname.Load(mac); ok {
t.Errorf("mac2name address %q should not be present", mac)
}
}
})
}
}
-79
View File
@@ -1,79 +0,0 @@
package clientinfo
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os/exec"
"strings"
"sync"
"github.com/Control-D-Inc/ctrld/internal/router"
"github.com/Control-D-Inc/ctrld/internal/router/ubios"
)
// ubiosDiscover provides client discovery functionality on Ubios routers.
type ubiosDiscover struct {
hostname sync.Map // mac => hostname
}
// refresh reloads unifi devices from database.
func (u *ubiosDiscover) refresh() error {
if router.Name() != ubios.Name {
return nil
}
return u.refreshDevices()
}
// LookupHostnameByIP returns hostname for given IP.
func (u *ubiosDiscover) LookupHostnameByIP(ip string) string {
return ""
}
// LookupHostnameByMac returns unifi device custom hostname for the given MAC address.
func (u *ubiosDiscover) LookupHostnameByMac(mac string) string {
val, ok := u.hostname.Load(mac)
if !ok {
return ""
}
return val.(string)
}
// refreshDevices updates unifi devices name from local mongodb.
func (u *ubiosDiscover) refreshDevices() error {
cmd := exec.Command("/usr/bin/mongo", "localhost:27117/ace", "--quiet", "--eval", `
DBQuery.shellBatchSize = 256;
db.user.find({name: {$exists: true, $ne: ""}}, {_id:0, mac:1, name:1});`)
b, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("out: %s, err: %w", string(b), err)
}
return u.storeDevices(bytes.NewReader(b))
}
// storeDevices saves unifi devices name for caching.
func (u *ubiosDiscover) storeDevices(r io.Reader) error {
decoder := json.NewDecoder(r)
device := struct {
MAC string
Name string
}{}
for {
err := decoder.Decode(&device)
if err == io.EOF {
break
}
if err != nil {
return err
}
mac := strings.ToLower(device.MAC)
u.hostname.Store(mac, normalizeHostname(device.Name))
}
return nil
}
// String returns human-readable format of ubiosDiscover.
func (u *ubiosDiscover) String() string {
return "ubios"
}
-43
View File
@@ -1,43 +0,0 @@
package clientinfo
import (
"strings"
"testing"
)
func Test_ubiosDiscover_storeDevices(t *testing.T) {
ud := &ubiosDiscover{}
r := strings.NewReader(`{ "mac": "00:00:00:00:00:01", "name": "device 1" }
{ "mac": "00:00:00:00:00:02", "name": "device 2" }
`)
if err := ud.storeDevices(r); err != nil {
t.Fatal(err)
}
tests := []struct {
name string
mac string
hostname string
}{
{"device 1", "00:00:00:00:00:01", "device 1"},
{"device 2", "00:00:00:00:00:02", "device 2"},
{"non-existed", "00:00:00:00:00:03", ""},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := ud.LookupHostnameByMac(tc.mac); got != tc.hostname {
t.Errorf("hostname mismatched, want: %q, got: %q", tc.hostname, got)
}
})
}
// Test for invalid input.
r = strings.NewReader(`{ "mac": "00:00:00:00:00:01", "name": "device 1"`)
if err := ud.storeDevices(r); err == nil {
t.Fatal("expected error, got nil")
} else {
t.Log(err)
}
}