mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-05-15 00:50:25 +02:00
all: add support for EdgeOS
This commit is contained in:
committed by
Cuong Manh Le
parent
ee53db1e35
commit
54e63ccf9b
+4
-1
@@ -175,7 +175,10 @@ func (p *prog) setDNS() {
|
||||
switch router.Name() {
|
||||
case router.DDWrt, router.OpenWrt, router.Ubios:
|
||||
// On router, ctrld run as a DNS forwarder, it does not have to change system DNS.
|
||||
// Except for Merlin/Tomato, which has WAN DNS setup on boot for NTP.
|
||||
// Except for:
|
||||
// + EdgeOS, which /etc/resolv.conf could be managed by vyatta_update_resolv.pl script.
|
||||
// + Merlin/Tomato, which has WAN DNS setup on boot for NTP.
|
||||
// + Synology, which /etc/resolv.conf is not configured to point to localhost.
|
||||
return
|
||||
}
|
||||
if cfg.Listener == nil || cfg.Listener["0"] == nil {
|
||||
|
||||
@@ -2,6 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/kardianos/service"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/router"
|
||||
)
|
||||
|
||||
func (p *prog) preRun() {
|
||||
@@ -17,6 +19,13 @@ func setDependencies(svc *service.Config) {
|
||||
"Wants=NetworkManager-wait-online.service",
|
||||
"After=NetworkManager-wait-online.service",
|
||||
}
|
||||
// On EdeOS, ctrld needs to start after vyatta-dhcpd, so it can read leases file.
|
||||
if router.Name() == router.EdgeOS {
|
||||
svc.Dependencies = append(svc.Dependencies, "Wants=vyatta-dhcpd.service")
|
||||
svc.Dependencies = append(svc.Dependencies, "After=vyatta-dhcpd.service")
|
||||
svc.Dependencies = append(svc.Dependencies, "Wants=dnsmasq.service")
|
||||
svc.Dependencies = append(svc.Dependencies, "After=dnsmasq.service")
|
||||
}
|
||||
}
|
||||
|
||||
func setWorkingDirectory(svc *service.Config, dir string) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
@@ -15,14 +16,18 @@ import (
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
)
|
||||
|
||||
var clientInfoFiles = []string{
|
||||
"/tmp/dnsmasq.leases", // ddwrt
|
||||
"/tmp/dhcp.leases", // openwrt
|
||||
"/var/lib/misc/dnsmasq.leases", // merlin
|
||||
"/mnt/data/udapi-config/dnsmasq.lease", // UDM Pro
|
||||
"/data/udapi-config/dnsmasq.lease", // UDR
|
||||
"/etc/dhcpd/dhcpd-leases.log", // Synology
|
||||
"/tmp/var/lib/misc/dnsmasq.leases", // Tomato
|
||||
type readClientInfoFunc func(name string) error
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (r *router) watchClientInfoTable() {
|
||||
@@ -34,14 +39,19 @@ func (r *router) watchClientInfoTable() {
|
||||
select {
|
||||
case <-timer.C:
|
||||
for _, name := range r.watcher.WatchList() {
|
||||
_ = readClientInfoFile(name)
|
||||
_ = clientInfoFiles[name](name)
|
||||
}
|
||||
case event, ok := <-r.watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if event.Has(fsnotify.Write) {
|
||||
if err := readClientInfoFile(event.Name); err != nil && !os.IsNotExist(err) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -80,17 +90,17 @@ func GetClientInfoByMac(mac string) *ctrld.ClientInfo {
|
||||
return val.(*ctrld.ClientInfo)
|
||||
}
|
||||
|
||||
func readClientInfoFile(name string) error {
|
||||
func dnsmasqReadClientInfoFile(name string) error {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return readClientInfoReader(f)
|
||||
return dnsmasqReadClientInfoReader(f)
|
||||
|
||||
}
|
||||
|
||||
func readClientInfoReader(reader io.Reader) error {
|
||||
func dnsmasqReadClientInfoReader(reader io.Reader) error {
|
||||
r := routerPlatform.Load()
|
||||
return lineread.Reader(reader, func(line []byte) error {
|
||||
fields := bytes.Fields(line)
|
||||
@@ -113,6 +123,54 @@ func readClientInfoReader(reader io.Reader) error {
|
||||
})
|
||||
}
|
||||
|
||||
func iscDHCPReadClientInfoFile(name string) error {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return iscDHCPReadClientInfoReader(f)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func normalizeIP(in string) string {
|
||||
// dnsmasq may put ip with interface index in lease file, strip it here.
|
||||
ip, _, found := strings.Cut(in, "%")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -31,31 +32,59 @@ func Test_normalizeIP(t *testing.T) {
|
||||
|
||||
func Test_readClientInfoReader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
mac string
|
||||
name string
|
||||
in string
|
||||
readFunc func(r io.Reader) error
|
||||
mac string
|
||||
}{
|
||||
{
|
||||
"good",
|
||||
"good dnsmasq",
|
||||
`1683329857 e6:20:59:b8:c1:6d 192.168.1.186 * 01:e6:20:59:b8:c1:6d
|
||||
`,
|
||||
dnsmasqReadClientInfoReader,
|
||||
"e6:20:59:b8:c1:6d",
|
||||
},
|
||||
{
|
||||
"bad seen on UDMdream machine",
|
||||
"bad dnsmasq seen on UDMdream machine",
|
||||
`1683329857 e6:20:59:b8:c1:6e 192.168.1.111 * 01:e6:20:59:b8:c1:6e
|
||||
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,
|
||||
"e6:20:59:b8:c1:6e",
|
||||
},
|
||||
{
|
||||
"isc-dhcpd good",
|
||||
`lease 192.168.1.1 {
|
||||
hardware ethernet 00:00:00:00:00:01;
|
||||
client-hostname "host-1";
|
||||
}
|
||||
`,
|
||||
iscDHCPReadClientInfoReader,
|
||||
"00:00:00:00:00:01",
|
||||
},
|
||||
{
|
||||
"isc-dhcpd bad mac",
|
||||
`lease 192.168.1.1 {
|
||||
hardware ethernet invalid-mac;
|
||||
client-hostname "host-1";
|
||||
}
|
||||
|
||||
lease 192.168.1.2 {
|
||||
hardware ethernet 00:00:00:00:00:02;
|
||||
client-hostname "host-2";
|
||||
}
|
||||
`,
|
||||
iscDHCPReadClientInfoReader,
|
||||
"00:00:00:00:00:02",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := routerPlatform.Load()
|
||||
r.mac.Delete(tc.mac)
|
||||
if err := readClientInfoReader(strings.NewReader(tc.in)); err != nil {
|
||||
if err := tc.readFunc(strings.NewReader(tc.in)); err != nil {
|
||||
t.Errorf("readClientInfoReader() error = %v", err)
|
||||
}
|
||||
info, existed := r.mac.Load(tc.mac)
|
||||
@@ -64,6 +93,8 @@ duid 00:01:00:01:2b:e4:2e:2c:52:52:14:26:dc:1c
|
||||
}
|
||||
if ci, ok := info.(*ctrld.ClientInfo); ok && existed && ci.Mac != tc.mac {
|
||||
t.Errorf("mac mismatched, got: %q, want: %q", ci.Mac, tc.mac)
|
||||
} else {
|
||||
t.Log(ci)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func dnsMasqConf() (string, error) {
|
||||
var sb strings.Builder
|
||||
var tmplText string
|
||||
switch Name() {
|
||||
case DDWrt, OpenWrt, Ubios, Synology, Tomato:
|
||||
case EdgeOS, DDWrt, OpenWrt, Ubios, Synology, Tomato:
|
||||
tmplText = dnsMasqConfigContentTmpl
|
||||
case Merlin:
|
||||
tmplText = merlinDNSMasqPostConfTmpl
|
||||
@@ -68,6 +68,8 @@ func dnsMasqConf() (string, error) {
|
||||
|
||||
func restartDNSMasq() error {
|
||||
switch Name() {
|
||||
case EdgeOS:
|
||||
return edgeOSRestartDNSMasq()
|
||||
case DDWrt:
|
||||
return ddwrtRestartDNSMasq()
|
||||
case Merlin:
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
const edgeOSDNSMasqConfigPath = "/etc/dnsmasq.d/dnsmasq-zzz-ctrld.conf"
|
||||
|
||||
func setupEdgeOS() error {
|
||||
// Disable dnsmasq as DNS server.
|
||||
dnsMasqConfigContent, err := dnsMasqConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(edgeOSDNSMasqConfigPath, []byte(dnsMasqConfigContent), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupEdgeOS() error {
|
||||
// Remove the custom dnsmasq config
|
||||
if err := os.Remove(edgeOSDNSMasqConfigPath); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postInstallEdgeOS() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func edgeOSRestartDNSMasq() error {
|
||||
if out, err := exec.Command("/etc/init.d/dnsmasq", "restart").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("edgeosRestartDNSMasq: %s, %w", string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+22
-12
@@ -21,6 +21,7 @@ const (
|
||||
Ubios = "ubios"
|
||||
Synology = "synology"
|
||||
Tomato = "tomato"
|
||||
EdgeOS = "edgeos"
|
||||
)
|
||||
|
||||
// ErrNotSupported reports the current router is not supported error.
|
||||
@@ -38,7 +39,7 @@ type router struct {
|
||||
// IsSupported reports whether the given platform is supported by ctrld.
|
||||
func IsSupported(platform string) bool {
|
||||
switch platform {
|
||||
case DDWrt, Merlin, OpenWrt, Ubios, Synology, Tomato:
|
||||
case EdgeOS, DDWrt, Merlin, OpenWrt, Synology, Tomato, Ubios:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -46,23 +47,24 @@ func IsSupported(platform string) bool {
|
||||
|
||||
// SupportedPlatforms return all platforms that can be configured to run with ctrld.
|
||||
func SupportedPlatforms() []string {
|
||||
return []string{DDWrt, Merlin, OpenWrt, Ubios, Synology, Tomato}
|
||||
return []string{EdgeOS, DDWrt, Merlin, OpenWrt, Synology, Tomato, Ubios}
|
||||
}
|
||||
|
||||
var configureFunc = map[string]func() error{
|
||||
EdgeOS: setupEdgeOS,
|
||||
DDWrt: setupDDWrt,
|
||||
Merlin: setupMerlin,
|
||||
OpenWrt: setupOpenWrt,
|
||||
Ubios: setupUbiOS,
|
||||
Synology: setupSynology,
|
||||
Tomato: setupTomato,
|
||||
Ubios: setupUbiOS,
|
||||
}
|
||||
|
||||
// Configure configures things for running ctrld on the router.
|
||||
func Configure(c *ctrld.Config) error {
|
||||
name := Name()
|
||||
switch name {
|
||||
case DDWrt, Merlin, OpenWrt, Ubios, Synology, Tomato:
|
||||
case EdgeOS, DDWrt, Merlin, OpenWrt, Synology, Tomato, Ubios:
|
||||
if c.HasUpstreamSendClientInfo() {
|
||||
r := routerPlatform.Load()
|
||||
r.sendClientInfo = true
|
||||
@@ -72,8 +74,8 @@ func Configure(c *ctrld.Config) error {
|
||||
}
|
||||
r.watcher = watcher
|
||||
go r.watchClientInfoTable()
|
||||
for _, file := range clientInfoFiles {
|
||||
_ = readClientInfoFile(file)
|
||||
for file, readClienInfoFunc := range clientInfoFiles {
|
||||
_ = readClienInfoFunc(file)
|
||||
_ = r.watcher.Add(file)
|
||||
}
|
||||
}
|
||||
@@ -97,7 +99,7 @@ func ConfigureService(sc *service.Config) error {
|
||||
}
|
||||
case OpenWrt:
|
||||
sc.Option["SysvScript"] = openWrtScript
|
||||
case Merlin, Ubios, Synology, Tomato:
|
||||
case EdgeOS, Merlin, Synology, Tomato, Ubios:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -119,18 +121,20 @@ func PreStart() (err error) {
|
||||
func PostInstall() error {
|
||||
name := Name()
|
||||
switch name {
|
||||
case EdgeOS:
|
||||
return postInstallEdgeOS()
|
||||
case DDWrt:
|
||||
return postInstallDDWrt()
|
||||
case Merlin:
|
||||
return postInstallMerlin()
|
||||
case OpenWrt:
|
||||
return postInstallOpenWrt()
|
||||
case Ubios:
|
||||
return postInstallUbiOS()
|
||||
case Synology:
|
||||
return postInstallSynology()
|
||||
case Tomato:
|
||||
return postInstallTomato()
|
||||
case Ubios:
|
||||
return postInstallUbiOS()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -139,18 +143,20 @@ func PostInstall() error {
|
||||
func Cleanup() error {
|
||||
name := Name()
|
||||
switch name {
|
||||
case EdgeOS:
|
||||
return cleanupEdgeOS()
|
||||
case DDWrt:
|
||||
return cleanupDDWrt()
|
||||
case Merlin:
|
||||
return cleanupMerlin()
|
||||
case OpenWrt:
|
||||
return cleanupOpenWrt()
|
||||
case Ubios:
|
||||
return cleanupUbiOS()
|
||||
case Synology:
|
||||
return cleanupSynology()
|
||||
case Tomato:
|
||||
return cleanupTomato()
|
||||
case Ubios:
|
||||
return cleanupUbiOS()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -159,7 +165,7 @@ func Cleanup() error {
|
||||
func ListenAddress() string {
|
||||
name := Name()
|
||||
switch name {
|
||||
case DDWrt, Merlin, OpenWrt, Ubios, Synology, Tomato:
|
||||
case EdgeOS, DDWrt, Merlin, OpenWrt, Synology, Tomato, Ubios:
|
||||
return "127.0.0.1:5354"
|
||||
}
|
||||
return ""
|
||||
@@ -190,6 +196,10 @@ func distroName() string {
|
||||
return Synology
|
||||
case bytes.HasPrefix(unameO(), []byte("Tomato")):
|
||||
return Tomato
|
||||
case haveDir("/config/scripts/post-config.d"):
|
||||
return EdgeOS
|
||||
case haveFile("/etc/ubnt/init/vyatta-router"):
|
||||
return EdgeOS // For 2.x
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user