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

View File

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

View File

@@ -2,6 +2,8 @@ package main
import (
"errors"
"fmt"
"math/rand"
"net"
"os"
"strconv"
@@ -120,7 +122,7 @@ func (p *prog) run() {
addr := net.JoinHostPort(listenerConfig.IP, strconv.Itoa(listenerConfig.Port))
mainLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, addr)
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)
return
}
@@ -128,39 +130,21 @@ func (p *prog) run() {
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) {
mainLog.Warn().Msgf("Address %s already in used, pick a random one", addr)
pc, err := net.ListenPacket("udp", net.JoinHostPort(listenerConfig.IP, "0"))
if err != nil {
mainLog.Fatal().Err(err).Msg("failed to listen packet")
return
}
_, 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,
},
})
ip := randomLocalIP()
listenerConfig.IP = ip
port := listenerConfig.Port
cfg.Upstream = map[string]*ctrld.UpstreamConfig{"0": cfg.Upstream["0"]}
if err := writeConfigFile(); err != nil {
mainLog.Fatal().Err(err).Msg("failed to write config file")
} else {
mainLog.Info().Msg("writing config file to: " + defaultConfigFile)
}
mainLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, pc.LocalAddr())
// There can be a race between closing the listener and start our own UDP server, but it's
// rare, and we only do this once, so let conservative here.
if err := pc.Close(); err != nil {
mainLog.Fatal().Err(err).Msg("failed to close packet conn")
return
}
p.cfg.Service.AllocateIP = true
p.preRun()
mainLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, net.JoinHostPort(ip, strconv.Itoa(port)))
if err := p.serveUDP(listenerNum); err != nil {
mainLog.Fatal().Err(err).Msgf("Unable to start dns proxy on listener.%s", listenerNum)
return
@@ -254,3 +238,8 @@ func (p *prog) resetDNS() {
}
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.
type Config struct {
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"`
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.

2
go.mod
View File

@@ -5,6 +5,7 @@ go 1.19
require (
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534
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/godbus/dbus/v5 v5.0.6
github.com/hashicorp/golang-lru/v2 v2.0.1
@@ -26,7 +27,6 @@ require (
require (
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // 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/universal-translator v0.18.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect