diff --git a/core/nylon.go b/core/nylon.go index fb0332d..b8371b6 100644 --- a/core/nylon.go +++ b/core/nylon.go @@ -1,6 +1,7 @@ package core import ( + "context" "net" "time" @@ -25,6 +26,27 @@ func (n *Nylon) Init(s *state.State) error { s.Log.Debug("init nylon") + if len(s.DnsResolvers) != 0 { + s.Log.Debug("setting custom DNS resolvers", "resolvers", s.DnsResolvers) + net.DefaultResolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + d := net.Dialer{ + Timeout: time.Second * 10, + } + var err error + var conn net.Conn + for _, resolver := range s.DnsResolvers { + conn, err = d.DialContext(ctx, network, resolver) + if err == nil { + return conn, nil + } + } + return conn, err + }, + } + } + // add neighbours for _, peer := range s.GetPeers(s.Id) { if !s.IsRouter(peer) { diff --git a/example/sample-node.yaml b/example/sample-node.yaml index ec896df..4ef57a8 100644 --- a/example/sample-node.yaml +++ b/example/sample-node.yaml @@ -6,6 +6,7 @@ usesystemrouting: false # Default: false - if enabled, Nylon will use the system logpath: "" # Default: "" - If set, Nylon will log to this file interfacename: "" # Default: "" - If set, Nylon will use this interface name instead of the default "nylon" or utunx on macOS disablerouting: false # Default: false - If true, Nylon will not route traffic through this node +dnsresolvers: [] # Default: [] - If set (e.g ["1.1.1.1:53"]), nylon will use these DNS resolvers for its own queries dist: # Optional: If set, Nylon will bootstrap central.yaml from this URL if it does not exist already url: https://static.example.com/network1.nybundle key: 7PaN6DmAayz4KnDnsXSXJH+Oy0TFGeoM4FEbQfLriVY= \ No newline at end of file diff --git a/state/config.go b/state/config.go index cdf7d64..aa2ef66 100644 --- a/state/config.go +++ b/state/config.go @@ -73,7 +73,8 @@ type LocalCfg struct { Dist *LocalDistributionCfg `yaml:",omitempty"` DisableRouting bool UseSystemRouting bool - NoNetConfigure bool `yaml:",omitempty"` + NoNetConfigure bool `yaml:",omitempty"` + DnsResolvers []string `yaml:",omitempty"` InterfaceName string LogPath string } diff --git a/state/validation.go b/state/validation.go index 5b7f1e5..f113446 100644 --- a/state/validation.go +++ b/state/validation.go @@ -61,6 +61,13 @@ func NodeConfigValidator(node *LocalCfg) error { return err } } + if len(node.DnsResolvers) != 0 { + for _, resolver := range node.DnsResolvers { + if _, err := netip.ParseAddrPort(resolver); err != nil { + return fmt.Errorf("dns resolver %s is not a valid ip:port: %v", resolver, err) + } + } + } return nil } diff --git a/state/validation_test.go b/state/validation_test.go index 3b669af..70462db 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -23,6 +23,38 @@ func TestNameValidator_Invalid(t *testing.T) { assert.Error(t, NameValidator(strings.Repeat("a", 200))) } +func TestNodeConfigValidator_DnsResolver(t *testing.T) { + assert.NoError(t, NodeConfigValidator(&LocalCfg{ + Id: "valid-node", + Port: 5, + Key: [32]byte{1}, + DnsResolvers: []string{"1.1.1.1:53"}, + })) + assert.NoError(t, NodeConfigValidator(&LocalCfg{ + Id: "valid-node", + Port: 5, + Key: [32]byte{1}, + })) + assert.Error(t, NodeConfigValidator(&LocalCfg{ + Id: "invalid-node", + Port: 5, + Key: [32]byte{1}, + DnsResolvers: []string{"google.com"}, + })) + assert.Error(t, NodeConfigValidator(&LocalCfg{ + Id: "invalid-node", + Port: 5, + Key: [32]byte{1}, + DnsResolvers: []string{"google.com:53"}, + })) + assert.Error(t, NodeConfigValidator(&LocalCfg{ + Id: "invalid-node", + Port: 5, + Key: [32]byte{1}, + DnsResolvers: []string{"1.1.1.1"}, + })) +} + func TestCentralConfigValidator_OverlappingService(t *testing.T) { cfg := &CentralCfg{ Services: map[ServiceId]netip.Prefix{