From cdc019dfe97e38e7a2d59c727b5bbf0f7de0197e Mon Sep 17 00:00:00 2001 From: Adam Chen Date: Fri, 14 Nov 2025 02:53:10 +0000 Subject: [PATCH 1/2] feat: allow manual configuration of dns resolver --- core/nylon.go | 22 ++++++++++++++++++++++ example/sample-node.yaml | 1 + state/config.go | 1 + state/validation.go | 7 +++++++ state/validation_test.go | 27 +++++++++++++++++++++++++++ 5 files changed, 58 insertions(+) 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..22a2c71 100644 --- a/state/config.go +++ b/state/config.go @@ -74,6 +74,7 @@ type LocalCfg struct { DisableRouting bool UseSystemRouting bool NoNetConfigure bool `yaml:",omitempty"` + DnsResolvers []string 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..edb1a42 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -23,6 +23,33 @@ 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}, + DnsResolver: "1.1.1.1:53", + })) + assert.Error(t, NodeConfigValidator(&LocalCfg{ + Id: "invalid-node", + Port: 5, + Key: [32]byte{1}, + DnsResolver: "google.com", + })) + assert.Error(t, NodeConfigValidator(&LocalCfg{ + Id: "invalid-node", + Port: 5, + Key: [32]byte{1}, + DnsResolver: "google.com:53", + })) + assert.Error(t, NodeConfigValidator(&LocalCfg{ + Id: "invalid-node", + Port: 5, + Key: [32]byte{1}, + DnsResolver: "1.1.1.1", + })) +} + func TestCentralConfigValidator_OverlappingService(t *testing.T) { cfg := &CentralCfg{ Services: map[ServiceId]netip.Prefix{ From 01d8b61714baa7722c8e226ee8c345f4be1ca6d2 Mon Sep 17 00:00:00 2001 From: Adam Chen Date: Sat, 15 Nov 2025 06:05:42 +0000 Subject: [PATCH 2/2] fix test --- state/config.go | 4 ++-- state/validation_test.go | 37 +++++++++++++++++++++---------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/state/config.go b/state/config.go index 22a2c71..aa2ef66 100644 --- a/state/config.go +++ b/state/config.go @@ -73,8 +73,8 @@ type LocalCfg struct { Dist *LocalDistributionCfg `yaml:",omitempty"` DisableRouting bool UseSystemRouting bool - NoNetConfigure bool `yaml:",omitempty"` - DnsResolvers []string + NoNetConfigure bool `yaml:",omitempty"` + DnsResolvers []string `yaml:",omitempty"` InterfaceName string LogPath string } diff --git a/state/validation_test.go b/state/validation_test.go index edb1a42..70462db 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -25,28 +25,33 @@ func TestNameValidator_Invalid(t *testing.T) { func TestNodeConfigValidator_DnsResolver(t *testing.T) { assert.NoError(t, NodeConfigValidator(&LocalCfg{ - Id: "valid-node", - Port: 5, - Key: [32]byte{1}, - DnsResolver: "1.1.1.1:53", + 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}, - DnsResolver: "google.com", + 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}, - DnsResolver: "google.com:53", + 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}, - DnsResolver: "1.1.1.1", + Id: "invalid-node", + Port: 5, + Key: [32]byte{1}, + DnsResolvers: []string{"1.1.1.1"}, })) }