cmd/ctrld: allocate new ip instead of port

So the alternative listener address can still be used as system
resolver.
This commit is contained in:
Cuong Manh Le
2023-02-21 23:52:50 +07:00
committed by Cuong Manh Le
parent 82900eeca6
commit cad71997aa
5 changed files with 41 additions and 43 deletions

View File

@@ -17,6 +17,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/fsnotify/fsnotify"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/kardianos/service" "github.com/kardianos/service"
"github.com/miekg/dns" "github.com/miekg/dns"
@@ -87,7 +89,6 @@ func initCLI() {
if daemon && runtime.GOOS == "windows" { if daemon && runtime.GOOS == "windows" {
log.Fatal("Cannot run in daemon mode. Please install a Windows service.") log.Fatal("Cannot run in daemon mode. Please install a Windows service.")
} }
noConfigStart := isNoConfigStart(cmd) noConfigStart := isNoConfigStart(cmd)
writeDefaultConfig := !noConfigStart && configBase64 == "" writeDefaultConfig := !noConfigStart && configBase64 == ""
configs := []struct { configs := []struct {
@@ -196,18 +197,23 @@ func initCLI() {
} }
setDependencies(sc) setDependencies(sc)
sc.Arguments = append([]string{"run"}, osArgs...) sc.Arguments = append([]string{"run"}, osArgs...)
// No config path, generating config in HOME directory.
noConfigStart := isNoConfigStart(cmd)
writeDefaultConfig := !noConfigStart && configBase64 == ""
if configPath != "" {
v.SetConfigFile(configPath)
}
if dir, err := os.UserHomeDir(); err == nil { if dir, err := os.UserHomeDir(); err == nil {
setWorkingDirectory(sc, dir) setWorkingDirectory(sc, dir)
// No config path, generating config in HOME directory.
noConfigStart := isNoConfigStart(cmd)
writeDefaultConfig := !noConfigStart && configBase64 == ""
if configPath == "" && writeDefaultConfig { if configPath == "" && writeDefaultConfig {
defaultConfigFile = filepath.Join(dir, defaultConfigFile) defaultConfigFile = filepath.Join(dir, defaultConfigFile)
readConfigFile(writeDefaultConfig && cdUID == "") v.SetConfigFile(defaultConfigFile)
} }
sc.Arguments = append(sc.Arguments, "--homedir="+dir) sc.Arguments = append(sc.Arguments, "--homedir="+dir)
} }
readConfigFile(writeDefaultConfig && cdUID == "")
if err := v.Unmarshal(&cfg); err != nil { if err := v.Unmarshal(&cfg); err != nil {
log.Fatalf("failed to unmarshal config: %v", err) log.Fatalf("failed to unmarshal config: %v", err)
} }
@@ -480,7 +486,7 @@ func writeConfigFile() error {
} }
} }
enc := toml.NewEncoder(f).SetIndentTables(true) enc := toml.NewEncoder(f).SetIndentTables(true)
if err := enc.Encode(v.AllSettings()); err != nil { if err := enc.Encode(&cfg); err != nil {
return err return err
} }
if err := f.Close(); err != nil { if err := f.Close(); err != nil {
@@ -495,6 +501,13 @@ func readConfigFile(writeDefaultConfig bool) bool {
if err == nil { if err == nil {
fmt.Println("loading config file from:", v.ConfigFileUsed()) fmt.Println("loading config file from:", v.ConfigFileUsed())
defaultConfigFile = v.ConfigFileUsed() defaultConfigFile = v.ConfigFileUsed()
v.OnConfigChange(func(in fsnotify.Event) {
if err := v.UnmarshalKey("listener", &cfg.Listener); err != nil {
log.Printf("failed to unmarshal listener config: %v", err)
return
}
})
v.WatchConfig()
return true return true
} }
@@ -630,10 +643,6 @@ func processCDFlags() {
}, },
} }
v = viper.NewWithOptions(viper.KeyDelimiter("::"))
v.Set("network", cfg.Network)
v.Set("upstream", cfg.Upstream)
v.Set("listener", cfg.Listener)
processLogAndCacheFlags() processLogAndCacheFlags()
if err := writeConfigFile(); err != nil { if err := writeConfigFile(); err != nil {
logger.Fatal().Err(err).Msg("failed to write config file") logger.Fatal().Err(err).Msg("failed to write config file")
@@ -705,14 +714,14 @@ func defaultIfaceName() string {
func selfCheckStatus(status service.Status) service.Status { func selfCheckStatus(status service.Status) service.Status {
c := new(dns.Client) c := new(dns.Client)
lc := cfg.Listener["0"]
bo := backoff.NewBackoff("self-check", logf, 10*time.Second) bo := backoff.NewBackoff("self-check", logf, 10*time.Second)
bo.LogLongerThan = 500 * time.Millisecond bo.LogLongerThan = 500 * time.Millisecond
ctx := context.Background() ctx := context.Background()
err := errors.New("query failed") err := errors.New("query failed")
maxAttempts := 10 maxAttempts := 20
mainLog.Debug().Msg("Performing self-check") mainLog.Debug().Msg("Performing self-check")
for i := 0; i < maxAttempts; i++ { for i := 0; i < maxAttempts; i++ {
lc := cfg.Listener["0"]
m := new(dns.Msg) m := new(dns.Msg)
m.SetQuestion(selfCheckFQDN+".", dns.TypeA) m.SetQuestion(selfCheckFQDN+".", dns.TypeA)
m.RecursionDesired = true m.RecursionDesired = true

View File

@@ -27,8 +27,8 @@ import (
// sudo ip a add 127.0.0.2/24 dev lo // sudo ip a add 127.0.0.2/24 dev lo
func allocateIP(ip string) error { func allocateIP(ip string) error {
cmd := exec.Command("ip", "a", "add", ip+"/24", "dev", "lo") cmd := exec.Command("ip", "a", "add", ip+"/24", "dev", "lo")
if err := cmd.Run(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
mainLog.Error().Err(err).Msg("allocateIP failed") mainLog.Error().Err(err).Msgf("allocateIP failed: %s", string(out))
return err return err
} }
return nil return nil

View File

@@ -2,6 +2,8 @@ package main
import ( import (
"errors" "errors"
"fmt"
"math/rand"
"net" "net"
"os" "os"
"strconv" "strconv"
@@ -120,7 +122,7 @@ func (p *prog) run() {
addr := net.JoinHostPort(listenerConfig.IP, strconv.Itoa(listenerConfig.Port)) addr := net.JoinHostPort(listenerConfig.IP, strconv.Itoa(listenerConfig.Port))
mainLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, addr) mainLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, addr)
err := p.serveUDP(listenerNum) err := p.serveUDP(listenerNum)
if err != nil && !defaultConfigWritten { if err != nil && !defaultConfigWritten && cdUID == "" {
mainLog.Fatal().Err(err).Msgf("Unable to start dns proxy on listener.%s", listenerNum) mainLog.Fatal().Err(err).Msgf("Unable to start dns proxy on listener.%s", listenerNum)
return return
} }
@@ -128,39 +130,21 @@ func (p *prog) run() {
return return
} }
if opErr, ok := err.(*net.OpError); ok { if opErr, ok := err.(*net.OpError); ok && listenerNum == "0" {
if sErr, ok := opErr.Err.(*os.SyscallError); ok && errors.Is(opErr.Err, syscall.EADDRINUSE) || errors.Is(sErr.Err, errWindowsAddrInUse) { if sErr, ok := opErr.Err.(*os.SyscallError); ok && errors.Is(opErr.Err, syscall.EADDRINUSE) || errors.Is(sErr.Err, errWindowsAddrInUse) {
mainLog.Warn().Msgf("Address %s already in used, pick a random one", addr) mainLog.Warn().Msgf("Address %s already in used, pick a random one", addr)
pc, err := net.ListenPacket("udp", net.JoinHostPort(listenerConfig.IP, "0")) ip := randomLocalIP()
if err != nil { listenerConfig.IP = ip
mainLog.Fatal().Err(err).Msg("failed to listen packet") port := listenerConfig.Port
return cfg.Upstream = map[string]*ctrld.UpstreamConfig{"0": cfg.Upstream["0"]}
}
_, portStr, _ := net.SplitHostPort(pc.LocalAddr().String())
port, err := strconv.Atoi(portStr)
if err != nil {
mainLog.Fatal().Err(err).Msg("malformed port")
return
}
listenerConfig.Port = port
v.Set("listener", map[string]*ctrld.ListenerConfig{
"0": {
IP: "127.0.0.1",
Port: port,
},
})
if err := writeConfigFile(); err != nil { if err := writeConfigFile(); err != nil {
mainLog.Fatal().Err(err).Msg("failed to write config file") mainLog.Fatal().Err(err).Msg("failed to write config file")
} else { } else {
mainLog.Info().Msg("writing config file to: " + defaultConfigFile) mainLog.Info().Msg("writing config file to: " + defaultConfigFile)
} }
mainLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, pc.LocalAddr()) p.cfg.Service.AllocateIP = true
// There can be a race between closing the listener and start our own UDP server, but it's p.preRun()
// rare, and we only do this once, so let conservative here. mainLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, net.JoinHostPort(ip, strconv.Itoa(port)))
if err := pc.Close(); err != nil {
mainLog.Fatal().Err(err).Msg("failed to close packet conn")
return
}
if err := p.serveUDP(listenerNum); err != nil { if err := p.serveUDP(listenerNum); err != nil {
mainLog.Fatal().Err(err).Msgf("Unable to start dns proxy on listener.%s", listenerNum) mainLog.Fatal().Err(err).Msgf("Unable to start dns proxy on listener.%s", listenerNum)
return return
@@ -254,3 +238,8 @@ func (p *prog) resetDNS() {
} }
logger.Debug().Msg("Restoring DNS successfully") logger.Debug().Msg("Restoring DNS successfully")
} }
func randomLocalIP() string {
n := rand.Intn(254-2) + 2
return fmt.Sprintf("127.0.0.%d", n)
}

View File

@@ -66,9 +66,9 @@ func InitConfig(v *viper.Viper, name string) {
// Config represents ctrld supported configuration. // Config represents ctrld supported configuration.
type Config struct { type Config struct {
Service ServiceConfig `mapstructure:"service" toml:"service,omitempty"` Service ServiceConfig `mapstructure:"service" toml:"service,omitempty"`
Listener map[string]*ListenerConfig `mapstructure:"listener" toml:"listener" validate:"min=1,dive"`
Network map[string]*NetworkConfig `mapstructure:"network" toml:"network" validate:"min=1,dive"` Network map[string]*NetworkConfig `mapstructure:"network" toml:"network" validate:"min=1,dive"`
Upstream map[string]*UpstreamConfig `mapstructure:"upstream" toml:"upstream" validate:"min=1,dive"` Upstream map[string]*UpstreamConfig `mapstructure:"upstream" toml:"upstream" validate:"min=1,dive"`
Listener map[string]*ListenerConfig `mapstructure:"listener" toml:"listener" validate:"min=1,dive"`
} }
// ServiceConfig specifies the general ctrld config. // ServiceConfig specifies the general ctrld config.

2
go.mod
View File

@@ -5,6 +5,7 @@ go 1.19
require ( require (
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534
github.com/frankban/quicktest v1.14.3 github.com/frankban/quicktest v1.14.3
github.com/fsnotify/fsnotify v1.6.0
github.com/go-playground/validator/v10 v10.11.1 github.com/go-playground/validator/v10 v10.11.1
github.com/godbus/dbus/v5 v5.0.6 github.com/godbus/dbus/v5 v5.0.6
github.com/hashicorp/golang-lru/v2 v2.0.1 github.com/hashicorp/golang-lru/v2 v2.0.1
@@ -26,7 +27,6 @@ require (
require ( require (
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.6.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-20210107165309-348f09dbbbc0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect