2022-11-03 18:39:37 +01:00
package dns
import (
"context"
"fmt"
"net"
"net/netip"
2023-02-13 15:25:11 +01:00
"strings"
2022-11-03 18:39:37 +01:00
"testing"
"time"
2023-02-13 15:25:11 +01:00
2023-04-13 17:00:01 +02:00
"github.com/netbirdio/netbird/client/internal/stdnet"
2023-02-13 15:25:11 +01:00
"github.com/miekg/dns"
2023-04-13 17:00:01 +02:00
2023-02-13 15:25:11 +01:00
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/iface"
2022-11-03 18:39:37 +01:00
)
var zoneRecords = [ ] nbdns . SimpleRecord {
{
Name : "peera.netbird.cloud" ,
Type : 1 ,
Class : nbdns . DefaultClass ,
TTL : 300 ,
RData : "1.2.3.4" ,
} ,
}
func TestUpdateDNSServer ( t * testing . T ) {
nameServers := [ ] nbdns . NameServer {
{
IP : netip . MustParseAddr ( "8.8.8.8" ) ,
NSType : nbdns . UDPNameServerType ,
Port : 53 ,
} ,
{
IP : netip . MustParseAddr ( "8.8.4.4" ) ,
NSType : nbdns . UDPNameServerType ,
Port : 53 ,
} ,
}
testCases := [ ] struct {
name string
initUpstreamMap registrationMap
initLocalMap registrationMap
initSerial uint64
inputSerial uint64
2022-11-07 15:38:21 +01:00
inputUpdate nbdns . Config
2022-11-03 18:39:37 +01:00
shouldFail bool
expectedUpstreamMap registrationMap
expectedLocalMap registrationMap
} {
{
2022-11-07 15:38:21 +01:00
name : "Initial Config Should Succeed" ,
2022-11-03 18:39:37 +01:00
initLocalMap : make ( registrationMap ) ,
initUpstreamMap : make ( registrationMap ) ,
initSerial : 0 ,
inputSerial : 1 ,
2022-11-07 15:38:21 +01:00
inputUpdate : nbdns . Config {
2022-11-03 18:39:37 +01:00
ServiceEnable : true ,
CustomZones : [ ] nbdns . CustomZone {
{
Domain : "netbird.cloud" ,
Records : zoneRecords ,
} ,
} ,
2022-11-07 15:38:21 +01:00
NameServerGroups : [ ] * nbdns . NameServerGroup {
2022-11-03 18:39:37 +01:00
{
Domains : [ ] string { "netbird.io" } ,
NameServers : nameServers ,
} ,
{
NameServers : nameServers ,
Primary : true ,
} ,
} ,
} ,
expectedUpstreamMap : registrationMap { "netbird.io" : struct { } { } , "netbird.cloud" : struct { } { } , nbdns . RootZone : struct { } { } } ,
2022-11-23 13:39:42 +01:00
expectedLocalMap : registrationMap { buildRecordKey ( zoneRecords [ 0 ] . Name , 1 , 1 ) : struct { } { } } ,
2022-11-03 18:39:37 +01:00
} ,
{
2022-11-07 15:38:21 +01:00
name : "New Config Should Succeed" ,
2022-11-03 18:39:37 +01:00
initLocalMap : registrationMap { "netbird.cloud" : struct { } { } } ,
2022-11-23 13:39:42 +01:00
initUpstreamMap : registrationMap { buildRecordKey ( zoneRecords [ 0 ] . Name , 1 , 1 ) : struct { } { } } ,
2022-11-03 18:39:37 +01:00
initSerial : 0 ,
inputSerial : 1 ,
2022-11-07 15:38:21 +01:00
inputUpdate : nbdns . Config {
2022-11-03 18:39:37 +01:00
ServiceEnable : true ,
CustomZones : [ ] nbdns . CustomZone {
{
Domain : "netbird.cloud" ,
Records : zoneRecords ,
} ,
} ,
2022-11-07 15:38:21 +01:00
NameServerGroups : [ ] * nbdns . NameServerGroup {
2022-11-03 18:39:37 +01:00
{
Domains : [ ] string { "netbird.io" } ,
NameServers : nameServers ,
} ,
} ,
} ,
expectedUpstreamMap : registrationMap { "netbird.io" : struct { } { } , "netbird.cloud" : struct { } { } } ,
2022-11-23 13:39:42 +01:00
expectedLocalMap : registrationMap { buildRecordKey ( zoneRecords [ 0 ] . Name , 1 , 1 ) : struct { } { } } ,
2022-11-03 18:39:37 +01:00
} ,
{
2022-11-07 15:38:21 +01:00
name : "Smaller Config Serial Should Be Skipped" ,
2022-11-03 18:39:37 +01:00
initLocalMap : make ( registrationMap ) ,
initUpstreamMap : make ( registrationMap ) ,
initSerial : 2 ,
inputSerial : 1 ,
shouldFail : true ,
} ,
{
name : "Empty NS Group Domain Or Not Primary Element Should Fail" ,
initLocalMap : make ( registrationMap ) ,
initUpstreamMap : make ( registrationMap ) ,
initSerial : 0 ,
inputSerial : 1 ,
2022-11-07 15:38:21 +01:00
inputUpdate : nbdns . Config {
2022-11-03 18:39:37 +01:00
ServiceEnable : true ,
CustomZones : [ ] nbdns . CustomZone {
{
Domain : "netbird.cloud" ,
Records : zoneRecords ,
} ,
} ,
2022-11-07 15:38:21 +01:00
NameServerGroups : [ ] * nbdns . NameServerGroup {
2022-11-03 18:39:37 +01:00
{
NameServers : nameServers ,
} ,
} ,
} ,
shouldFail : true ,
} ,
{
name : "Invalid NS Group Nameservers list Should Fail" ,
initLocalMap : make ( registrationMap ) ,
initUpstreamMap : make ( registrationMap ) ,
initSerial : 0 ,
inputSerial : 1 ,
2022-11-07 15:38:21 +01:00
inputUpdate : nbdns . Config {
2022-11-03 18:39:37 +01:00
ServiceEnable : true ,
CustomZones : [ ] nbdns . CustomZone {
{
Domain : "netbird.cloud" ,
Records : zoneRecords ,
} ,
} ,
2022-11-07 15:38:21 +01:00
NameServerGroups : [ ] * nbdns . NameServerGroup {
2022-11-03 18:39:37 +01:00
{
NameServers : nameServers ,
} ,
} ,
} ,
shouldFail : true ,
} ,
{
name : "Invalid Custom Zone Records list Should Fail" ,
initLocalMap : make ( registrationMap ) ,
initUpstreamMap : make ( registrationMap ) ,
initSerial : 0 ,
inputSerial : 1 ,
2022-11-07 15:38:21 +01:00
inputUpdate : nbdns . Config {
2022-11-03 18:39:37 +01:00
ServiceEnable : true ,
CustomZones : [ ] nbdns . CustomZone {
{
Domain : "netbird.cloud" ,
} ,
} ,
2022-11-07 15:38:21 +01:00
NameServerGroups : [ ] * nbdns . NameServerGroup {
2022-11-03 18:39:37 +01:00
{
NameServers : nameServers ,
Primary : true ,
} ,
} ,
} ,
shouldFail : true ,
} ,
{
2022-11-07 15:38:21 +01:00
name : "Empty Config Should Succeed and Clean Maps" ,
2022-11-03 18:39:37 +01:00
initLocalMap : registrationMap { "netbird.cloud" : struct { } { } } ,
initUpstreamMap : registrationMap { zoneRecords [ 0 ] . Name : struct { } { } } ,
initSerial : 0 ,
inputSerial : 1 ,
2022-11-07 15:38:21 +01:00
inputUpdate : nbdns . Config { ServiceEnable : true } ,
2022-11-03 18:39:37 +01:00
expectedUpstreamMap : make ( registrationMap ) ,
expectedLocalMap : make ( registrationMap ) ,
} ,
2022-12-13 12:26:48 +01:00
{
name : "Disabled Service Should clean map" ,
initLocalMap : registrationMap { "netbird.cloud" : struct { } { } } ,
initUpstreamMap : registrationMap { zoneRecords [ 0 ] . Name : struct { } { } } ,
initSerial : 0 ,
inputSerial : 1 ,
inputUpdate : nbdns . Config { ServiceEnable : false } ,
expectedUpstreamMap : make ( registrationMap ) ,
expectedLocalMap : make ( registrationMap ) ,
} ,
2022-11-03 18:39:37 +01:00
}
2022-12-13 12:26:48 +01:00
for n , testCase := range testCases {
2022-11-03 18:39:37 +01:00
t . Run ( testCase . name , func ( t * testing . T ) {
2023-04-13 17:00:01 +02:00
newNet , err := stdnet . NewNet ( nil )
if err != nil {
t . Fatal ( err )
}
2023-04-17 11:15:37 +02:00
wgIface , err := iface . NewWGIFace ( fmt . Sprintf ( "utun230%d" , n ) , fmt . Sprintf ( "100.66.100.%d/32" , n + 1 ) , iface . DefaultMTU , nil , nil , newNet )
2022-12-13 12:26:48 +01:00
if err != nil {
t . Fatal ( err )
}
err = wgIface . Create ( )
if err != nil {
t . Fatal ( err )
}
defer func ( ) {
err = wgIface . Close ( )
if err != nil {
t . Log ( err )
}
} ( )
2023-01-17 19:16:50 +01:00
dnsServer , err := NewDefaultServer ( context . Background ( ) , wgIface , "" )
2022-12-13 12:26:48 +01:00
if err != nil {
t . Fatal ( err )
}
defer func ( ) {
err = dnsServer . hostManager . restoreHostDNS ( )
if err != nil {
t . Log ( err )
}
} ( )
2022-11-03 18:39:37 +01:00
dnsServer . dnsMuxMap = testCase . initUpstreamMap
dnsServer . localResolver . registeredMap = testCase . initLocalMap
dnsServer . updateSerial = testCase . initSerial
2022-11-23 13:39:42 +01:00
// pretend we are running
2022-11-03 18:39:37 +01:00
dnsServer . listenerIsRunning = true
2022-12-13 12:26:48 +01:00
err = dnsServer . UpdateDNSServer ( testCase . inputSerial , testCase . inputUpdate )
2022-11-03 18:39:37 +01:00
if err != nil {
if testCase . shouldFail {
return
}
t . Fatalf ( "update dns server should not fail, got error: %v" , err )
}
if len ( dnsServer . dnsMuxMap ) != len ( testCase . expectedUpstreamMap ) {
t . Fatalf ( "update upstream failed, map size is different than expected, want %d, got %d" , len ( testCase . expectedUpstreamMap ) , len ( dnsServer . dnsMuxMap ) )
}
for key := range testCase . expectedUpstreamMap {
_ , found := dnsServer . dnsMuxMap [ key ]
if ! found {
t . Fatalf ( "update upstream failed, key %s was not found in the dnsMuxMap: %#v" , key , dnsServer . dnsMuxMap )
}
}
if len ( dnsServer . localResolver . registeredMap ) != len ( testCase . expectedLocalMap ) {
t . Fatalf ( "update local failed, registered map size is different than expected, want %d, got %d" , len ( testCase . expectedLocalMap ) , len ( dnsServer . localResolver . registeredMap ) )
}
for key := range testCase . expectedLocalMap {
_ , found := dnsServer . localResolver . registeredMap [ key ]
if ! found {
t . Fatalf ( "update local failed, key %s was not found in the localResolver.registeredMap: %#v" , key , dnsServer . localResolver . registeredMap )
}
}
} )
}
}
func TestDNSServerStartStop ( t * testing . T ) {
2023-01-17 19:16:50 +01:00
testCases := [ ] struct {
name string
addrPort string
} {
{
name : "Should Pass With Port Discovery" ,
} ,
{
name : "Should Pass With Custom Port" ,
addrPort : "127.0.0.1:3535" ,
} ,
2022-11-03 18:39:37 +01:00
}
2023-01-17 19:16:50 +01:00
for _ , testCase := range testCases {
t . Run ( testCase . name , func ( t * testing . T ) {
dnsServer := getDefaultServerWithNoHostManager ( t , testCase . addrPort )
2022-11-03 18:39:37 +01:00
2023-01-17 19:16:50 +01:00
dnsServer . hostManager = newNoopHostMocker ( )
dnsServer . Start ( )
time . Sleep ( 100 * time . Millisecond )
if ! dnsServer . listenerIsRunning {
t . Fatal ( "dns server listener is not running" )
2022-11-03 18:39:37 +01:00
}
2023-01-17 19:16:50 +01:00
defer dnsServer . Stop ( )
err := dnsServer . localResolver . registerRecord ( zoneRecords [ 0 ] )
2022-11-03 18:39:37 +01:00
if err != nil {
2023-01-17 19:16:50 +01:00
t . Error ( err )
2022-11-03 18:39:37 +01:00
}
2023-01-17 19:16:50 +01:00
dnsServer . dnsMux . Handle ( "netbird.cloud" , dnsServer . localResolver )
2022-11-03 18:39:37 +01:00
2023-01-17 19:16:50 +01:00
resolver := & net . Resolver {
PreferGo : true ,
Dial : func ( ctx context . Context , network , address string ) ( net . Conn , error ) {
d := net . Dialer {
Timeout : time . Second * 5 ,
}
addr := fmt . Sprintf ( "%s:%d" , dnsServer . runtimeIP , dnsServer . runtimePort )
conn , err := d . DialContext ( ctx , network , addr )
if err != nil {
t . Log ( err )
// retry test before exit, for slower systems
return d . DialContext ( ctx , network , addr )
}
2022-11-03 18:39:37 +01:00
2023-01-17 19:16:50 +01:00
return conn , nil
} ,
}
2022-11-03 18:39:37 +01:00
2023-01-17 19:16:50 +01:00
ips , err := resolver . LookupHost ( context . Background ( ) , zoneRecords [ 0 ] . Name )
if err != nil {
t . Fatalf ( "failed to connect to the server, error: %v" , err )
}
2022-11-03 18:39:37 +01:00
2023-01-17 19:16:50 +01:00
if ips [ 0 ] != zoneRecords [ 0 ] . RData {
t . Fatalf ( "got a different IP from the server: want %s, got %s" , zoneRecords [ 0 ] . RData , ips [ 0 ] )
}
dnsServer . Stop ( )
ctx , cancel := context . WithTimeout ( context . TODO ( ) , time . Second * 1 )
defer cancel ( )
_ , err = resolver . LookupHost ( ctx , zoneRecords [ 0 ] . Name )
if err == nil {
t . Fatalf ( "we should encounter an error when querying a stopped server" )
}
} )
2022-11-03 18:39:37 +01:00
}
}
2022-11-23 13:39:42 +01:00
2023-02-13 15:25:11 +01:00
func TestDNSServerUpstreamDeactivateCallback ( t * testing . T ) {
hostManager := & mockHostConfigurator { }
server := DefaultServer {
dnsMux : dns . DefaultServeMux ,
localResolver : & localResolver {
registeredMap : make ( registrationMap ) ,
} ,
hostManager : hostManager ,
currentConfig : hostDNSConfig {
domains : [ ] domainConfig {
{ false , "domain0" , false } ,
{ false , "domain1" , false } ,
{ false , "domain2" , false } ,
} ,
} ,
}
var domainsUpdate string
hostManager . applyDNSConfigFunc = func ( config hostDNSConfig ) error {
domains := [ ] string { }
for _ , item := range config . domains {
if item . disabled {
continue
}
domains = append ( domains , item . domain )
}
domainsUpdate = strings . Join ( domains , "," )
return nil
}
deactivate , reactivate := server . upstreamCallbacks ( & nbdns . NameServerGroup {
Domains : [ ] string { "domain1" } ,
NameServers : [ ] nbdns . NameServer {
{ IP : netip . MustParseAddr ( "8.8.0.0" ) , NSType : nbdns . UDPNameServerType , Port : 53 } ,
} ,
} , nil )
deactivate ( )
expected := "domain0,domain2"
domains := [ ] string { }
for _ , item := range server . currentConfig . domains {
if item . disabled {
continue
}
domains = append ( domains , item . domain )
}
got := strings . Join ( domains , "," )
if expected != got {
t . Errorf ( "expected domains list: %q, got %q" , expected , got )
}
reactivate ( )
expected = "domain0,domain1,domain2"
domains = [ ] string { }
for _ , item := range server . currentConfig . domains {
if item . disabled {
continue
}
domains = append ( domains , item . domain )
}
got = strings . Join ( domains , "," )
if expected != got {
t . Errorf ( "expected domains list: %q, got %q" , expected , domainsUpdate )
}
}
2023-01-17 19:16:50 +01:00
func getDefaultServerWithNoHostManager ( t * testing . T , addrPort string ) * DefaultServer {
2022-11-23 13:39:42 +01:00
mux := dns . NewServeMux ( )
2023-01-17 19:16:50 +01:00
var parsedAddrPort * netip . AddrPort
if addrPort != "" {
parsed , err := netip . ParseAddrPort ( addrPort )
if err != nil {
t . Fatal ( err )
}
parsedAddrPort = & parsed
2022-11-23 13:39:42 +01:00
}
dnsServer := & dns . Server {
Net : "udp" ,
Handler : mux ,
UDPSize : 65535 ,
}
2023-02-13 15:25:11 +01:00
ctx , cancel := context . WithCancel ( context . TODO ( ) )
2022-11-23 13:39:42 +01:00
return & DefaultServer {
ctx : ctx ,
2023-02-13 15:25:11 +01:00
ctxCancel : cancel ,
2022-11-23 13:39:42 +01:00
server : dnsServer ,
dnsMux : mux ,
dnsMuxMap : make ( registrationMap ) ,
localResolver : & localResolver {
registeredMap : make ( registrationMap ) ,
} ,
2023-01-17 19:16:50 +01:00
customAddress : parsedAddrPort ,
2022-11-23 13:39:42 +01:00
}
}