mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
Compare commits
19 Commits
android-cr
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ca559e5a4 | ||
|
|
0e3f764299 | ||
|
|
e52402eb0c | ||
|
|
2133f31854 | ||
|
|
a198a5cd65 | ||
|
|
eb2b231bd2 | ||
|
|
7af29cfbc0 | ||
|
|
ce1a165348 | ||
|
|
fd48e6d795 | ||
|
|
d71d1341b6 | ||
|
|
21855df4af | ||
|
|
66e2d3a40a | ||
|
|
26257cf24a | ||
|
|
36a7423634 | ||
|
|
e616091249 | ||
|
|
0948161529 | ||
|
|
ce29b5d217 | ||
|
|
de24fa293e | ||
|
|
6663925c4d |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: ["windows-latest", "ubuntu-latest", "macOS-latest"]
|
os: ["windows-latest", "ubuntu-latest", "macOS-latest"]
|
||||||
go: ["1.23.x"]
|
go: ["1.24.x"]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -21,6 +21,6 @@ jobs:
|
|||||||
- run: "go test -race ./..."
|
- run: "go test -race ./..."
|
||||||
- uses: dominikh/staticcheck-action@v1.3.1
|
- uses: dominikh/staticcheck-action@v1.3.1
|
||||||
with:
|
with:
|
||||||
version: "2024.1.1"
|
version: "2025.1"
|
||||||
install-go: false
|
install-go: false
|
||||||
cache-key: ${{ matrix.go }}
|
cache-key: ${{ matrix.go }}
|
||||||
|
|||||||
@@ -178,7 +178,15 @@ func RunMobile(appConfig *AppConfig, appCallback *AppCallback, stopCh chan struc
|
|||||||
noConfigStart = false
|
noConfigStart = false
|
||||||
homedir = appConfig.HomeDir
|
homedir = appConfig.HomeDir
|
||||||
verbose = appConfig.Verbose
|
verbose = appConfig.Verbose
|
||||||
cdUID = appConfig.CdUID
|
if appConfig.ProvisionID != "" {
|
||||||
|
cdOrg = appConfig.ProvisionID
|
||||||
|
}
|
||||||
|
if appConfig.CustomHostname != "" {
|
||||||
|
customHostname = appConfig.CustomHostname
|
||||||
|
}
|
||||||
|
if appConfig.CdUID != "" {
|
||||||
|
cdUID = appConfig.CdUID
|
||||||
|
}
|
||||||
cdUpstreamProto = appConfig.UpstreamProto
|
cdUpstreamProto = appConfig.UpstreamProto
|
||||||
logPath = appConfig.LogPath
|
logPath = appConfig.LogPath
|
||||||
run(appCallback, stopCh)
|
run(appCallback, stopCh)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -188,6 +189,7 @@ func initRunCmd() *cobra.Command {
|
|||||||
runCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`)
|
runCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`)
|
||||||
_ = runCmd.Flags().MarkHidden("iface")
|
_ = runCmd.Flags().MarkHidden("iface")
|
||||||
runCmd.Flags().StringVarP(&cdUpstreamProto, "proto", "", ctrld.ResolverTypeDOH, `Control D upstream type, either "doh" or "doh3"`)
|
runCmd.Flags().StringVarP(&cdUpstreamProto, "proto", "", ctrld.ResolverTypeDOH, `Control D upstream type, either "doh" or "doh3"`)
|
||||||
|
runCmd.Flags().BoolVarP(&rfc1918, "rfc1918", "", false, "Listen on RFC1918 addresses when 127.0.0.1 is the only listener")
|
||||||
|
|
||||||
runCmd.FParseErrWhitelist = cobra.FParseErrWhitelist{UnknownFlags: true}
|
runCmd.FParseErrWhitelist = cobra.FParseErrWhitelist{UnknownFlags: true}
|
||||||
rootCmd.AddCommand(runCmd)
|
rootCmd.AddCommand(runCmd)
|
||||||
@@ -206,6 +208,7 @@ func initStartCmd() *cobra.Command {
|
|||||||
|
|
||||||
NOTE: running "ctrld start" without any arguments will start already installed ctrld service.`,
|
NOTE: running "ctrld start" without any arguments will start already installed ctrld service.`,
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
args = filterEmptyStrings(args)
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
return fmt.Errorf("'ctrld start' doesn't accept positional arguments\n" +
|
return fmt.Errorf("'ctrld start' doesn't accept positional arguments\n" +
|
||||||
"Use flags instead (e.g. --cd, --iface) or see 'ctrld start --help' for all options")
|
"Use flags instead (e.g. --cd, --iface) or see 'ctrld start --help' for all options")
|
||||||
@@ -219,6 +222,7 @@ NOTE: running "ctrld start" without any arguments will start already installed c
|
|||||||
sc := &service.Config{}
|
sc := &service.Config{}
|
||||||
*sc = *svcConfig
|
*sc = *svcConfig
|
||||||
osArgs := os.Args[2:]
|
osArgs := os.Args[2:]
|
||||||
|
osArgs = filterEmptyStrings(osArgs)
|
||||||
if os.Args[1] == "service" {
|
if os.Args[1] == "service" {
|
||||||
osArgs = os.Args[3:]
|
osArgs = os.Args[3:]
|
||||||
}
|
}
|
||||||
@@ -528,6 +532,7 @@ NOTE: running "ctrld start" without any arguments will start already installed c
|
|||||||
startCmd.Flags().BoolVarP(&skipSelfChecks, "skip_self_checks", "", false, `Skip self checks after installing ctrld service`)
|
startCmd.Flags().BoolVarP(&skipSelfChecks, "skip_self_checks", "", false, `Skip self checks after installing ctrld service`)
|
||||||
startCmd.Flags().BoolVarP(&startOnly, "start_only", "", false, "Do not install new service")
|
startCmd.Flags().BoolVarP(&startOnly, "start_only", "", false, "Do not install new service")
|
||||||
_ = startCmd.Flags().MarkHidden("start_only")
|
_ = startCmd.Flags().MarkHidden("start_only")
|
||||||
|
startCmd.Flags().BoolVarP(&rfc1918, "rfc1918", "", false, "Listen on RFC1918 addresses when 127.0.0.1 is the only listener")
|
||||||
|
|
||||||
routerCmd := &cobra.Command{
|
routerCmd := &cobra.Command{
|
||||||
Use: "setup",
|
Use: "setup",
|
||||||
@@ -566,6 +571,7 @@ NOTE: running "ctrld start" without any arguments will start already installed c
|
|||||||
|
|
||||||
NOTE: running "ctrld start" without any arguments will start already installed ctrld service.`,
|
NOTE: running "ctrld start" without any arguments will start already installed ctrld service.`,
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
args = filterEmptyStrings(args)
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
return fmt.Errorf("'ctrld start' doesn't accept positional arguments\n" +
|
return fmt.Errorf("'ctrld start' doesn't accept positional arguments\n" +
|
||||||
"Use flags instead (e.g. --cd, --iface) or see 'ctrld start --help' for all options")
|
"Use flags instead (e.g. --cd, --iface) or see 'ctrld start --help' for all options")
|
||||||
@@ -1381,3 +1387,11 @@ func initServicesCmd(commands ...*cobra.Command) *cobra.Command {
|
|||||||
|
|
||||||
return serviceCmd
|
return serviceCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filterEmptyStrings removes empty strings from a slice of strings.
|
||||||
|
// It returns a new slice containing only non-empty strings.
|
||||||
|
func filterEmptyStrings(slice []string) []string {
|
||||||
|
return slices.DeleteFunc(slice, func(s string) bool {
|
||||||
|
return s == ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -84,13 +84,7 @@ type upstreamForResult struct {
|
|||||||
srcAddr string
|
srcAddr string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *prog) serveDNS(mainCtx context.Context, listenerNum string) error {
|
func (p *prog) serveDNS(listenerNum string) error {
|
||||||
// Start network monitoring
|
|
||||||
if err := p.monitorNetworkChanges(mainCtx); err != nil {
|
|
||||||
mainLog.Load().Error().Err(err).Msg("Failed to start network monitoring")
|
|
||||||
// Don't return here as we still want DNS service to run
|
|
||||||
}
|
|
||||||
|
|
||||||
listenerConfig := p.cfg.Listener[listenerNum]
|
listenerConfig := p.cfg.Listener[listenerNum]
|
||||||
// make sure ip is allocated
|
// make sure ip is allocated
|
||||||
if allocErr := p.allocateIP(listenerConfig.IP); allocErr != nil {
|
if allocErr := p.allocateIP(listenerConfig.IP); allocErr != nil {
|
||||||
@@ -213,8 +207,8 @@ func (p *prog) serveDNS(mainCtx context.Context, listenerNum string) error {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// When we spawn a listener on 127.0.0.1, also spawn listeners on the RFC1918
|
// When we spawn a listener on 127.0.0.1, also spawn listeners on the RFC1918 addresses of the machine
|
||||||
// addresses of the machine. So ctrld could receive queries from LAN clients.
|
// if explicitly set via setting rfc1918 flag, so ctrld could receive queries from LAN clients.
|
||||||
if needRFC1918Listeners(listenerConfig) {
|
if needRFC1918Listeners(listenerConfig) {
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
for _, addr := range ctrld.Rfc1918Addresses() {
|
for _, addr := range ctrld.Rfc1918Addresses() {
|
||||||
@@ -1045,7 +1039,7 @@ func (p *prog) queryFromSelf(ip string) bool {
|
|||||||
// needRFC1918Listeners reports whether ctrld need to spawn listener for RFC 1918 addresses.
|
// needRFC1918Listeners reports whether ctrld need to spawn listener for RFC 1918 addresses.
|
||||||
// This is helpful for non-desktop platforms to receive queries from LAN clients.
|
// This is helpful for non-desktop platforms to receive queries from LAN clients.
|
||||||
func needRFC1918Listeners(lc *ctrld.ListenerConfig) bool {
|
func needRFC1918Listeners(lc *ctrld.ListenerConfig) bool {
|
||||||
return lc.IP == "127.0.0.1" && lc.Port == 53 && !ctrld.IsDesktopPlatform()
|
return rfc1918 && lc.IP == "127.0.0.1" && lc.Port == 53
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipFromARPA parses a FQDN arpa domain and return the IP address if valid.
|
// ipFromARPA parses a FQDN arpa domain and return the IP address if valid.
|
||||||
@@ -1187,7 +1181,7 @@ func FlushDNSCache() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// monitorNetworkChanges starts monitoring for network interface changes
|
// monitorNetworkChanges starts monitoring for network interface changes
|
||||||
func (p *prog) monitorNetworkChanges(ctx context.Context) error {
|
func (p *prog) monitorNetworkChanges() error {
|
||||||
mon, err := netmon.New(func(format string, args ...any) {
|
mon, err := netmon.New(func(format string, args ...any) {
|
||||||
// Always fetch the latest logger (and inject the prefix)
|
// Always fetch the latest logger (and inject the prefix)
|
||||||
mainLog.Load().Printf("netmon: "+format, args...)
|
mainLog.Load().Printf("netmon: "+format, args...)
|
||||||
@@ -1406,9 +1400,6 @@ func (p *prog) checkUpstreamOnce(upstream string, uc *ctrld.UpstreamConfig) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := new(dns.Msg)
|
|
||||||
msg.SetQuestion(".", dns.TypeNS)
|
|
||||||
|
|
||||||
timeout := 1000 * time.Millisecond
|
timeout := 1000 * time.Millisecond
|
||||||
if uc.Timeout > 0 {
|
if uc.Timeout > 0 {
|
||||||
timeout = time.Millisecond * time.Duration(uc.Timeout)
|
timeout = time.Millisecond * time.Duration(uc.Timeout)
|
||||||
@@ -1422,6 +1413,7 @@ func (p *prog) checkUpstreamOnce(upstream string, uc *ctrld.UpstreamConfig) erro
|
|||||||
mainLog.Load().Debug().Msgf("Rebootstrapping resolver for upstream: %s", upstream)
|
mainLog.Load().Debug().Msgf("Rebootstrapping resolver for upstream: %s", upstream)
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
msg := uc.VerifyMsg()
|
||||||
_, err = resolver.Resolve(ctx, msg)
|
_, err = resolver.Resolve(ctx, msg)
|
||||||
duration := time.Since(start)
|
duration := time.Since(start)
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ type AppCallback struct {
|
|||||||
|
|
||||||
// AppConfig allows overwriting ctrld cli flags from mobile platforms.
|
// AppConfig allows overwriting ctrld cli flags from mobile platforms.
|
||||||
type AppConfig struct {
|
type AppConfig struct {
|
||||||
CdUID string
|
CdUID string
|
||||||
HomeDir string
|
ProvisionID string
|
||||||
UpstreamProto string
|
CustomHostname string
|
||||||
Verbose int
|
HomeDir string
|
||||||
LogPath string
|
UpstreamProto string
|
||||||
|
Verbose int
|
||||||
|
LogPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ var (
|
|||||||
skipSelfChecks bool
|
skipSelfChecks bool
|
||||||
cleanup bool
|
cleanup bool
|
||||||
startOnly bool
|
startOnly bool
|
||||||
|
rfc1918 bool
|
||||||
|
|
||||||
mainLog atomic.Pointer[zerolog.Logger]
|
mainLog atomic.Pointer[zerolog.Logger]
|
||||||
consoleWriter zerolog.ConsoleWriter
|
consoleWriter zerolog.ConsoleWriter
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import (
|
|||||||
"github.com/Control-D-Inc/ctrld/internal/controld"
|
"github.com/Control-D-Inc/ctrld/internal/controld"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/dnscache"
|
"github.com/Control-D-Inc/ctrld/internal/dnscache"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/router"
|
"github.com/Control-D-Inc/ctrld/internal/router"
|
||||||
|
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -328,7 +329,7 @@ func (p *prog) apiConfigReload() {
|
|||||||
|
|
||||||
// Performing self-upgrade check for production version.
|
// Performing self-upgrade check for production version.
|
||||||
if isStable {
|
if isStable {
|
||||||
selfUpgradeCheck(resolverConfig.Ctrld.VersionTarget, curVer, &logger)
|
_ = selfUpgradeCheck(resolverConfig.Ctrld.VersionTarget, curVer, &logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resolverConfig.DeactivationPin != nil {
|
if resolverConfig.DeactivationPin != nil {
|
||||||
@@ -529,6 +530,15 @@ func (p *prog) run(reload bool, reloadCh chan struct{}) {
|
|||||||
go p.watchLinkState(ctx)
|
go p.watchLinkState(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !reload {
|
||||||
|
go func() {
|
||||||
|
// Start network monitoring
|
||||||
|
if err := p.monitorNetworkChanges(); err != nil {
|
||||||
|
mainLog.Load().Error().Err(err).Msg("Failed to start network monitoring")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
for listenerNum := range p.cfg.Listener {
|
for listenerNum := range p.cfg.Listener {
|
||||||
p.cfg.Listener[listenerNum].Init()
|
p.cfg.Listener[listenerNum].Init()
|
||||||
if !reload {
|
if !reload {
|
||||||
@@ -540,7 +550,7 @@ func (p *prog) run(reload bool, reloadCh chan struct{}) {
|
|||||||
}
|
}
|
||||||
addr := net.JoinHostPort(listenerConfig.IP, strconv.Itoa(listenerConfig.Port))
|
addr := net.JoinHostPort(listenerConfig.IP, strconv.Itoa(listenerConfig.Port))
|
||||||
mainLog.Load().Info().Msgf("starting DNS server on listener.%s: %s", listenerNum, addr)
|
mainLog.Load().Info().Msgf("starting DNS server on listener.%s: %s", listenerNum, addr)
|
||||||
if err := p.serveDNS(ctx, listenerNum); err != nil {
|
if err := p.serveDNS(listenerNum); err != nil {
|
||||||
mainLog.Load().Fatal().Err(err).Msgf("unable to start dns proxy on listener.%s", listenerNum)
|
mainLog.Load().Fatal().Err(err).Msgf("unable to start dns proxy on listener.%s", listenerNum)
|
||||||
}
|
}
|
||||||
mainLog.Load().Debug().Msgf("end of serveDNS listener.%s: %s", listenerNum, addr)
|
mainLog.Load().Debug().Msgf("end of serveDNS listener.%s: %s", listenerNum, addr)
|
||||||
@@ -607,6 +617,12 @@ func (p *prog) setupClientInfoDiscover(selfIP string) {
|
|||||||
format := ctrld.LeaseFileFormat(p.cfg.Service.DHCPLeaseFileFormat)
|
format := ctrld.LeaseFileFormat(p.cfg.Service.DHCPLeaseFileFormat)
|
||||||
p.ciTable.AddLeaseFile(leaseFile, format)
|
p.ciTable.AddLeaseFile(leaseFile, format)
|
||||||
}
|
}
|
||||||
|
if leaseFiles := dnsmasq.AdditionalLeaseFiles(); len(leaseFiles) > 0 {
|
||||||
|
mainLog.Load().Debug().Msgf("watching additional lease files: %v", leaseFiles)
|
||||||
|
for _, leaseFile := range leaseFiles {
|
||||||
|
p.ciTable.AddLeaseFile(leaseFile, ctrld.Dnsmasq)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// runClientInfoDiscover runs the client info discover.
|
// runClientInfoDiscover runs the client info discover.
|
||||||
@@ -1467,14 +1483,15 @@ func selfUninstallCheck(uninstallErr error, p *prog, logger zerolog.Logger) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// selfUpgradeCheck checks if the version target vt is greater
|
// shouldUpgrade checks if the version target vt is greater than the current one cv.
|
||||||
// than the current one cv, perform self-upgrade then.
|
// Major version upgrades are not allowed to prevent breaking changes.
|
||||||
//
|
//
|
||||||
// The callers must ensure curVer and logger are non-nil.
|
// The callers must ensure curVer and logger are non-nil.
|
||||||
func selfUpgradeCheck(vt string, cv *semver.Version, logger *zerolog.Logger) {
|
// Returns true if upgrade is allowed, false otherwise.
|
||||||
|
func shouldUpgrade(vt string, cv *semver.Version, logger *zerolog.Logger) bool {
|
||||||
if vt == "" {
|
if vt == "" {
|
||||||
logger.Debug().Msg("no version target set, skipped checking self-upgrade")
|
logger.Debug().Msg("no version target set, skipped checking self-upgrade")
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
vts := vt
|
vts := vt
|
||||||
if !strings.HasPrefix(vts, "v") {
|
if !strings.HasPrefix(vts, "v") {
|
||||||
@@ -1483,28 +1500,58 @@ func selfUpgradeCheck(vt string, cv *semver.Version, logger *zerolog.Logger) {
|
|||||||
targetVer, err := semver.NewVersion(vts)
|
targetVer, err := semver.NewVersion(vts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn().Err(err).Msgf("invalid target version, skipped self-upgrade: %s", vt)
|
logger.Warn().Err(err).Msgf("invalid target version, skipped self-upgrade: %s", vt)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent major version upgrades to avoid breaking changes
|
||||||
|
if targetVer.Major() != cv.Major() {
|
||||||
|
logger.Warn().
|
||||||
|
Str("target", vt).
|
||||||
|
Str("current", cv.String()).
|
||||||
|
Msgf("major version upgrade not allowed (target: %d, current: %d), skipped self-upgrade", targetVer.Major(), cv.Major())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if !targetVer.GreaterThan(cv) {
|
if !targetVer.GreaterThan(cv) {
|
||||||
logger.Debug().
|
logger.Debug().
|
||||||
Str("target", vt).
|
Str("target", vt).
|
||||||
Str("current", cv.String()).
|
Str("current", cv.String()).
|
||||||
Msgf("target version is not greater than current one, skipped self-upgrade")
|
Msgf("target version is not greater than current one, skipped self-upgrade")
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// performUpgrade executes the self-upgrade command.
|
||||||
|
// Returns true if upgrade was initiated successfully, false otherwise.
|
||||||
|
func performUpgrade(vt string) bool {
|
||||||
exe, err := os.Executable()
|
exe, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mainLog.Load().Error().Err(err).Msg("failed to get executable path, skipped self-upgrade")
|
mainLog.Load().Error().Err(err).Msg("failed to get executable path, skipped self-upgrade")
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
cmd := exec.Command(exe, "upgrade", "prod", "-vv")
|
cmd := exec.Command(exe, "upgrade", "prod", "-vv")
|
||||||
cmd.SysProcAttr = sysProcAttrForDetachedChildProcess()
|
cmd.SysProcAttr = sysProcAttrForDetachedChildProcess()
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
mainLog.Load().Error().Err(err).Msg("failed to start self-upgrade")
|
mainLog.Load().Error().Err(err).Msg("failed to start self-upgrade")
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
mainLog.Load().Debug().Msgf("self-upgrade triggered, version target: %s", vts)
|
mainLog.Load().Debug().Msgf("self-upgrade triggered, version target: %s", vt)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// selfUpgradeCheck checks if the version target vt is greater
|
||||||
|
// than the current one cv, perform self-upgrade then.
|
||||||
|
// Major version upgrades are not allowed to prevent breaking changes.
|
||||||
|
//
|
||||||
|
// The callers must ensure curVer and logger are non-nil.
|
||||||
|
// Returns true if upgrade is allowed and should proceed, false otherwise.
|
||||||
|
func selfUpgradeCheck(vt string, cv *semver.Version, logger *zerolog.Logger) bool {
|
||||||
|
if shouldUpgrade(vt, cv, logger) {
|
||||||
|
return performUpgrade(vt)
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// leakOnUpstreamFailure reports whether ctrld should initiate a recovery flow
|
// leakOnUpstreamFailure reports whether ctrld should initiate a recovery flow
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if isAndroid() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r, err := newLoopbackOSConfigurator(); err == nil {
|
if r, err := newLoopbackOSConfigurator(); err == nil {
|
||||||
useSystemdResolved = r.Mode() == "systemd-resolved"
|
useSystemdResolved = r.Mode() == "systemd-resolved"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Control-D-Inc/ctrld"
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/Control-D-Inc/ctrld"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_prog_dnsWatchdogEnabled(t *testing.T) {
|
func Test_prog_dnsWatchdogEnabled(t *testing.T) {
|
||||||
@@ -55,3 +59,215 @@ func Test_prog_dnsWatchdogInterval(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_shouldUpgrade(t *testing.T) {
|
||||||
|
// Helper function to create a version
|
||||||
|
makeVersion := func(v string) *semver.Version {
|
||||||
|
ver, err := semver.NewVersion(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create version %s: %v", v, err)
|
||||||
|
}
|
||||||
|
return ver
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
versionTarget string
|
||||||
|
currentVersion *semver.Version
|
||||||
|
shouldUpgrade bool
|
||||||
|
description string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty version target",
|
||||||
|
versionTarget: "",
|
||||||
|
currentVersion: makeVersion("v1.0.0"),
|
||||||
|
shouldUpgrade: false,
|
||||||
|
description: "should skip upgrade when version target is empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid version target",
|
||||||
|
versionTarget: "invalid-version",
|
||||||
|
currentVersion: makeVersion("v1.0.0"),
|
||||||
|
shouldUpgrade: false,
|
||||||
|
description: "should skip upgrade when version target is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same version",
|
||||||
|
versionTarget: "v1.0.0",
|
||||||
|
currentVersion: makeVersion("v1.0.0"),
|
||||||
|
shouldUpgrade: false,
|
||||||
|
description: "should skip upgrade when target version equals current version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "older version",
|
||||||
|
versionTarget: "v1.0.0",
|
||||||
|
currentVersion: makeVersion("v1.1.0"),
|
||||||
|
shouldUpgrade: false,
|
||||||
|
description: "should skip upgrade when target version is older than current version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "patch upgrade allowed",
|
||||||
|
versionTarget: "v1.0.1",
|
||||||
|
currentVersion: makeVersion("v1.0.0"),
|
||||||
|
shouldUpgrade: true,
|
||||||
|
description: "should allow patch version upgrade within same major version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "minor upgrade allowed",
|
||||||
|
versionTarget: "v1.1.0",
|
||||||
|
currentVersion: makeVersion("v1.0.0"),
|
||||||
|
shouldUpgrade: true,
|
||||||
|
description: "should allow minor version upgrade within same major version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "major upgrade blocked",
|
||||||
|
versionTarget: "v2.0.0",
|
||||||
|
currentVersion: makeVersion("v1.0.0"),
|
||||||
|
shouldUpgrade: false,
|
||||||
|
description: "should block major version upgrade",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "major downgrade blocked",
|
||||||
|
versionTarget: "v1.0.0",
|
||||||
|
currentVersion: makeVersion("v2.0.0"),
|
||||||
|
shouldUpgrade: false,
|
||||||
|
description: "should block major version downgrade",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version without v prefix",
|
||||||
|
versionTarget: "1.0.1",
|
||||||
|
currentVersion: makeVersion("v1.0.0"),
|
||||||
|
shouldUpgrade: true,
|
||||||
|
description: "should handle version target without v prefix",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "complex version upgrade allowed",
|
||||||
|
versionTarget: "v1.5.3",
|
||||||
|
currentVersion: makeVersion("v1.4.2"),
|
||||||
|
shouldUpgrade: true,
|
||||||
|
description: "should allow complex version upgrade within same major version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "complex major upgrade blocked",
|
||||||
|
versionTarget: "v3.1.0",
|
||||||
|
currentVersion: makeVersion("v2.5.3"),
|
||||||
|
shouldUpgrade: false,
|
||||||
|
description: "should block complex major version upgrade",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pre-release version upgrade allowed",
|
||||||
|
versionTarget: "v1.0.1-beta.1",
|
||||||
|
currentVersion: makeVersion("v1.0.0"),
|
||||||
|
shouldUpgrade: true,
|
||||||
|
description: "should allow pre-release version upgrade within same major version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pre-release major upgrade blocked",
|
||||||
|
versionTarget: "v2.0.0-alpha.1",
|
||||||
|
currentVersion: makeVersion("v1.0.0"),
|
||||||
|
shouldUpgrade: false,
|
||||||
|
description: "should block pre-release major version upgrade",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
// Create test logger
|
||||||
|
testLogger := zerolog.New(zerolog.NewTestWriter(t)).With().Logger()
|
||||||
|
|
||||||
|
// Call the function and capture the result
|
||||||
|
result := shouldUpgrade(tc.versionTarget, tc.currentVersion, &testLogger)
|
||||||
|
|
||||||
|
// Assert the expected result
|
||||||
|
assert.Equal(t, tc.shouldUpgrade, result, tc.description)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_selfUpgradeCheck(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("skipped due to Windows file locking issue on Github Action runners")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create a version
|
||||||
|
makeVersion := func(v string) *semver.Version {
|
||||||
|
ver, err := semver.NewVersion(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create version %s: %v", v, err)
|
||||||
|
}
|
||||||
|
return ver
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
versionTarget string
|
||||||
|
currentVersion *semver.Version
|
||||||
|
shouldUpgrade bool
|
||||||
|
description string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "upgrade allowed",
|
||||||
|
versionTarget: "v1.0.1",
|
||||||
|
currentVersion: makeVersion("v1.0.0"),
|
||||||
|
shouldUpgrade: true,
|
||||||
|
description: "should allow upgrade and attempt to perform it",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "upgrade blocked",
|
||||||
|
versionTarget: "v2.0.0",
|
||||||
|
currentVersion: makeVersion("v1.0.0"),
|
||||||
|
shouldUpgrade: false,
|
||||||
|
description: "should block upgrade and not attempt to perform it",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
// Create test logger
|
||||||
|
testLogger := zerolog.New(zerolog.NewTestWriter(t)).With().Logger()
|
||||||
|
|
||||||
|
// Call the function and capture the result
|
||||||
|
result := selfUpgradeCheck(tc.versionTarget, tc.currentVersion, &testLogger)
|
||||||
|
|
||||||
|
// Assert the expected result
|
||||||
|
assert.Equal(t, tc.shouldUpgrade, result, tc.description)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_performUpgrade(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("skipped due to Windows file locking issue on Github Action runners")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
versionTarget string
|
||||||
|
expectedResult bool
|
||||||
|
description string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid version target",
|
||||||
|
versionTarget: "v1.0.1",
|
||||||
|
expectedResult: true,
|
||||||
|
description: "should attempt to perform upgrade with valid version target",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty version target",
|
||||||
|
versionTarget: "",
|
||||||
|
expectedResult: true,
|
||||||
|
description: "should attempt to perform upgrade even with empty version target",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
// Call the function and capture the result
|
||||||
|
result := performUpgrade(tc.versionTarget)
|
||||||
|
assert.Equal(t, tc.expectedResult, result, tc.description)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,15 +28,17 @@ type AppCallback interface {
|
|||||||
// Start configures utility with config.toml from provided directory.
|
// Start configures utility with config.toml from provided directory.
|
||||||
// This function will block until Stop is called
|
// This function will block until Stop is called
|
||||||
// Check port availability prior to calling it.
|
// Check port availability prior to calling it.
|
||||||
func (c *Controller) Start(CdUID string, HomeDir string, UpstreamProto string, logLevel int, logPath string) {
|
func (c *Controller) Start(CdUID string, ProvisionID string, CustomHostname string, HomeDir string, UpstreamProto string, logLevel int, logPath string) {
|
||||||
if c.stopCh == nil {
|
if c.stopCh == nil {
|
||||||
c.stopCh = make(chan struct{})
|
c.stopCh = make(chan struct{})
|
||||||
c.Config = cli.AppConfig{
|
c.Config = cli.AppConfig{
|
||||||
CdUID: CdUID,
|
CdUID: CdUID,
|
||||||
HomeDir: HomeDir,
|
ProvisionID: ProvisionID,
|
||||||
UpstreamProto: UpstreamProto,
|
CustomHostname: CustomHostname,
|
||||||
Verbose: logLevel,
|
HomeDir: HomeDir,
|
||||||
LogPath: logPath,
|
UpstreamProto: UpstreamProto,
|
||||||
|
Verbose: logLevel,
|
||||||
|
LogPath: logPath,
|
||||||
}
|
}
|
||||||
appCallback := mapCallback(c.AppCallback)
|
appCallback := mapCallback(c.AppCallback)
|
||||||
cli.RunMobile(&c.Config, &appCallback, c.stopCh)
|
cli.RunMobile(&c.Config, &appCallback, c.stopCh)
|
||||||
|
|||||||
@@ -358,6 +358,15 @@ func (uc *UpstreamConfig) Init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyMsg creates and returns a new DNS message could be used for testing upstream health.
|
||||||
|
func (uc *UpstreamConfig) VerifyMsg() *dns.Msg {
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.RecursionDesired = true
|
||||||
|
msg.SetQuestion(".", dns.TypeNS)
|
||||||
|
msg.SetEdns0(4096, false) // ensure handling of large DNS response
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyDomain returns the domain name that could be resolved by the upstream endpoint.
|
// VerifyDomain returns the domain name that could be resolved by the upstream endpoint.
|
||||||
// It returns empty for non-ControlD upstream endpoint.
|
// It returns empty for non-ControlD upstream endpoint.
|
||||||
func (uc *UpstreamConfig) VerifyDomain() string {
|
func (uc *UpstreamConfig) VerifyDomain() string {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func (uc *UpstreamConfig) setupDOH3Transport() {
|
|||||||
func (uc *UpstreamConfig) newDOH3Transport(addrs []string) http.RoundTripper {
|
func (uc *UpstreamConfig) newDOH3Transport(addrs []string) http.RoundTripper {
|
||||||
rt := &http3.Transport{}
|
rt := &http3.Transport{}
|
||||||
rt.TLSClientConfig = &tls.Config{RootCAs: uc.certPool}
|
rt.TLSClientConfig = &tls.Config{RootCAs: uc.certPool}
|
||||||
rt.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
rt.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
||||||
_, port, _ := net.SplitHostPort(addr)
|
_, port, _ := net.SplitHostPort(addr)
|
||||||
// if we have a bootstrap ip set, use it to avoid DNS lookup
|
// if we have a bootstrap ip set, use it to avoid DNS lookup
|
||||||
if uc.BootstrapIP != "" {
|
if uc.BootstrapIP != "" {
|
||||||
@@ -96,14 +96,14 @@ func (uc *UpstreamConfig) doh3Transport(dnsType uint16) http.RoundTripper {
|
|||||||
// - quic dialer is different with net.Dialer
|
// - quic dialer is different with net.Dialer
|
||||||
// - simplification for quic free version
|
// - simplification for quic free version
|
||||||
type parallelDialerResult struct {
|
type parallelDialerResult struct {
|
||||||
conn quic.EarlyConnection
|
conn *quic.Conn
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
type quicParallelDialer struct{}
|
type quicParallelDialer struct{}
|
||||||
|
|
||||||
// Dial performs parallel dialing to the given address list.
|
// Dial performs parallel dialing to the given address list.
|
||||||
func (d *quicParallelDialer) Dial(ctx context.Context, addrs []string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
func (d *quicParallelDialer) Dial(ctx context.Context, addrs []string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
||||||
if len(addrs) == 0 {
|
if len(addrs) == 0 {
|
||||||
return nil, errors.New("empty addresses")
|
return nil, errors.New("empty addresses")
|
||||||
}
|
}
|
||||||
|
|||||||
42
docs/known-issues.md
Normal file
42
docs/known-issues.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Known Issues
|
||||||
|
|
||||||
|
This document outlines known issues with ctrld and their current status, workarounds, and recommendations.
|
||||||
|
|
||||||
|
## macOS (Darwin) Issues
|
||||||
|
|
||||||
|
### Self-Upgrade Issue on Darwin 15.5
|
||||||
|
|
||||||
|
**Issue**: ctrld self-upgrading functionality may not work on macOS Darwin 15.5.
|
||||||
|
|
||||||
|
**Status**: Under investigation
|
||||||
|
|
||||||
|
**Description**: Users on macOS Darwin 15.5 may experience issues when ctrld attempts to perform automatic self-upgrades. The upgrade process would be triggered, but ctrld won't be upgraded.
|
||||||
|
|
||||||
|
**Workarounds**:
|
||||||
|
1. **Recommended**: Upgrade your macOS system to Darwin 15.6 or later, which has been tested and verified to work correctly with ctrld self-upgrade functionality.
|
||||||
|
2. **Alternative**: Run `ctrld upgrade prod` directly to manually upgrade ctrld to the latest version on Darwin 15.5.
|
||||||
|
|
||||||
|
**Affected Versions**: ctrld v1.4.2 and later on macOS Darwin 15.5
|
||||||
|
|
||||||
|
**Last Updated**: 05/09/2025
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributing to Known Issues
|
||||||
|
|
||||||
|
If you encounter an issue not listed here, please:
|
||||||
|
|
||||||
|
1. Check the [GitHub Issues](https://github.com/Control-D-Inc/ctrld/issues) to see if it's already reported
|
||||||
|
2. If not reported, create a new issue with:
|
||||||
|
- Detailed description of the problem
|
||||||
|
- Steps to reproduce
|
||||||
|
- Expected vs actual behavior
|
||||||
|
- System information (OS, version, architecture)
|
||||||
|
- ctrld version
|
||||||
|
|
||||||
|
## Issue Status Legend
|
||||||
|
|
||||||
|
- **Under investigation**: Issue is confirmed and being analyzed
|
||||||
|
- **Workaround available**: Temporary solution exists while permanent fix is developed
|
||||||
|
- **Fixed**: Issue has been resolved in a specific version
|
||||||
|
- **Won't fix**: Issue is acknowledged but will not be addressed due to technical limitations or design decisions
|
||||||
@@ -142,7 +142,7 @@ func (s *testQUICServer) serve(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleConnection manages an individual QUIC connection by accepting and handling incoming streams in separate goroutines.
|
// handleConnection manages an individual QUIC connection by accepting and handling incoming streams in separate goroutines.
|
||||||
func (s *testQUICServer) handleConnection(t *testing.T, conn quic.Connection) {
|
func (s *testQUICServer) handleConnection(t *testing.T, conn *quic.Conn) {
|
||||||
for {
|
for {
|
||||||
stream, err := conn.AcceptStream(context.Background())
|
stream, err := conn.AcceptStream(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -154,7 +154,7 @@ func (s *testQUICServer) handleConnection(t *testing.T, conn quic.Connection) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleStream processes a single QUIC stream, reads DNS messages, generates a response, and sends it back to the client.
|
// handleStream processes a single QUIC stream, reads DNS messages, generates a response, and sends it back to the client.
|
||||||
func (s *testQUICServer) handleStream(t *testing.T, stream quic.Stream) {
|
func (s *testQUICServer) handleStream(t *testing.T, stream *quic.Stream) {
|
||||||
defer stream.Close()
|
defer stream.Close()
|
||||||
|
|
||||||
// Read length (2 bytes)
|
// Read length (2 bytes)
|
||||||
|
|||||||
7
go.mod
7
go.mod
@@ -29,7 +29,7 @@ require (
|
|||||||
github.com/prometheus/client_golang v1.19.1
|
github.com/prometheus/client_golang v1.19.1
|
||||||
github.com/prometheus/client_model v0.5.0
|
github.com/prometheus/client_model v0.5.0
|
||||||
github.com/prometheus/prom2json v1.3.3
|
github.com/prometheus/prom2json v1.3.3
|
||||||
github.com/quic-go/quic-go v0.48.2
|
github.com/quic-go/quic-go v0.54.0
|
||||||
github.com/rs/zerolog v1.28.0
|
github.com/rs/zerolog v1.28.0
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
@@ -54,10 +54,8 @@ require (
|
|||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
@@ -74,7 +72,6 @@ require (
|
|||||||
github.com/mdlayher/packet v1.1.2 // indirect
|
github.com/mdlayher/packet v1.1.2 // indirect
|
||||||
github.com/mdlayher/socket v0.5.0 // indirect
|
github.com/mdlayher/socket v0.5.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
@@ -89,7 +86,7 @@ require (
|
|||||||
github.com/subosito/gotenv v1.4.2 // indirect
|
github.com/subosito/gotenv v1.4.2 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
|
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
go.uber.org/mock v0.4.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
|
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/crypto v0.36.0 // indirect
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
|
|||||||
20
go.sum
20
go.sum
@@ -91,8 +91,6 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
|
|||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg=
|
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg=
|
||||||
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA=
|
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||||
@@ -103,8 +101,6 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j
|
|||||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
||||||
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
||||||
@@ -162,8 +158,6 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
|
|||||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
|
||||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -242,10 +236,6 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
|
|||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
|
||||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
|
||||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
@@ -271,8 +261,8 @@ github.com/prometheus/prom2json v1.3.3 h1:IYfSMiZ7sSOfliBoo89PcufjWO4eAR0gznGcET
|
|||||||
github.com/prometheus/prom2json v1.3.3/go.mod h1:Pv4yIPktEkK7btWsrUTWDDDrnpUrAELaOCj+oFwlgmc=
|
github.com/prometheus/prom2json v1.3.3/go.mod h1:Pv4yIPktEkK7btWsrUTWDDDrnpUrAELaOCj+oFwlgmc=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||||
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
@@ -330,8 +320,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8=
|
go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8=
|
||||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
@@ -505,8 +495,6 @@ golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
|||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
|||||||
@@ -16,4 +16,5 @@ var clientInfoFiles = map[string]ctrld.LeaseFileFormat{
|
|||||||
"/var/dhcpd/var/db/dhcpd.leases": ctrld.IscDhcpd, // Pfsense
|
"/var/dhcpd/var/db/dhcpd.leases": ctrld.IscDhcpd, // Pfsense
|
||||||
"/home/pi/.router/run/dhcp/dnsmasq.leases": ctrld.Dnsmasq, // Firewalla
|
"/home/pi/.router/run/dhcp/dnsmasq.leases": ctrld.Dnsmasq, // Firewalla
|
||||||
"/var/lib/kea/dhcp4.leases": ctrld.KeaDHCP4, // Pfsense
|
"/var/lib/kea/dhcp4.leases": ctrld.KeaDHCP4, // Pfsense
|
||||||
|
"/var/db/dnsmasq.leases": ctrld.Dnsmasq, // OPNsense
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ func (m *mdns) lookupIPByHostname(name string, v6 bool) string {
|
|||||||
if value == name {
|
if value == name {
|
||||||
if addr, err := netip.ParseAddr(key.(string)); err == nil && addr.Is6() == v6 {
|
if addr, err := netip.ParseAddr(key.(string)); err == nil && addr.Is6() == v6 {
|
||||||
ip = addr.String()
|
ip = addr.String()
|
||||||
//lint:ignore S1008 This is used for readable.
|
|
||||||
if addr.IsLoopback() { // Continue searching if this is loopback address.
|
if addr.IsLoopback() { // Continue searching if this is loopback address.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ func (p *ptrDiscover) lookupIPByHostname(name string, v6 bool) string {
|
|||||||
if value == name {
|
if value == name {
|
||||||
if addr, err := netip.ParseAddr(key.(string)); err == nil && addr.Is6() == v6 {
|
if addr, err := netip.ParseAddr(key.(string)); err == nil && addr.Is6() == v6 {
|
||||||
ip = addr.String()
|
ip = addr.String()
|
||||||
//lint:ignore S1008 This is used for readable.
|
|
||||||
if addr.IsLoopback() { // Continue searching if this is loopback address.
|
if addr.IsLoopback() { // Continue searching if this is loopback address.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -120,8 +119,7 @@ func (p *ptrDiscover) lookupIPByHostname(name string, v6 bool) string {
|
|||||||
// is reachable, set p.serverDown to false, so p.lookupHostname can continue working.
|
// is reachable, set p.serverDown to false, so p.lookupHostname can continue working.
|
||||||
func (p *ptrDiscover) checkServer() {
|
func (p *ptrDiscover) checkServer() {
|
||||||
bo := backoff.NewBackoff("ptrDiscover", func(format string, args ...any) {}, time.Minute*5)
|
bo := backoff.NewBackoff("ptrDiscover", func(format string, args ...any) {}, time.Minute*5)
|
||||||
m := new(dns.Msg)
|
m := (&ctrld.UpstreamConfig{}).VerifyMsg()
|
||||||
m.SetQuestion(".", dns.TypeNS)
|
|
||||||
ping := func() error {
|
ping := func() error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,3 +29,62 @@ func interfaceNameFromReader(r io.Reader) (string, error) {
|
|||||||
}
|
}
|
||||||
return "", errors.New("not found")
|
return "", errors.New("not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdditionalConfigFiles returns a list of Dnsmasq configuration files found in the "/tmp/etc" directory.
|
||||||
|
func AdditionalConfigFiles() []string {
|
||||||
|
if paths, err := filepath.Glob("/tmp/etc/dnsmasq-*.conf"); err == nil {
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdditionalLeaseFiles returns a list of lease file paths corresponding to the Dnsmasq configuration files.
|
||||||
|
func AdditionalLeaseFiles() []string {
|
||||||
|
cfgFiles := AdditionalConfigFiles()
|
||||||
|
if len(cfgFiles) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
leaseFiles := make([]string, 0, len(cfgFiles))
|
||||||
|
for _, cfgFile := range cfgFiles {
|
||||||
|
if leaseFile := leaseFileFromConfigFileName(cfgFile); leaseFile != "" {
|
||||||
|
leaseFiles = append(leaseFiles, leaseFile)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
leaseFiles = append(leaseFiles, defaultLeaseFileFromConfigPath(cfgFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return leaseFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
// leaseFileFromConfigFileName retrieves the DHCP lease file path by reading and parsing the provided configuration file.
|
||||||
|
func leaseFileFromConfigFileName(cfgFile string) string {
|
||||||
|
if f, err := os.Open(cfgFile); err == nil {
|
||||||
|
return leaseFileFromReader(f)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// leaseFileFromReader parses the given io.Reader for the "dhcp-leasefile" configuration and returns its value as a string.
|
||||||
|
func leaseFileFromReader(r io.Reader) string {
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
before, after, found := strings.Cut(line, "=")
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if before == "dhcp-leasefile" {
|
||||||
|
return after
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultLeaseFileFromConfigPath generates the default lease file path based on the provided configuration file path.
|
||||||
|
func defaultLeaseFileFromConfigPath(path string) string {
|
||||||
|
name := filepath.Base(path)
|
||||||
|
return filepath.Join("/var/lib/misc", strings.TrimSuffix(name, ".conf")+".leases")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package dnsmasq
|
package dnsmasq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -44,3 +45,49 @@ interface=eth0
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_leaseFileFromReader(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
in io.Reader
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"default",
|
||||||
|
strings.NewReader(`
|
||||||
|
dhcp-script=/sbin/dhcpc_lease
|
||||||
|
dhcp-leasefile=/var/lib/misc/dnsmasq-1.leases
|
||||||
|
script-arp
|
||||||
|
`),
|
||||||
|
"/var/lib/misc/dnsmasq-1.leases",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"non-default",
|
||||||
|
strings.NewReader(`
|
||||||
|
dhcp-script=/sbin/dhcpc_lease
|
||||||
|
dhcp-leasefile=/tmp/var/lib/misc/dnsmasq-1.leases
|
||||||
|
script-arp
|
||||||
|
`),
|
||||||
|
"/tmp/var/lib/misc/dnsmasq-1.leases",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"missing",
|
||||||
|
strings.NewReader(`
|
||||||
|
dhcp-script=/sbin/dhcpc_lease
|
||||||
|
script-arp
|
||||||
|
`),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if got := leaseFileFromReader(tc.in); got != tc.expected {
|
||||||
|
t.Errorf("leaseFileFromReader() = %v, want %v", got, tc.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -26,9 +27,13 @@ max-cache-ttl=0
|
|||||||
{{- end}}
|
{{- end}}
|
||||||
`
|
`
|
||||||
|
|
||||||
const MerlinConfPath = "/tmp/etc/dnsmasq.conf"
|
const (
|
||||||
const MerlinJffsConfPath = "/jffs/configs/dnsmasq.conf"
|
MerlinConfPath = "/tmp/etc/dnsmasq.conf"
|
||||||
const MerlinPostConfPath = "/jffs/scripts/dnsmasq.postconf"
|
MerlinJffsConfDir = "/jffs/configs"
|
||||||
|
MerlinJffsConfPath = "/jffs/configs/dnsmasq.conf"
|
||||||
|
MerlinPostConfPath = "/jffs/scripts/dnsmasq.postconf"
|
||||||
|
)
|
||||||
|
|
||||||
const MerlinPostConfMarker = `# GENERATED BY ctrld - EOF`
|
const MerlinPostConfMarker = `# GENERATED BY ctrld - EOF`
|
||||||
const MerlinPostConfTmpl = `# GENERATED BY ctrld - DO NOT MODIFY
|
const MerlinPostConfTmpl = `# GENERATED BY ctrld - DO NOT MODIFY
|
||||||
|
|
||||||
@@ -159,3 +164,27 @@ func FirewallaSelfInterfaces() []*net.Interface {
|
|||||||
}
|
}
|
||||||
return ifaces
|
return ifaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ubios43ConfPath = "/run/dnsmasq.dhcp.conf.d"
|
||||||
|
ubios42ConfPath = "/run/dnsmasq.conf.d"
|
||||||
|
ubios43PidFile = "/run/dnsmasq-main.pid"
|
||||||
|
ubios42PidFile = "/run/dnsmasq.pid"
|
||||||
|
UbiosConfName = "zzzctrld.conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UbiosConfPath returns the appropriate configuration path based on the system's directory structure.
|
||||||
|
func UbiosConfPath() string {
|
||||||
|
if st, _ := os.Stat(ubios43ConfPath); st != nil && st.IsDir() {
|
||||||
|
return ubios43ConfPath
|
||||||
|
}
|
||||||
|
return ubios42ConfPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// UbiosPidFile returns the appropriate dnsmasq pid file based on the system's directory structure.
|
||||||
|
func UbiosPidFile() string {
|
||||||
|
if st, _ := os.Stat(ubios43PidFile); st != nil && !st.IsDir() {
|
||||||
|
return ubios43PidFile
|
||||||
|
}
|
||||||
|
return ubios42PidFile
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
@@ -181,7 +182,7 @@ func ContentFilteringEnabled() bool {
|
|||||||
// DnsShieldEnabled reports whether DNS Shield is enabled.
|
// DnsShieldEnabled reports whether DNS Shield is enabled.
|
||||||
// See: https://community.ui.com/releases/UniFi-OS-Dream-Machines-3-2-7/251dfc1e-f4dd-4264-a080-3be9d8b9e02b
|
// See: https://community.ui.com/releases/UniFi-OS-Dream-Machines-3-2-7/251dfc1e-f4dd-4264-a080-3be9d8b9e02b
|
||||||
func DnsShieldEnabled() bool {
|
func DnsShieldEnabled() bool {
|
||||||
buf, err := os.ReadFile("/var/run/dnsmasq.conf.d/dns.conf")
|
buf, err := os.ReadFile(filepath.Join(dnsmasq.UbiosConfPath(), "dns.conf"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
@@ -20,10 +21,18 @@ import (
|
|||||||
|
|
||||||
const Name = "merlin"
|
const Name = "merlin"
|
||||||
|
|
||||||
|
// nvramKvMap is a map of NVRAM key-value pairs used to configure and manage Merlin-specific settings.
|
||||||
var nvramKvMap = map[string]string{
|
var nvramKvMap = map[string]string{
|
||||||
"dnspriv_enable": "0", // Ensure Merlin native DoT disabled.
|
"dnspriv_enable": "0", // Ensure Merlin native DoT disabled.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dnsmasqConfig represents configuration paths for dnsmasq operations in Merlin firmware.
|
||||||
|
type dnsmasqConfig struct {
|
||||||
|
confPath string
|
||||||
|
jffsConfPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merlin represents a configuration handler for setting up and managing ctrld on Merlin routers.
|
||||||
type Merlin struct {
|
type Merlin struct {
|
||||||
cfg *ctrld.Config
|
cfg *ctrld.Config
|
||||||
}
|
}
|
||||||
@@ -33,18 +42,22 @@ func New(cfg *ctrld.Config) *Merlin {
|
|||||||
return &Merlin{cfg: cfg}
|
return &Merlin{cfg: cfg}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConfigureService configures the service based on the provided configuration. It returns an error if the configuration fails.
|
||||||
func (m *Merlin) ConfigureService(config *service.Config) error {
|
func (m *Merlin) ConfigureService(config *service.Config) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Install sets up the necessary configurations and services required for the Merlin instance to function properly.
|
||||||
func (m *Merlin) Install(_ *service.Config) error {
|
func (m *Merlin) Install(_ *service.Config) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Uninstall removes the ctrld-related configurations and services from the Merlin router and reverts to the original state.
|
||||||
func (m *Merlin) Uninstall(_ *service.Config) error {
|
func (m *Merlin) Uninstall(_ *service.Config) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PreRun prepares the Merlin instance for operation by waiting for essential services and directories to become available.
|
||||||
func (m *Merlin) PreRun() error {
|
func (m *Merlin) PreRun() error {
|
||||||
// Wait NTP ready.
|
// Wait NTP ready.
|
||||||
_ = m.Cleanup()
|
_ = m.Cleanup()
|
||||||
@@ -66,6 +79,7 @@ func (m *Merlin) PreRun() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup initializes and configures the Merlin instance for use, including setting up dnsmasq and necessary nvram settings.
|
||||||
func (m *Merlin) Setup() error {
|
func (m *Merlin) Setup() error {
|
||||||
if m.cfg.FirstListener().IsDirectDnsListener() {
|
if m.cfg.FirstListener().IsDirectDnsListener() {
|
||||||
return nil
|
return nil
|
||||||
@@ -79,35 +93,10 @@ func (m *Merlin) Setup() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy current dnsmasq config to /jffs/configs/dnsmasq.conf,
|
for _, cfg := range getDnsmasqConfigs() {
|
||||||
// Then we will run postconf script on this file.
|
if err := m.setupDnsmasq(cfg); err != nil {
|
||||||
//
|
return fmt.Errorf("failed to setup dnsmasq: config: %s, error: %w", cfg.confPath, err)
|
||||||
// Normally, adding postconf script is enough. However, we see
|
}
|
||||||
// reports on some Merlin devices that postconf scripts does not
|
|
||||||
// work, but manipulating the config directly via /jffs/configs does.
|
|
||||||
src, err := os.Open(dnsmasq.MerlinConfPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open dnsmasq config: %w", err)
|
|
||||||
}
|
|
||||||
defer src.Close()
|
|
||||||
|
|
||||||
dst, err := os.Create(dnsmasq.MerlinJffsConfPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create %s: %w", dnsmasq.MerlinJffsConfPath, err)
|
|
||||||
}
|
|
||||||
defer dst.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(dst, src); err != nil {
|
|
||||||
return fmt.Errorf("failed to copy current dnsmasq config: %w", err)
|
|
||||||
}
|
|
||||||
if err := dst.Close(); err != nil {
|
|
||||||
return fmt.Errorf("failed to save %s: %w", dnsmasq.MerlinJffsConfPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run postconf script on /jffs/configs/dnsmasq.conf directly.
|
|
||||||
cmd := exec.Command("/bin/sh", dnsmasq.MerlinPostConfPath, dnsmasq.MerlinJffsConfPath)
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
return fmt.Errorf("failed to run post conf: %s: %w", string(out), err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart dnsmasq service.
|
// Restart dnsmasq service.
|
||||||
@@ -122,6 +111,7 @@ func (m *Merlin) Setup() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup restores the original dnsmasq and nvram configurations and restarts dnsmasq if necessary.
|
||||||
func (m *Merlin) Cleanup() error {
|
func (m *Merlin) Cleanup() error {
|
||||||
if m.cfg.FirstListener().IsDirectDnsListener() {
|
if m.cfg.FirstListener().IsDirectDnsListener() {
|
||||||
return nil
|
return nil
|
||||||
@@ -143,9 +133,11 @@ func (m *Merlin) Cleanup() error {
|
|||||||
if err := os.WriteFile(dnsmasq.MerlinPostConfPath, merlinParsePostConf(buf), 0750); err != nil {
|
if err := os.WriteFile(dnsmasq.MerlinPostConfPath, merlinParsePostConf(buf), 0750); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Remove /jffs/configs/dnsmasq.conf file.
|
|
||||||
if err := os.Remove(dnsmasq.MerlinJffsConfPath); err != nil && !os.IsNotExist(err) {
|
for _, cfg := range getDnsmasqConfigs() {
|
||||||
return err
|
if err := m.cleanupDnsmasqJffs(cfg); err != nil {
|
||||||
|
return fmt.Errorf("failed to cleanup jffs dnsmasq: config: %s, error: %w", cfg.confPath, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Restart dnsmasq service.
|
// Restart dnsmasq service.
|
||||||
if err := restartDNSMasq(); err != nil {
|
if err := restartDNSMasq(); err != nil {
|
||||||
@@ -154,6 +146,54 @@ func (m *Merlin) Cleanup() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setupDnsmasq sets up dnsmasq configuration by writing postconf, copying configuration, and running a postconf script.
|
||||||
|
func (m *Merlin) setupDnsmasq(cfg *dnsmasqConfig) error {
|
||||||
|
src, err := os.Open(cfg.confPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil // nothing to do if conf file does not exist.
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open dnsmasq config: %w", err)
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
|
||||||
|
// Copy current dnsmasq config to cfg.jffsConfPath,
|
||||||
|
// Then we will run postconf script on this file.
|
||||||
|
//
|
||||||
|
// Normally, adding postconf script is enough. However, we see
|
||||||
|
// reports on some Merlin devices that postconf scripts does not
|
||||||
|
// work, but manipulating the config directly via /jffs/configs does.
|
||||||
|
dst, err := os.Create(cfg.jffsConfPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create %s: %w", cfg.jffsConfPath, err)
|
||||||
|
}
|
||||||
|
defer dst.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(dst, src); err != nil {
|
||||||
|
return fmt.Errorf("failed to copy current dnsmasq config: %w", err)
|
||||||
|
}
|
||||||
|
if err := dst.Close(); err != nil {
|
||||||
|
return fmt.Errorf("failed to save %s: %w", cfg.jffsConfPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run postconf script on cfg.jffsConfPath directly.
|
||||||
|
cmd := exec.Command("/bin/sh", dnsmasq.MerlinPostConfPath, cfg.jffsConfPath)
|
||||||
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
return fmt.Errorf("failed to run post conf: %s: %w", string(out), err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanupDnsmasqJffs removes the JFFS configuration file specified in the given dnsmasqConfig, if it exists.
|
||||||
|
func (m *Merlin) cleanupDnsmasqJffs(cfg *dnsmasqConfig) error {
|
||||||
|
// Remove cfg.jffsConfPath file.
|
||||||
|
if err := os.Remove(cfg.jffsConfPath); err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeDnsmasqPostconf writes the requireddnsmasqConfigs post-configuration for dnsmasq to enable custom DNS settings with ctrld.
|
||||||
func (m *Merlin) writeDnsmasqPostconf() error {
|
func (m *Merlin) writeDnsmasqPostconf() error {
|
||||||
buf, err := os.ReadFile(dnsmasq.MerlinPostConfPath)
|
buf, err := os.ReadFile(dnsmasq.MerlinPostConfPath)
|
||||||
// Already setup.
|
// Already setup.
|
||||||
@@ -179,6 +219,8 @@ func (m *Merlin) writeDnsmasqPostconf() error {
|
|||||||
return os.WriteFile(dnsmasq.MerlinPostConfPath, []byte(data), 0750)
|
return os.WriteFile(dnsmasq.MerlinPostConfPath, []byte(data), 0750)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// restartDNSMasq restarts the dnsmasq service by executing the appropriate system command using "service".
|
||||||
|
// Returns an error if the command fails or if there is an issue processing the command output.
|
||||||
func restartDNSMasq() error {
|
func restartDNSMasq() error {
|
||||||
if out, err := exec.Command("service", "restart_dnsmasq").CombinedOutput(); err != nil {
|
if out, err := exec.Command("service", "restart_dnsmasq").CombinedOutput(); err != nil {
|
||||||
return fmt.Errorf("restart_dnsmasq: %s, %w", string(out), err)
|
return fmt.Errorf("restart_dnsmasq: %s, %w", string(out), err)
|
||||||
@@ -186,6 +228,22 @@ func restartDNSMasq() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getDnsmasqConfigs retrieves a list of dnsmasqConfig containing configuration and JFFS paths for dnsmasq operations.
|
||||||
|
func getDnsmasqConfigs() []*dnsmasqConfig {
|
||||||
|
cfgs := []*dnsmasqConfig{
|
||||||
|
{dnsmasq.MerlinConfPath, dnsmasq.MerlinJffsConfPath},
|
||||||
|
}
|
||||||
|
for _, path := range dnsmasq.AdditionalConfigFiles() {
|
||||||
|
jffsConfPath := filepath.Join(dnsmasq.MerlinJffsConfDir, filepath.Base(path))
|
||||||
|
cfgs = append(cfgs, &dnsmasqConfig{path, jffsConfPath})
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// merlinParsePostConf parses the dnsmasq post configuration by removing content after the MerlinPostConfMarker, if present.
|
||||||
|
// If no marker is found, the original buffer is returned unmodified.
|
||||||
|
// Returns nil if the input buffer is empty.
|
||||||
func merlinParsePostConf(buf []byte) []byte {
|
func merlinParsePostConf(buf []byte) []byte {
|
||||||
if len(buf) == 0 {
|
if len(buf) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -197,6 +255,7 @@ func merlinParsePostConf(buf []byte) []byte {
|
|||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// waitDirExists waits until the specified directory exists, polling its existence every second.
|
||||||
func waitDirExists(dir string) {
|
func waitDirExists(dir string) {
|
||||||
for {
|
for {
|
||||||
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||||
|
|||||||
@@ -13,14 +13,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
|
|
||||||
|
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is a copy of https://github.com/kardianos/service/blob/v1.2.1/service_sysv_linux.go,
|
// This is a copy of https://github.com/kardianos/service/blob/v1.2.1/service_sysv_linux.go,
|
||||||
// with modification for supporting ubios v1 init system.
|
// with modification for supporting ubios v1 init system.
|
||||||
|
|
||||||
// Keep in sync with ubios.ubiosDNSMasqConfigPath
|
|
||||||
const ubiosDNSMasqConfigPath = "/run/dnsmasq.conf.d/zzzctrld.conf"
|
|
||||||
|
|
||||||
type ubiosSvc struct {
|
type ubiosSvc struct {
|
||||||
i service.Interface
|
i service.Interface
|
||||||
platform string
|
platform string
|
||||||
@@ -86,7 +85,7 @@ func (s *ubiosSvc) Install() error {
|
|||||||
}{
|
}{
|
||||||
s.Config,
|
s.Config,
|
||||||
path,
|
path,
|
||||||
ubiosDNSMasqConfigPath,
|
filepath.Join(dnsmasq.UbiosConfPath(), dnsmasq.UbiosConfName),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.template().Execute(f, to); err != nil {
|
if err := s.template().Execute(f, to); err != nil {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package ubios
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
@@ -12,19 +13,19 @@ import (
|
|||||||
"github.com/Control-D-Inc/ctrld/internal/router/edgeos"
|
"github.com/Control-D-Inc/ctrld/internal/router/edgeos"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const Name = "ubios"
|
||||||
Name = "ubios"
|
|
||||||
ubiosDNSMasqConfigPath = "/run/dnsmasq.conf.d/zzzctrld.conf"
|
|
||||||
ubiosDNSMasqDnsConfigPath = "/run/dnsmasq.conf.d/dns.conf"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Ubios struct {
|
type Ubios struct {
|
||||||
cfg *ctrld.Config
|
cfg *ctrld.Config
|
||||||
|
dnsmasqConfPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a router.Router for configuring/setup/run ctrld on Ubios routers.
|
// New returns a router.Router for configuring/setup/run ctrld on Ubios routers.
|
||||||
func New(cfg *ctrld.Config) *Ubios {
|
func New(cfg *ctrld.Config) *Ubios {
|
||||||
return &Ubios{cfg: cfg}
|
return &Ubios{
|
||||||
|
cfg: cfg,
|
||||||
|
dnsmasqConfPath: filepath.Join(dnsmasq.UbiosConfPath(), dnsmasq.UbiosConfName),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Ubios) ConfigureService(config *service.Config) error {
|
func (u *Ubios) ConfigureService(config *service.Config) error {
|
||||||
@@ -59,7 +60,7 @@ func (u *Ubios) Setup() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(ubiosDNSMasqConfigPath, []byte(data), 0600); err != nil {
|
if err := os.WriteFile(u.dnsmasqConfPath, []byte(data), 0600); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Restart dnsmasq service.
|
// Restart dnsmasq service.
|
||||||
@@ -74,7 +75,7 @@ func (u *Ubios) Cleanup() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Remove the custom dnsmasq config
|
// Remove the custom dnsmasq config
|
||||||
if err := os.Remove(ubiosDNSMasqConfigPath); err != nil {
|
if err := os.Remove(u.dnsmasqConfPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Restart dnsmasq service.
|
// Restart dnsmasq service.
|
||||||
@@ -85,7 +86,7 @@ func (u *Ubios) Cleanup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func restartDNSMasq() error {
|
func restartDNSMasq() error {
|
||||||
buf, err := os.ReadFile("/run/dnsmasq.pid")
|
buf, err := os.ReadFile(dnsmasq.UbiosPidFile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"tailscale.com/net/netmon"
|
||||||
|
|
||||||
"github.com/Control-D-Inc/ctrld/internal/dns/resolvconffile"
|
"github.com/Control-D-Inc/ctrld/internal/dns/resolvconffile"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -128,3 +131,25 @@ func virtualInterfaces() set {
|
|||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validInterfacesMap returns a set containing non virtual interfaces.
|
||||||
|
// TODO: deduplicated with cmd/cli/net_linux.go in v2.
|
||||||
|
func validInterfaces() set {
|
||||||
|
m := make(map[string]struct{})
|
||||||
|
vis := virtualInterfaces()
|
||||||
|
netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) {
|
||||||
|
if _, existed := vis[i.Name]; existed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m[i.Name] = struct{}{}
|
||||||
|
})
|
||||||
|
// Fallback to default route interface if found nothing.
|
||||||
|
if len(m) == 0 {
|
||||||
|
defaultRoute, err := netmon.DefaultRoute()
|
||||||
|
if err != nil {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
m[defaultRoute.InterfaceName] = struct{}{}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,20 +23,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxDNSAdapterRetries = 5
|
maxDNSAdapterRetries = 5
|
||||||
retryDelayDNSAdapter = 1 * time.Second
|
retryDelayDNSAdapter = 1 * time.Second
|
||||||
defaultDNSAdapterTimeout = 10 * time.Second
|
defaultDNSAdapterTimeout = 10 * time.Second
|
||||||
minDNSServers = 1 // Minimum number of DNS servers we want to find
|
minDNSServers = 1 // Minimum number of DNS servers we want to find
|
||||||
NetSetupUnknown uint32 = 0
|
|
||||||
NetSetupWorkgroup uint32 = 1
|
DS_FORCE_REDISCOVERY = 0x00000001
|
||||||
NetSetupDomain uint32 = 2
|
DS_DIRECTORY_SERVICE_REQUIRED = 0x00000010
|
||||||
NetSetupCloudDomain uint32 = 3
|
DS_BACKGROUND_ONLY = 0x00000100
|
||||||
DS_FORCE_REDISCOVERY = 0x00000001
|
DS_IP_REQUIRED = 0x00000200
|
||||||
DS_DIRECTORY_SERVICE_REQUIRED = 0x00000010
|
DS_IS_DNS_NAME = 0x00020000
|
||||||
DS_BACKGROUND_ONLY = 0x00000100
|
DS_RETURN_DNS_NAME = 0x40000000
|
||||||
DS_IP_REQUIRED = 0x00000200
|
|
||||||
DS_IS_DNS_NAME = 0x00020000
|
|
||||||
DS_RETURN_DNS_NAME = 0x40000000
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DomainControllerInfo struct {
|
type DomainControllerInfo struct {
|
||||||
@@ -158,7 +155,7 @@ func getDNSServers(ctx context.Context) ([]string, error) {
|
|||||||
0, // DomainGuid - not needed
|
0, // DomainGuid - not needed
|
||||||
0, // SiteName - not needed
|
0, // SiteName - not needed
|
||||||
uintptr(flags), // Flags
|
uintptr(flags), // Flags
|
||||||
uintptr(unsafe.Pointer(&info))) // DomainControllerInfo - output
|
uintptr(unsafe.Pointer(&info))) // DomainControllerInfo - output
|
||||||
|
|
||||||
if ret != 0 {
|
if ret != 0 {
|
||||||
switch ret {
|
switch ret {
|
||||||
@@ -343,27 +340,28 @@ func checkDomainJoined() bool {
|
|||||||
var domain *uint16
|
var domain *uint16
|
||||||
var status uint32
|
var status uint32
|
||||||
|
|
||||||
err := windows.NetGetJoinInformation(nil, &domain, &status)
|
if err := windows.NetGetJoinInformation(nil, &domain, &status); err != nil {
|
||||||
if err != nil {
|
Log(context.Background(), logger.Debug(), "Failed to get domain join status: %v", err)
|
||||||
Log(context.Background(), logger.Debug(),
|
|
||||||
"Failed to get domain join status: %v", err)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
defer windows.NetApiBufferFree((*byte)(unsafe.Pointer(domain)))
|
defer windows.NetApiBufferFree((*byte)(unsafe.Pointer(domain)))
|
||||||
|
|
||||||
|
// NETSETUP_JOIN_STATUS constants from Microsoft Windows API
|
||||||
|
// See: https://learn.microsoft.com/en-us/windows/win32/api/lmjoin/ne-lmjoin-netsetup_join_status
|
||||||
|
//
|
||||||
|
// NetSetupUnknownStatus uint32 = 0 // The status is unknown
|
||||||
|
// NetSetupUnjoined uint32 = 1 // The computer is not joined to a domain or workgroup
|
||||||
|
// NetSetupWorkgroupName uint32 = 2 // The computer is joined to a workgroup
|
||||||
|
// NetSetupDomainName uint32 = 3 // The computer is joined to a domain
|
||||||
|
//
|
||||||
|
// We only care about NetSetupDomainName.
|
||||||
domainName := windows.UTF16PtrToString(domain)
|
domainName := windows.UTF16PtrToString(domain)
|
||||||
Log(context.Background(), logger.Debug(),
|
Log(context.Background(), logger.Debug(),
|
||||||
"Domain join status: domain=%s status=%d (Unknown=0, Workgroup=1, Domain=2, CloudDomain=3)",
|
"Domain join status: domain=%s status=%d (UnknownStatus=0, Unjoined=1, WorkgroupName=2, DomainName=3)",
|
||||||
domainName, status)
|
domainName, status)
|
||||||
|
|
||||||
// Consider domain or cloud domain as domain-joined
|
isDomain := status == syscall.NetSetupDomainName
|
||||||
isDomain := status == NetSetupDomain || status == NetSetupCloudDomain
|
Log(context.Background(), logger.Debug(), "Is domain joined? status=%d, result=%v", status, isDomain)
|
||||||
Log(context.Background(), logger.Debug(),
|
|
||||||
"Is domain joined? status=%d, traditional=%v, cloud=%v, result=%v",
|
|
||||||
status,
|
|
||||||
status == NetSetupDomain,
|
|
||||||
status == NetSetupCloudDomain,
|
|
||||||
isDomain)
|
|
||||||
|
|
||||||
return isDomain
|
return isDomain
|
||||||
}
|
}
|
||||||
|
|||||||
35
net_darwin.go
Normal file
35
net_darwin.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package ctrld
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// validInterfaces returns a set of all valid hardware ports.
|
||||||
|
// TODO: deduplicated with cmd/cli/net_darwin.go in v2.
|
||||||
|
func validInterfaces() map[string]struct{} {
|
||||||
|
b, err := exec.Command("networksetup", "-listallhardwareports").Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return parseListAllHardwarePorts(bytes.NewReader(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseListAllHardwarePorts parses output of "networksetup -listallhardwareports"
|
||||||
|
// and returns map presents all hardware ports.
|
||||||
|
func parseListAllHardwarePorts(r io.Reader) map[string]struct{} {
|
||||||
|
m := make(map[string]struct{})
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
after, ok := strings.CutPrefix(line, "Device: ")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m[after] = struct{}{}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package cli
|
package ctrld
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"maps"
|
"maps"
|
||||||
15
net_others.go
Normal file
15
net_others.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//go:build !darwin && !windows && !linux
|
||||||
|
|
||||||
|
package ctrld
|
||||||
|
|
||||||
|
import "tailscale.com/net/netmon"
|
||||||
|
|
||||||
|
// validInterfaces returns a set containing only default route interfaces.
|
||||||
|
// TODO: deuplicated with cmd/cli/net_others.go in v2.
|
||||||
|
func validInterfaces() map[string]struct{} {
|
||||||
|
defaultRoute, err := netmon.DefaultRoute()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return map[string]struct{}{defaultRoute.InterfaceName: {}}
|
||||||
|
}
|
||||||
@@ -729,10 +729,15 @@ func newResolverWithNameserver(nameservers []string) *osResolver {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rfc1918Addresses returns the list of local interfaces private IP addresses
|
// Rfc1918Addresses returns the list of local physical interfaces private IP addresses
|
||||||
func Rfc1918Addresses() []string {
|
func Rfc1918Addresses() []string {
|
||||||
|
vis := validInterfaces()
|
||||||
var res []string
|
var res []string
|
||||||
netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) {
|
netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) {
|
||||||
|
// Skip virtual interfaces.
|
||||||
|
if _, existed := vis[i.Name]; !existed {
|
||||||
|
return
|
||||||
|
}
|
||||||
addrs, _ := i.Addrs()
|
addrs, _ := i.Addrs()
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
ipNet, ok := addr.(*net.IPNet)
|
ipNet, ok := addr.(*net.IPNet)
|
||||||
|
|||||||
@@ -282,6 +282,35 @@ func Test_Edns0_CacheReply(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/Control-D-Inc/ctrld/issues/255
|
||||||
|
func Test_legacyResolverWithBigExtraSection(t *testing.T) {
|
||||||
|
lanPC, err := net.ListenPacket("udp", "127.0.0.1:0") // 127.0.0.1 is considered LAN (loopback)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to listen on LAN address: %v", err)
|
||||||
|
}
|
||||||
|
lanServer, lanAddr, err := runLocalPacketConnTestServer(t, lanPC, bigExtraSectionHandler())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to run LAN test server: %v", err)
|
||||||
|
}
|
||||||
|
defer lanServer.Shutdown()
|
||||||
|
|
||||||
|
uc := &UpstreamConfig{
|
||||||
|
Name: "Legacy",
|
||||||
|
Type: ResolverTypeLegacy,
|
||||||
|
Endpoint: lanAddr,
|
||||||
|
}
|
||||||
|
uc.Init()
|
||||||
|
r, err := NewResolver(uc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = r.Resolve(context.Background(), uc.VerifyMsg())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_upstreamTypeFromEndpoint(t *testing.T) {
|
func Test_upstreamTypeFromEndpoint(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -370,6 +399,68 @@ func countHandler(call *atomic.Int64) dns.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mustRR(s string) dns.RR {
|
||||||
|
r, err := dns.NewRR(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func bigExtraSectionHandler() dns.HandlerFunc {
|
||||||
|
return func(w dns.ResponseWriter, msg *dns.Msg) {
|
||||||
|
m := &dns.Msg{
|
||||||
|
Answer: []dns.RR{
|
||||||
|
mustRR(". 7149 IN NS m.root-servers.net."),
|
||||||
|
mustRR(". 7149 IN NS c.root-servers.net."),
|
||||||
|
mustRR(". 7149 IN NS e.root-servers.net."),
|
||||||
|
mustRR(". 7149 IN NS j.root-servers.net."),
|
||||||
|
mustRR(". 7149 IN NS g.root-servers.net."),
|
||||||
|
mustRR(". 7149 IN NS k.root-servers.net."),
|
||||||
|
mustRR(". 7149 IN NS l.root-servers.net."),
|
||||||
|
mustRR(". 7149 IN NS d.root-servers.net."),
|
||||||
|
mustRR(". 7149 IN NS h.root-servers.net."),
|
||||||
|
mustRR(". 7149 IN NS b.root-servers.net."),
|
||||||
|
mustRR(". 7149 IN NS a.root-servers.net."),
|
||||||
|
mustRR(". 7149 IN NS f.root-servers.net."),
|
||||||
|
mustRR(". 7149 IN NS i.root-servers.net."),
|
||||||
|
},
|
||||||
|
Extra: []dns.RR{
|
||||||
|
mustRR("m.root-servers.net. 656 IN A 202.12.27.33"),
|
||||||
|
mustRR("m.root-servers.net. 656 IN AAAA 2001:dc3::35"),
|
||||||
|
mustRR("c.root-servers.net. 656 IN A 192.33.4.12"),
|
||||||
|
mustRR("c.root-servers.net. 656 IN AAAA 2001:500:2::c"),
|
||||||
|
mustRR("e.root-servers.net. 656 IN A 192.203.230.10"),
|
||||||
|
mustRR("e.root-servers.net. 656 IN AAAA 2001:500:a8::e"),
|
||||||
|
mustRR("j.root-servers.net. 656 IN A 192.58.128.30"),
|
||||||
|
mustRR("j.root-servers.net. 656 IN AAAA 2001:503:c27::2:30"),
|
||||||
|
mustRR("g.root-servers.net. 656 IN A 192.112.36.4"),
|
||||||
|
mustRR("g.root-servers.net. 656 IN AAAA 2001:500:12::d0d"),
|
||||||
|
mustRR("k.root-servers.net. 656 IN A 193.0.14.129"),
|
||||||
|
mustRR("k.root-servers.net. 656 IN AAAA 2001:7fd::1"),
|
||||||
|
mustRR("l.root-servers.net. 656 IN A 199.7.83.42"),
|
||||||
|
mustRR("l.root-servers.net. 656 IN AAAA 2001:500:9f::42"),
|
||||||
|
mustRR("d.root-servers.net. 656 IN A 199.7.91.13"),
|
||||||
|
mustRR("d.root-servers.net. 656 IN AAAA 2001:500:2d::d"),
|
||||||
|
mustRR("h.root-servers.net. 656 IN A 198.97.190.53"),
|
||||||
|
mustRR("h.root-servers.net. 656 IN AAAA 2001:500:1::53"),
|
||||||
|
mustRR("b.root-servers.net. 656 IN A 170.247.170.2"),
|
||||||
|
mustRR("b.root-servers.net. 656 IN AAAA 2801:1b8:10::b"),
|
||||||
|
mustRR("a.root-servers.net. 656 IN A 198.41.0.4"),
|
||||||
|
mustRR("a.root-servers.net. 656 IN AAAA 2001:503:ba3e::2:30"),
|
||||||
|
mustRR("f.root-servers.net. 656 IN A 192.5.5.241"),
|
||||||
|
mustRR("f.root-servers.net. 656 IN AAAA 2001:500:2f::f"),
|
||||||
|
mustRR("i.root-servers.net. 656 IN A 192.36.148.17"),
|
||||||
|
mustRR("i.root-servers.net. 656 IN AAAA 2001:7fe::53"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Compress = true
|
||||||
|
m.SetReply(msg)
|
||||||
|
w.WriteMsg(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func generateEdns0ClientCookie() string {
|
func generateEdns0ClientCookie() string {
|
||||||
cookie := make([]byte, 8)
|
cookie := make([]byte, 8)
|
||||||
if _, err := rand.Read(cookie); err != nil {
|
if _, err := rand.Read(cookie); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user