From 8cfc895b24f94e0af0f3508a23df630475176381 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Sun, 19 Jan 2025 19:59:30 +0100 Subject: [PATCH 01/10] Add reverse dns zone --- client/internal/dns.go | 85 +++++++++++++++++++++++++++++++++++++++ client/internal/engine.go | 9 +++-- 2 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 client/internal/dns.go diff --git a/client/internal/dns.go b/client/internal/dns.go new file mode 100644 index 00000000000..b9b8a1ca93e --- /dev/null +++ b/client/internal/dns.go @@ -0,0 +1,85 @@ +package internal + +import ( + "net" + "slices" + "strings" + + "github.com/miekg/dns" + log "github.com/sirupsen/logrus" + + nbdns "github.com/netbirdio/netbird/dns" +) + +func createPTRRecord(aRecord nbdns.SimpleRecord, ipNet *net.IPNet) (nbdns.SimpleRecord, bool) { + ip := net.ParseIP(aRecord.RData) + if ip == nil || ip.To4() == nil { + return nbdns.SimpleRecord{}, false + } + + if !ipNet.Contains(ip) { + return nbdns.SimpleRecord{}, false + } + + ipOctets := strings.Split(ip.String(), ".") + slices.Reverse(ipOctets) + rdnsName := dns.Fqdn(strings.Join(ipOctets, ".") + ".in-addr.arpa") + + return nbdns.SimpleRecord{ + Name: rdnsName, + Type: int(dns.TypePTR), + Class: aRecord.Class, + TTL: aRecord.TTL, + RData: dns.Fqdn(aRecord.Name), + }, true +} + +func addReverseZone(config *nbdns.Config, ipNet *net.IPNet) { + networkIP := ipNet.IP.Mask(ipNet.Mask) + + maskOnes, _ := ipNet.Mask.Size() + // round up to nearest byte + octetsToUse := (maskOnes + 7) / 8 + + octets := strings.Split(networkIP.String(), ".") + if octetsToUse > len(octets) { + log.Warnf("invalid network mask size for reverse DNS: %d", maskOnes) + return + } + + reverseOctets := make([]string, octetsToUse) + for i := 0; i < octetsToUse; i++ { + reverseOctets[octetsToUse-1-i] = octets[i] + } + + zoneName := dns.Fqdn(strings.Join(reverseOctets, ".") + ".in-addr.arpa") + + for _, zone := range config.CustomZones { + if zone.Domain == zoneName { + log.Debugf("reverse DNS zone %s already exists", zoneName) + return + } + } + + var records []nbdns.SimpleRecord + + for _, zone := range config.CustomZones { + for _, record := range zone.Records { + if record.Type != int(dns.TypeA) { + continue + } + + if ptrRecord, ok := createPTRRecord(record, ipNet); ok { + records = append(records, ptrRecord) + } + } + } + + reverseZone := nbdns.CustomZone{ + Domain: zoneName, + Records: records, + } + + config.CustomZones = append(config.CustomZones, reverseZone) + log.Debugf("added reverse DNS zone: %s with %d records", zoneName, len(records)) +} diff --git a/client/internal/engine.go b/client/internal/engine.go index b3689c91153..dceb08ca583 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -972,7 +972,7 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error { protoDNSConfig = &mgmProto.DNSConfig{} } - if err := e.dnsServer.UpdateDNSServer(serial, toDNSConfig(protoDNSConfig)); err != nil { + if err := e.dnsServer.UpdateDNSServer(serial, toDNSConfig(protoDNSConfig, e.wgInterface.Address().Network)); err != nil { log.Errorf("failed to update dns server, err: %v", err) } @@ -1041,7 +1041,7 @@ func toRouteDomains(myPubKey string, protoRoutes []*mgmProto.Route) []string { return dnsRoutes } -func toDNSConfig(protoDNSConfig *mgmProto.DNSConfig) nbdns.Config { +func toDNSConfig(protoDNSConfig *mgmProto.DNSConfig, network *net.IPNet) nbdns.Config { dnsUpdate := nbdns.Config{ ServiceEnable: protoDNSConfig.GetServiceEnable(), CustomZones: make([]nbdns.CustomZone, 0), @@ -1081,6 +1081,9 @@ func toDNSConfig(protoDNSConfig *mgmProto.DNSConfig) nbdns.Config { } dnsUpdate.NameServerGroups = append(dnsUpdate.NameServerGroups, dnsNSGroup) } + + addReverseZone(&dnsUpdate, network) + return dnsUpdate } @@ -1387,7 +1390,7 @@ func (e *Engine) readInitialSettings() ([]*route.Route, *nbdns.Config, error) { return nil, nil, err } routes := toRoutes(netMap.GetRoutes()) - dnsCfg := toDNSConfig(netMap.GetDNSConfig()) + dnsCfg := toDNSConfig(netMap.GetDNSConfig(), e.wgInterface.Address().Network) return routes, &dnsCfg, nil } From a0ebf7e67ea088c00b522d5969e97a52694ebe8a Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Sun, 19 Jan 2025 20:24:13 +0100 Subject: [PATCH 02/10] Skip records and zones on errors instead of failing --- client/internal/dns/server.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/internal/dns/server.go b/client/internal/dns/server.go index 1fe913fd9c1..408fe8cbc0b 100644 --- a/client/internal/dns/server.go +++ b/client/internal/dns/server.go @@ -425,7 +425,8 @@ func (s *DefaultServer) buildLocalHandlerUpdate(customZones []nbdns.CustomZone) for _, customZone := range customZones { if len(customZone.Records) == 0 { - return nil, nil, fmt.Errorf("received an empty list of records") + log.Warnf("received a custom zone with empty records, skipping domain: %s", customZone.Domain) + continue } muxUpdates = append(muxUpdates, muxUpdate{ @@ -437,7 +438,8 @@ func (s *DefaultServer) buildLocalHandlerUpdate(customZones []nbdns.CustomZone) for _, record := range customZone.Records { var class uint16 = dns.ClassINET if record.Class != nbdns.DefaultClass { - return nil, nil, fmt.Errorf("received an invalid class type: %s", record.Class) + log.Warnf("received an invalid class type: %s", record.Class) + continue } key := buildRecordKey(record.Name, class, uint16(record.Type)) localRecords[key] = record From 21ad8d277abf7094ca2e441528988b025411494d Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Sun, 19 Jan 2025 20:35:14 +0100 Subject: [PATCH 03/10] Improve error messages --- client/internal/dns/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/internal/dns/server.go b/client/internal/dns/server.go index 408fe8cbc0b..f7f6b381c8f 100644 --- a/client/internal/dns/server.go +++ b/client/internal/dns/server.go @@ -380,11 +380,11 @@ func (s *DefaultServer) applyConfiguration(update nbdns.Config) error { localMuxUpdates, localRecords, err := s.buildLocalHandlerUpdate(update.CustomZones) if err != nil { - return fmt.Errorf("not applying dns update, error: %v", err) + return fmt.Errorf("local handler updater: %w", err) } upstreamMuxUpdates, err := s.buildUpstreamHandlerUpdate(update.NameServerGroups) if err != nil { - return fmt.Errorf("not applying dns update, error: %v", err) + return fmt.Errorf("upstream handler updater: %w", err) } muxUpdates := append(localMuxUpdates, upstreamMuxUpdates...) //nolint:gocritic From 27fc95326d7ef95c8b60724f7552b035c5266746 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Sun, 19 Jan 2025 20:37:25 +0100 Subject: [PATCH 04/10] Fix test --- client/internal/engine_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index ca49eca09f6..a92b5b77228 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -250,6 +250,15 @@ func TestEngine_UpdateNetworkMap(t *testing.T) { RemovePeerFunc: func(peerKey string) error { return nil }, + AddressFunc: func() iface.WGAddress { + return iface.WGAddress{ + IP: net.ParseIP("10.20.0.1"), + Network: &net.IPNet{ + IP: net.ParseIP("10.20.0.0"), + Mask: net.IPv4Mask(255, 255, 255, 0), + }, + } + }, } engine.wgInterface = wgIface engine.routeManager = routemanager.NewManager(routemanager.ManagerConfig{ From 6b1de5158b7aa7b8b7e866c0731e82d40cc786a6 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Sun, 19 Jan 2025 21:27:02 +0100 Subject: [PATCH 05/10] Fix engine test --- client/internal/engine.go | 4 +++- client/internal/engine_test.go | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/client/internal/engine.go b/client/internal/engine.go index dceb08ca583..a936a844c16 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -1082,7 +1082,9 @@ func toDNSConfig(protoDNSConfig *mgmProto.DNSConfig, network *net.IPNet) nbdns.C dnsUpdate.NameServerGroups = append(dnsUpdate.NameServerGroups, dnsNSGroup) } - addReverseZone(&dnsUpdate, network) + if len(dnsUpdate.CustomZones) > 0 { + addReverseZone(&dnsUpdate, network) + } return dnsUpdate } diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index a92b5b77228..f19abdf7083 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -701,6 +701,9 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) { }, }, }, + { + Domain: "0.66.100.in-addr.arpa.", + }, }, NameServerGroups: []*mgmtProto.NameServerGroup{ { @@ -730,6 +733,9 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) { }, }, }, + { + Domain: "0.66.100.in-addr.arpa.", + }, }, expectedNSGroupsLen: 1, expectedNSGroups: []*nbdns.NameServerGroup{ From ce1c824016238dd0a9da3e2c64b1d86b8e8f9bb8 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Sun, 19 Jan 2025 22:20:10 +0100 Subject: [PATCH 06/10] Fix DNS test --- client/internal/dns/server_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go index c166820c457..e0cb634d3e4 100644 --- a/client/internal/dns/server_test.go +++ b/client/internal/dns/server_test.go @@ -220,7 +220,7 @@ func TestUpdateDNSServer(t *testing.T) { shouldFail: true, }, { - name: "Invalid Custom Zone Records list Should Fail", + name: "Invalid Custom Zone Records list Should Skip", initLocalMap: make(registrationMap), initUpstreamMap: make(registeredHandlerMap), initSerial: 0, @@ -239,7 +239,7 @@ func TestUpdateDNSServer(t *testing.T) { }, }, }, - shouldFail: true, + expectedUpstreamMap: registeredHandlerMap{".": dummyHandler}, }, { name: "Empty Config Should Succeed and Clean Maps", From 70297446ccfba35291db3092d4087d7b38cf573b Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Fri, 14 Feb 2025 18:22:51 +0100 Subject: [PATCH 07/10] Exclude reverse zone from search domains --- client/internal/dns/host.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/internal/dns/host.go b/client/internal/dns/host.go index fbe8c4dbbb1..cfc0cc3c389 100644 --- a/client/internal/dns/host.go +++ b/client/internal/dns/host.go @@ -9,6 +9,11 @@ import ( nbdns "github.com/netbirdio/netbird/dns" ) +const ( + ipv4ReverseZone = ".in-addr.arpa" + ipv6ReverseZone = ".ip6.arpa" +) + type hostManager interface { applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error restoreHostDNS() error @@ -94,9 +99,10 @@ func dnsConfigToHostDNSConfig(dnsConfig nbdns.Config, ip string, port int) HostD } for _, customZone := range dnsConfig.CustomZones { + matchOnly := strings.HasSuffix(customZone.Domain, ipv4ReverseZone) || strings.HasSuffix(customZone.Domain, ipv6ReverseZone) config.Domains = append(config.Domains, DomainConfig{ Domain: strings.TrimSuffix(customZone.Domain, "."), - MatchOnly: false, + MatchOnly: matchOnly, }) } From 666ad6947187229b17637c71fe89c2e7e7fc5d93 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Fri, 14 Feb 2025 19:15:42 +0100 Subject: [PATCH 08/10] Fix test --- client/internal/dns/server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go index 1c6efdc9675..b4f2af43789 100644 --- a/client/internal/dns/server_test.go +++ b/client/internal/dns/server_test.go @@ -285,7 +285,7 @@ func TestUpdateDNSServer(t *testing.T) { }, }, }, - expectedUpstreamMap: registeredHandlerMap{".": dummyHandler}, + expectedUpstreamMap: registeredHandlerMap{".": handlerWrapper{handler: &localResolver{}}}, }, { name: "Empty Config Should Succeed and Clean Maps", From 4a1f8b9fa8c771a9b78878ae718c0118d2a2657b Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Fri, 14 Feb 2025 20:35:04 +0100 Subject: [PATCH 09/10] Fix test properly --- client/internal/dns/server_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go index b4f2af43789..11bceb7d3d5 100644 --- a/client/internal/dns/server_test.go +++ b/client/internal/dns/server_test.go @@ -285,7 +285,11 @@ func TestUpdateDNSServer(t *testing.T) { }, }, }, - expectedUpstreamMap: registeredHandlerMap{".": handlerWrapper{handler: &localResolver{}}}, + expectedUpstreamMap: registeredHandlerMap{generateDummyHandler(".", nameServers).id(): handlerWrapper{ + domain: ".", + handler: dummyHandler, + priority: PriorityDefault, + }}, }, { name: "Empty Config Should Succeed and Clean Maps", From 38a5c06b4d392a8fc1c081a45f8cea17a56676c3 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Fri, 21 Feb 2025 12:24:37 +0100 Subject: [PATCH 10/10] Reduce complexity --- client/internal/dns.go | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/client/internal/dns.go b/client/internal/dns.go index b9b8a1ca93e..8a73f50f2be 100644 --- a/client/internal/dns.go +++ b/client/internal/dns.go @@ -1,6 +1,7 @@ package internal import ( + "fmt" "net" "slices" "strings" @@ -34,17 +35,17 @@ func createPTRRecord(aRecord nbdns.SimpleRecord, ipNet *net.IPNet) (nbdns.Simple }, true } -func addReverseZone(config *nbdns.Config, ipNet *net.IPNet) { +// generateReverseZoneName creates the reverse DNS zone name for a given network +func generateReverseZoneName(ipNet *net.IPNet) (string, error) { networkIP := ipNet.IP.Mask(ipNet.Mask) - maskOnes, _ := ipNet.Mask.Size() + // round up to nearest byte octetsToUse := (maskOnes + 7) / 8 octets := strings.Split(networkIP.String(), ".") if octetsToUse > len(octets) { - log.Warnf("invalid network mask size for reverse DNS: %d", maskOnes) - return + return "", fmt.Errorf("invalid network mask size for reverse DNS: %d", maskOnes) } reverseOctets := make([]string, octetsToUse) @@ -52,15 +53,22 @@ func addReverseZone(config *nbdns.Config, ipNet *net.IPNet) { reverseOctets[octetsToUse-1-i] = octets[i] } - zoneName := dns.Fqdn(strings.Join(reverseOctets, ".") + ".in-addr.arpa") + return dns.Fqdn(strings.Join(reverseOctets, ".") + ".in-addr.arpa"), nil +} +// zoneExists checks if a zone with the given name already exists in the configuration +func zoneExists(config *nbdns.Config, zoneName string) bool { for _, zone := range config.CustomZones { if zone.Domain == zoneName { log.Debugf("reverse DNS zone %s already exists", zoneName) - return + return true } } + return false +} +// collectPTRRecords gathers all PTR records for the given network from A records +func collectPTRRecords(config *nbdns.Config, ipNet *net.IPNet) []nbdns.SimpleRecord { var records []nbdns.SimpleRecord for _, zone := range config.CustomZones { @@ -75,6 +83,24 @@ func addReverseZone(config *nbdns.Config, ipNet *net.IPNet) { } } + return records +} + +// addReverseZone adds a reverse DNS zone to the configuration for the given network +func addReverseZone(config *nbdns.Config, ipNet *net.IPNet) { + zoneName, err := generateReverseZoneName(ipNet) + if err != nil { + log.Warn(err) + return + } + + if zoneExists(config, zoneName) { + log.Debugf("reverse DNS zone %s already exists", zoneName) + return + } + + records := collectPTRRecords(config, ipNet) + reverseZone := nbdns.CustomZone{ Domain: zoneName, Records: records,