mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
all: add router client info detection
This commit add the ability for ctrld to gather client information, including mac/ip/hostname, and send to Control-D server through a config per upstream. - Add send_client_info upstream config. - Read/Watch dnsmasq leases files on supported platforms. - Add corresponding client info to DoH query header All of these only apply for Control-D upstream, though.
This commit is contained in:
committed by
Cuong Manh Le
parent
d52cd11322
commit
0645a738ad
11
client_info.go
Normal file
11
client_info.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package ctrld
|
||||
|
||||
// ClientInfoCtxKey is the context key to store client info.
|
||||
type ClientInfoCtxKey struct{}
|
||||
|
||||
// ClientInfo represents ctrld's clients information.
|
||||
type ClientInfo struct {
|
||||
Mac string
|
||||
IP string
|
||||
Hostname string
|
||||
}
|
||||
@@ -20,7 +20,13 @@ import (
|
||||
"github.com/Control-D-Inc/ctrld/internal/router"
|
||||
)
|
||||
|
||||
const staleTTL = 60 * time.Second
|
||||
const (
|
||||
staleTTL = 60 * time.Second
|
||||
// EDNS0_OPTION_MAC is dnsmasq EDNS0 code for adding mac option.
|
||||
// https://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=blob;f=src/dns-protocol.h;h=76ac66a8c28317e9c121a74ab5fd0e20f6237dc8;hb=HEAD#l81
|
||||
// This is also dns.EDNS0LOCALSTART, but define our own constant here for clarification.
|
||||
EDNS0_OPTION_MAC = 0xFDE9
|
||||
)
|
||||
|
||||
var osUpstreamConfig = &ctrld.UpstreamConfig{
|
||||
Name: "OS resolver",
|
||||
@@ -230,6 +236,12 @@ func (p *prog) proxy(ctx context.Context, upstreams []string, failoverRcodes []i
|
||||
return dnsResolver.Resolve(resolveCtx, msg)
|
||||
}
|
||||
resolve := func(n int, upstreamConfig *ctrld.UpstreamConfig, msg *dns.Msg) *dns.Msg {
|
||||
if upstreamConfig.UpstreamSendClientInfo() {
|
||||
ci := router.GetClientInfoByMac(macFromMsg(msg))
|
||||
if ci != nil {
|
||||
ctx = context.WithValue(ctx, ctrld.ClientInfoCtxKey{}, ci)
|
||||
}
|
||||
}
|
||||
answer, err := resolve1(n, upstreamConfig, msg)
|
||||
if err != nil {
|
||||
ctrld.Log(ctx, mainLog.Debug().Err(err), "could not resolve query on first attempt, retrying...")
|
||||
@@ -386,3 +398,17 @@ func dnsListenAddress(lc *ctrld.ListenerConfig) string {
|
||||
}
|
||||
return net.JoinHostPort(lc.IP, strconv.Itoa(lc.Port))
|
||||
}
|
||||
|
||||
func macFromMsg(msg *dns.Msg) string {
|
||||
if opt := msg.IsEdns0(); opt != nil {
|
||||
for _, s := range opt.Option {
|
||||
switch e := s.(type) {
|
||||
case *dns.EDNS0_LOCAL:
|
||||
if e.Code == EDNS0_OPTION_MAC {
|
||||
return net.HardwareAddr(e.Data).String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -155,3 +155,39 @@ func TestCache(t *testing.T) {
|
||||
assert.Equal(t, answer1.Rcode, got1.Rcode)
|
||||
assert.Equal(t, answer2.Rcode, got2.Rcode)
|
||||
}
|
||||
|
||||
func Test_macFromMsg(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mac string
|
||||
wantMac bool
|
||||
}{
|
||||
{"has mac", "4c:20:b8:ab:87:1b", true},
|
||||
{"no mac", "4c:20:b8:ab:87:1b", false},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
hw, err := net.ParseMAC(tc.mac)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(selfCheckFQDN+".", dns.TypeA)
|
||||
o := &dns.OPT{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT}}
|
||||
if tc.wantMac {
|
||||
ec1 := &dns.EDNS0_LOCAL{Code: EDNS0_OPTION_MAC, Data: hw}
|
||||
o.Option = append(o.Option, ec1)
|
||||
}
|
||||
m.Extra = append(m.Extra, o)
|
||||
got := macFromMsg(m)
|
||||
if tc.wantMac && got != tc.mac {
|
||||
t.Errorf("mismatch, want: %q, got: %q", tc.mac, got)
|
||||
}
|
||||
if !tc.wantMac && got != "" {
|
||||
t.Errorf("unexpected mac: %q", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,9 @@ func (p *prog) Stop(s service.Service) error {
|
||||
return err
|
||||
}
|
||||
p.preStop()
|
||||
if err := router.Stop(); err != nil {
|
||||
mainLog.Warn().Err(err).Msg("problem occurred while stopping router")
|
||||
}
|
||||
mainLog.Info().Msg("Service stopped")
|
||||
close(p.stopCh)
|
||||
return nil
|
||||
|
||||
54
config.go
54
config.go
@@ -80,6 +80,17 @@ type Config struct {
|
||||
Upstream map[string]*UpstreamConfig `mapstructure:"upstream" toml:"upstream" validate:"min=1,dive"`
|
||||
}
|
||||
|
||||
// HasUpstreamSendClientInfo reports whether the config has any upstream
|
||||
// is configured to send client info to Control D DNS server.
|
||||
func (c *Config) HasUpstreamSendClientInfo() bool {
|
||||
for _, uc := range c.Upstream {
|
||||
if uc.UpstreamSendClientInfo() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ServiceConfig specifies the general ctrld config.
|
||||
type ServiceConfig struct {
|
||||
LogLevel string `mapstructure:"log_level" toml:"log_level,omitempty"`
|
||||
@@ -101,12 +112,15 @@ type NetworkConfig struct {
|
||||
|
||||
// UpstreamConfig specifies configuration for upstreams that ctrld will forward requests to.
|
||||
type UpstreamConfig struct {
|
||||
Name string `mapstructure:"name" toml:"name,omitempty"`
|
||||
Type string `mapstructure:"type" toml:"type,omitempty" validate:"oneof=doh doh3 dot doq os legacy"`
|
||||
Endpoint string `mapstructure:"endpoint" toml:"endpoint,omitempty" validate:"required_unless=Type os"`
|
||||
BootstrapIP string `mapstructure:"bootstrap_ip" toml:"bootstrap_ip,omitempty"`
|
||||
Domain string `mapstructure:"-" toml:"-"`
|
||||
Timeout int `mapstructure:"timeout" toml:"timeout,omitempty" validate:"gte=0"`
|
||||
Name string `mapstructure:"name" toml:"name,omitempty"`
|
||||
Type string `mapstructure:"type" toml:"type,omitempty" validate:"oneof=doh doh3 dot doq os legacy"`
|
||||
Endpoint string `mapstructure:"endpoint" toml:"endpoint,omitempty" validate:"required_unless=Type os"`
|
||||
BootstrapIP string `mapstructure:"bootstrap_ip" toml:"bootstrap_ip,omitempty"`
|
||||
Domain string `mapstructure:"-" toml:"-"`
|
||||
Timeout int `mapstructure:"timeout" toml:"timeout,omitempty" validate:"gte=0"`
|
||||
// The caller should not access this field directly.
|
||||
// Use UpstreamSendClientInfo instead.
|
||||
SendClientInfo *bool `mapstructure:"send_client_info" toml:"send_client_info,omitempty"`
|
||||
transport *http.Transport `mapstructure:"-" toml:"-"`
|
||||
http3RoundTripper http.RoundTripper `mapstructure:"-" toml:"-"`
|
||||
certPool *x509.CertPool `mapstructure:"-" toml:"-"`
|
||||
@@ -163,6 +177,34 @@ func (uc *UpstreamConfig) Init() {
|
||||
}
|
||||
}
|
||||
|
||||
// UpstreamSendClientInfo reports whether the upstream is
|
||||
// configured to send client info to Control D DNS server.
|
||||
//
|
||||
// Client info includes:
|
||||
// - MAC
|
||||
// - Lan IP
|
||||
// - Hostname
|
||||
func (uc *UpstreamConfig) UpstreamSendClientInfo() bool {
|
||||
if uc.SendClientInfo != nil && !(*uc.SendClientInfo) {
|
||||
return false
|
||||
}
|
||||
if uc.SendClientInfo == nil {
|
||||
return true
|
||||
}
|
||||
switch uc.Type {
|
||||
case ResolverTypeDOH, ResolverTypeDOH3:
|
||||
if u, err := url.Parse(uc.Endpoint); err == nil {
|
||||
domain := u.Hostname()
|
||||
for _, parent := range []string{"controld.com", "controld.net"} {
|
||||
if dns.IsSubDomain(parent, domain) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetCertPool sets the system cert pool used for TLS connections.
|
||||
func (uc *UpstreamConfig) SetCertPool(cp *x509.CertPool) {
|
||||
uc.certPool = cp
|
||||
|
||||
@@ -147,6 +147,28 @@ func TestUpstreamConfig_Init(t *testing.T) {
|
||||
Timeout: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"doh+doh3 with send client info set",
|
||||
&UpstreamConfig{
|
||||
Name: "doh",
|
||||
Type: "doh",
|
||||
Endpoint: "https://example.com?k=v",
|
||||
BootstrapIP: "",
|
||||
Domain: "",
|
||||
Timeout: 0,
|
||||
SendClientInfo: ptrBool(false),
|
||||
},
|
||||
&UpstreamConfig{
|
||||
Name: "doh",
|
||||
Type: "doh",
|
||||
Endpoint: "https://example.com?k=v",
|
||||
BootstrapIP: "",
|
||||
Domain: "example.com",
|
||||
Timeout: 0,
|
||||
SendClientInfo: ptrBool(false),
|
||||
u: u2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
@@ -158,3 +180,7 @@ func TestUpstreamConfig_Init(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ptrBool(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
@@ -24,10 +24,12 @@ func TestLoadConfig(t *testing.T) {
|
||||
assert.Contains(t, cfg.Network, "0")
|
||||
assert.Contains(t, cfg.Network, "1")
|
||||
|
||||
assert.Len(t, cfg.Upstream, 3)
|
||||
assert.Len(t, cfg.Upstream, 4)
|
||||
assert.Contains(t, cfg.Upstream, "0")
|
||||
assert.Contains(t, cfg.Upstream, "1")
|
||||
assert.Contains(t, cfg.Upstream, "2")
|
||||
assert.Contains(t, cfg.Upstream, "3")
|
||||
assert.NotNil(t, cfg.Upstream["3"].SendClientInfo)
|
||||
|
||||
assert.Len(t, cfg.Listener, 2)
|
||||
assert.Contains(t, cfg.Listener, "0")
|
||||
@@ -42,6 +44,8 @@ func TestLoadConfig(t *testing.T) {
|
||||
assert.Len(t, cfg.Listener["0"].Policy.Rules, 2)
|
||||
assert.Contains(t, cfg.Listener["0"].Policy.Rules[0], "*.ru")
|
||||
assert.Contains(t, cfg.Listener["0"].Policy.Rules[1], "*.local.host")
|
||||
|
||||
assert.True(t, cfg.HasUpstreamSendClientInfo())
|
||||
}
|
||||
|
||||
func TestLoadDefaultConfig(t *testing.T) {
|
||||
|
||||
32
doh.go
32
doh.go
@@ -12,12 +12,22 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const (
|
||||
DoHMacHeader = "Dns-Mac"
|
||||
DoHIPHeader = "Dns-IP"
|
||||
DoHHostHeader = "Dns-Host"
|
||||
|
||||
headerContentTypeValue = "application/dns-message"
|
||||
headerAcceptValue = "application/dns-message"
|
||||
)
|
||||
|
||||
func newDohResolver(uc *UpstreamConfig) *dohResolver {
|
||||
r := &dohResolver{
|
||||
endpoint: uc.u,
|
||||
isDoH3: uc.Type == ResolverTypeDOH3,
|
||||
transport: uc.transport,
|
||||
http3RoundTripper: uc.http3RoundTripper,
|
||||
sendClientInfo: uc.UpstreamSendClientInfo(),
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -27,6 +37,7 @@ type dohResolver struct {
|
||||
isDoH3 bool
|
||||
transport *http.Transport
|
||||
http3RoundTripper http.RoundTripper
|
||||
sendClientInfo bool
|
||||
}
|
||||
|
||||
func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) {
|
||||
@@ -45,8 +56,7 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/dns-message")
|
||||
req.Header.Set("Accept", "application/dns-message")
|
||||
addHeader(ctx, req, r.sendClientInfo)
|
||||
|
||||
c := http.Client{Transport: r.transport}
|
||||
if r.isDoH3 {
|
||||
@@ -78,3 +88,21 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro
|
||||
answer := new(dns.Msg)
|
||||
return answer, answer.Unpack(buf)
|
||||
}
|
||||
|
||||
func addHeader(ctx context.Context, req *http.Request, sendClientInfo bool) {
|
||||
req.Header.Set("Content-Type", headerContentTypeValue)
|
||||
req.Header.Set("Accept", headerAcceptValue)
|
||||
if sendClientInfo {
|
||||
if ci, ok := ctx.Value(ClientInfoCtxKey{}).(*ClientInfo); ok && ci != nil {
|
||||
if ci.Mac != "" {
|
||||
req.Header.Set(DoHMacHeader, ci.Mac)
|
||||
}
|
||||
if ci.IP != "" {
|
||||
req.Header.Set(DoHIPHeader, ci.IP)
|
||||
}
|
||||
if ci.Hostname != "" {
|
||||
req.Header.Set(DoHHostHeader, ci.Hostname)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
88
internal/router/client_info.go
Normal file
88
internal/router/client_info.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"tailscale.com/util/lineread"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
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() {
|
||||
_ = readClientInfoFile(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) {
|
||||
log.Println("could not read client info file:", err)
|
||||
}
|
||||
}
|
||||
case err, ok := <-r.watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Println("error:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func readClientInfoFile(name string) error {
|
||||
r := routerPlatform.Load()
|
||||
return lineread.File(name, func(line []byte) error {
|
||||
fields := bytes.Fields(line)
|
||||
mac := string(fields[1])
|
||||
ip := string(fields[2])
|
||||
hostname := string(fields[3])
|
||||
r.mac.Store(mac, &ctrld.ClientInfo{Mac: mac, IP: ip, Hostname: hostname})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -20,9 +20,9 @@ https://wiki.dd-wrt.com/wiki/index.php/Journalling_Flash_File_System
|
||||
`)
|
||||
|
||||
var nvramKeys = map[string]string{
|
||||
"dns_dnsmasq": "1", // Make dnsmasq running but disable DNS ability, ctrld will replace it.
|
||||
"dnsmasq_options": dnsMasqConfigContent, // Configuration of dnsmasq set by ctrld.
|
||||
"dns_crypt": "0", // Disable DNSCrypt.
|
||||
"dns_dnsmasq": "1", // Make dnsmasq running but disable DNS ability, ctrld will replace it.
|
||||
"dnsmasq_options": "", // Configuration of dnsmasq set by ctrld, filled by setupDDWrt.
|
||||
"dns_crypt": "0", // Disable DNSCrypt.
|
||||
}
|
||||
|
||||
func setupDDWrt() error {
|
||||
@@ -31,6 +31,11 @@ func setupDDWrt() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := dnsMasqConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nvramKeys["dnsmasq_options"] = data
|
||||
// Backup current value, store ctrld's configs.
|
||||
for key, value := range nvramKeys {
|
||||
old, err := nvram("get", key)
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
package router
|
||||
|
||||
const dnsMasqConfigContent = `# GENERATED BY ctrld - DO NOT MODIFY
|
||||
import (
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const dnsMasqConfigContentTmpl = `# GENERATED BY ctrld - DO NOT MODIFY
|
||||
no-resolv
|
||||
server=127.0.0.1#5354
|
||||
{{- if .SendClientInfo}}
|
||||
add-mac
|
||||
{{- end}}
|
||||
`
|
||||
|
||||
const merlinDNSMasqPostConfPath = "/jffs/scripts/dnsmasq.postconf"
|
||||
const merlinDNSMasqPostConfMarker = `# GENERATED BY ctrld - EOF`
|
||||
|
||||
const merlinDNSMasqPostConf = `# GENERATED BY ctrld - DO NOT MODIFY
|
||||
const merlinDNSMasqPostConfTmpl = `# GENERATED BY ctrld - DO NOT MODIFY
|
||||
|
||||
#!/bin/sh
|
||||
|
||||
@@ -20,7 +28,9 @@ if [ -n "$pid" ] && [ -f "/proc/${pid}/cmdline" ]; then
|
||||
pc_delete "servers-file" "$config_file" # no WAN DNS settings
|
||||
pc_append "no-resolv" "$config_file" # do not read /etc/resolv.conf
|
||||
pc_append "server=127.0.0.1#5354" "$config_file" # use ctrld as upstream
|
||||
|
||||
{{- if .SendClientInfo}}
|
||||
pc_append "add-mac" "$config_file" # add client mac
|
||||
{{- end}}
|
||||
|
||||
# For John fork
|
||||
pc_delete "resolv-file" "$config_file" # no WAN DNS settings
|
||||
@@ -32,3 +42,24 @@ if [ -n "$pid" ] && [ -f "/proc/${pid}/cmdline" ]; then
|
||||
exit 0
|
||||
fi
|
||||
`
|
||||
|
||||
func dnsMasqConf() (string, error) {
|
||||
var sb strings.Builder
|
||||
var tmplText string
|
||||
switch Name() {
|
||||
case DDWrt, OpenWrt, Ubios:
|
||||
tmplText = dnsMasqConfigContentTmpl
|
||||
case Merlin:
|
||||
tmplText = merlinDNSMasqPostConfTmpl
|
||||
}
|
||||
tmpl := template.Must(template.New("").Parse(tmplText))
|
||||
var to = &struct {
|
||||
SendClientInfo bool
|
||||
}{
|
||||
routerPlatform.Load().sendClientInfo,
|
||||
}
|
||||
if err := tmpl.Execute(&sb, to); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ func setupMerlin() error {
|
||||
return err
|
||||
}
|
||||
|
||||
merlinDNSMasqPostConf, err := dnsMasqConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := strings.Join([]string{
|
||||
merlinDNSMasqPostConf,
|
||||
"\n",
|
||||
@@ -38,7 +42,7 @@ func setupMerlin() error {
|
||||
}
|
||||
|
||||
func cleanupMerlin() error {
|
||||
buf, err := os.ReadFile(merlinDNSMasqPostConf)
|
||||
buf, err := os.ReadFile(merlinDNSMasqPostConfPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
func Test_merlinParsePostConf(t *testing.T) {
|
||||
origContent := "# foo"
|
||||
data := strings.Join([]string{
|
||||
merlinDNSMasqPostConf,
|
||||
merlinDNSMasqPostConfTmpl,
|
||||
"\n",
|
||||
merlinDNSMasqPostConfMarker,
|
||||
"\n",
|
||||
|
||||
@@ -19,6 +19,10 @@ func setupOpenWrt() error {
|
||||
return err
|
||||
}
|
||||
// Disable dnsmasq as DNS server.
|
||||
dnsMasqConfigContent, err := dnsMasqConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(openwrtDNSMasqConfigPath, []byte(dnsMasqConfigContent), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/kardianos/service"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
@@ -25,7 +27,10 @@ var ErrNotSupported = errors.New("unsupported platform")
|
||||
var routerPlatform atomic.Pointer[router]
|
||||
|
||||
type router struct {
|
||||
name string
|
||||
name string
|
||||
sendClientInfo bool
|
||||
mac sync.Map
|
||||
watcher *fsnotify.Watcher
|
||||
}
|
||||
|
||||
// SupportedPlatforms return all platforms that can be configured to run with ctrld.
|
||||
@@ -33,18 +38,37 @@ func SupportedPlatforms() []string {
|
||||
return []string{DDWrt, Merlin, OpenWrt, Ubios}
|
||||
}
|
||||
|
||||
var configureFunc = map[string]func() error{
|
||||
DDWrt: setupDDWrt,
|
||||
Merlin: setupMerlin,
|
||||
OpenWrt: setupOpenWrt,
|
||||
Ubios: setupUbiOS,
|
||||
}
|
||||
|
||||
// Configure configures things for running ctrld on the router.
|
||||
func Configure(c *ctrld.Config) error {
|
||||
name := Name()
|
||||
switch name {
|
||||
case DDWrt:
|
||||
return setupDDWrt()
|
||||
case Merlin:
|
||||
return setupMerlin()
|
||||
case OpenWrt:
|
||||
return setupOpenWrt()
|
||||
case Ubios:
|
||||
return setupUbiOS()
|
||||
case DDWrt, Merlin, OpenWrt, Ubios:
|
||||
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 := range clientInfoFiles {
|
||||
_ = readClientInfoFile(file)
|
||||
_ = r.watcher.Add(file)
|
||||
}
|
||||
}
|
||||
configure := configureFunc[name]
|
||||
if err := configure(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return ErrNotSupported
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ const (
|
||||
|
||||
func setupUbiOS() error {
|
||||
// Disable dnsmasq as DNS server.
|
||||
dnsMasqConfigContent, err := dnsMasqConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(ubiosDNSMasqConfigPath, []byte(dnsMasqConfigContent), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -50,6 +50,13 @@ type = "legacy"
|
||||
endpoint = "8.8.8.8"
|
||||
timeout = 5
|
||||
|
||||
[upstream.3]
|
||||
name = "DOH with client info"
|
||||
type = "doh"
|
||||
endpoint = "https://dns.controld.com/client_info_upstream/main-device"
|
||||
timeout = 5
|
||||
send_client_info = false
|
||||
|
||||
[listener.0]
|
||||
ip = "127.0.0.1"
|
||||
port = 53
|
||||
|
||||
Reference in New Issue
Block a user