mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
all: allow chosing random address and port for listener
This commit is contained in:
committed by
Cuong Manh Le
parent
69319c6b41
commit
c7bad63869
@@ -9,6 +9,7 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
@@ -72,40 +73,37 @@ func (p *prog) serveDNS(listenerNum string) error {
|
||||
g, ctx := errgroup.WithContext(context.Background())
|
||||
for _, proto := range []string{"udp", "tcp"} {
|
||||
proto := proto
|
||||
// On Windows, there's no easy way for disabling/removing IPv6 DNS resolver, so we check whether we can
|
||||
// listen on ::1, then spawn a listener for receiving DNS requests.
|
||||
if needLocalIPv6Listener() {
|
||||
g.Go(func() error {
|
||||
s := &dns.Server{
|
||||
Addr: net.JoinHostPort("::1", strconv.Itoa(listenerConfig.Port)),
|
||||
Net: proto,
|
||||
Handler: handler,
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
_ = s.Shutdown()
|
||||
}()
|
||||
if err := s.ListenAndServe(); err != nil {
|
||||
mainLog.Error().Err(err).Msg("could not serving on ::1")
|
||||
s, errCh := runDNSServer(net.JoinHostPort("::1", strconv.Itoa(listenerConfig.Port)), proto, handler)
|
||||
defer s.Shutdown()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case err := <-errCh:
|
||||
// Local ipv6 listener should not terminate ctrld.
|
||||
// It's a workaround for a quirk on Windows.
|
||||
mainLog.Warn().Err(err).Msg("local ipv6 listener failed")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
g.Go(func() error {
|
||||
s := &dns.Server{
|
||||
Addr: dnsListenAddress(listenerConfig),
|
||||
Net: proto,
|
||||
Handler: handler,
|
||||
s, errCh := runDNSServer(dnsListenAddress(listenerConfig), proto, handler)
|
||||
defer s.Shutdown()
|
||||
if listenerConfig.Port == 0 {
|
||||
switch s.Net {
|
||||
case "udp":
|
||||
mainLog.Info().Msgf("Random port chosen for udp listener.%s: %s", listenerNum, s.PacketConn.LocalAddr())
|
||||
case "tcp":
|
||||
mainLog.Info().Msgf("Random port chosen for tcp listener.%s: %s", listenerNum, s.Listener.Addr())
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
_ = s.Shutdown()
|
||||
}()
|
||||
if err := s.ListenAndServe(); err != nil {
|
||||
mainLog.Error().Err(err).Msgf("could not listen and serve on: %s", s.Addr)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case err := <-errCh:
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return g.Wait()
|
||||
@@ -389,6 +387,8 @@ func ttlFromMsg(msg *dns.Msg) uint32 {
|
||||
}
|
||||
|
||||
func needLocalIPv6Listener() bool {
|
||||
// On Windows, there's no easy way for disabling/removing IPv6 DNS resolver, so we check whether we can
|
||||
// listen on ::1, then spawn a listener for receiving DNS requests.
|
||||
return ctrldnet.SupportsIPv6ListenLocal() && runtime.GOOS == "windows"
|
||||
}
|
||||
|
||||
@@ -412,3 +412,31 @@ func macFromMsg(msg *dns.Msg) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// runDNSServer starts a DNS server for given address and network,
|
||||
// with the given handler. It ensures the server has started listening.
|
||||
// Any error will be reported to the caller via returned channel.
|
||||
//
|
||||
// It's the caller responsibility to call Shutdown to close the server.
|
||||
func runDNSServer(addr, network string, handler dns.Handler) (*dns.Server, <-chan error) {
|
||||
s := &dns.Server{
|
||||
Addr: addr,
|
||||
Net: network,
|
||||
Handler: handler,
|
||||
}
|
||||
|
||||
waitLock := sync.Mutex{}
|
||||
waitLock.Lock()
|
||||
s.NotifyStartedFunc = waitLock.Unlock
|
||||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
defer close(errCh)
|
||||
if err := s.ListenAndServe(); err != nil {
|
||||
mainLog.Error().Err(err).Msgf("could not listen and serve on: %s", s.Addr)
|
||||
errCh <- err
|
||||
}
|
||||
}()
|
||||
waitLock.Lock()
|
||||
return s, errCh
|
||||
}
|
||||
|
||||
13
config.go
13
config.go
@@ -133,8 +133,8 @@ type UpstreamConfig struct {
|
||||
|
||||
// ListenerConfig specifies the networks configuration that ctrld will run on.
|
||||
type ListenerConfig struct {
|
||||
IP string `mapstructure:"ip" toml:"ip,omitempty" validate:"ip"`
|
||||
Port int `mapstructure:"port" toml:"port,omitempty" validate:"gt=0"`
|
||||
IP string `mapstructure:"ip" toml:"ip,omitempty" validate:"iporempty"`
|
||||
Port int `mapstructure:"port" toml:"port,omitempty" validate:"gte=0"`
|
||||
Restricted bool `mapstructure:"restricted" toml:"restricted,omitempty"`
|
||||
Policy *ListenerPolicyConfig `mapstructure:"policy" toml:"policy,omitempty"`
|
||||
}
|
||||
@@ -329,6 +329,7 @@ func (lc *ListenerConfig) Init() {
|
||||
// ValidateConfig validates the given config.
|
||||
func ValidateConfig(validate *validator.Validate, cfg *Config) error {
|
||||
_ = validate.RegisterValidation("dnsrcode", validateDnsRcode)
|
||||
_ = validate.RegisterValidation("iporempty", validateIpOrEmpty)
|
||||
return validate.Struct(cfg)
|
||||
}
|
||||
|
||||
@@ -336,6 +337,14 @@ func validateDnsRcode(fl validator.FieldLevel) bool {
|
||||
return dnsrcode.FromString(fl.Field().String()) != -1
|
||||
}
|
||||
|
||||
func validateIpOrEmpty(fl validator.FieldLevel) bool {
|
||||
val := fl.Field().String()
|
||||
if val == "" {
|
||||
return true
|
||||
}
|
||||
return net.ParseIP(val) != nil
|
||||
}
|
||||
|
||||
func defaultPortFor(typ string) string {
|
||||
switch typ {
|
||||
case ResolverTypeDOH, ResolverTypeDOH3:
|
||||
|
||||
@@ -65,6 +65,7 @@ func TestConfigValidation(t *testing.T) {
|
||||
{"invalid Config", &ctrld.Config{}, true},
|
||||
{"default Config", defaultConfig(t), false},
|
||||
{"sample Config", testhelper.SampleConfig(t), false},
|
||||
{"empty listener IP", emptyListenerIP(t), false},
|
||||
{"invalid cidr", invalidNetworkConfig(t), true},
|
||||
{"invalid upstream type", invalidUpstreamType(t), true},
|
||||
{"invalid upstream timeout", invalidUpstreamTimeout(t), true},
|
||||
@@ -134,9 +135,15 @@ func invalidListenerIP(t *testing.T) *ctrld.Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func emptyListenerIP(t *testing.T) *ctrld.Config {
|
||||
cfg := defaultConfig(t)
|
||||
cfg.Listener["0"].IP = ""
|
||||
return cfg
|
||||
}
|
||||
|
||||
func invalidListenerPort(t *testing.T) *ctrld.Config {
|
||||
cfg := defaultConfig(t)
|
||||
cfg.Listener["0"].Port = 0
|
||||
cfg.Listener["0"].Port = -1
|
||||
return cfg
|
||||
}
|
||||
|
||||
|
||||
@@ -271,16 +271,14 @@ The `[listener]` section specifies the ip and port of the local DNS server. You
|
||||
```
|
||||
|
||||
### ip
|
||||
IP address that serves the incoming requests.
|
||||
IP address that serves the incoming requests. If `ip` is empty, ctrld will listen on all available addresses.
|
||||
|
||||
- Type: string
|
||||
- Required: yes
|
||||
- Type: ip address
|
||||
|
||||
### port
|
||||
Port number that the listener will listen on for incoming requests.
|
||||
Port number that the listener will listen on for incoming requests. If `port` is `0`, a random available port will be chosen.
|
||||
|
||||
- Type: number
|
||||
- Required: yes
|
||||
|
||||
### restricted
|
||||
If set to `true` makes the listener `REFUSE` DNS queries from all source IP addresses that are not explicitly defined in the policy using a `network`.
|
||||
|
||||
Reference in New Issue
Block a user