@ -99,6 +99,7 @@ func TestDomainRoutes(t *testing.T) {
a := NewAppConnector ( t . Logf , rc )
a := NewAppConnector ( t . Logf , rc )
a . updateDomains ( [ ] string { "example.com" } )
a . updateDomains ( [ ] string { "example.com" } )
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "192.0.0.8" ) )
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "192.0.0.8" ) )
a . Wait ( context . Background ( ) )
want := map [ string ] [ ] netip . Addr {
want := map [ string ] [ ] netip . Addr {
"example.com" : { netip . MustParseAddr ( "192.0.0.8" ) } ,
"example.com" : { netip . MustParseAddr ( "192.0.0.8" ) } ,
@ -110,6 +111,7 @@ func TestDomainRoutes(t *testing.T) {
}
}
func TestObserveDNSResponse ( t * testing . T ) {
func TestObserveDNSResponse ( t * testing . T ) {
ctx := context . Background ( )
rc := & appctest . RouteCollector { }
rc := & appctest . RouteCollector { }
a := NewAppConnector ( t . Logf , rc )
a := NewAppConnector ( t . Logf , rc )
@ -123,6 +125,26 @@ func TestObserveDNSResponse(t *testing.T) {
a . updateDomains ( [ ] string { "example.com" } )
a . updateDomains ( [ ] string { "example.com" } )
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "192.0.0.8" ) )
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "192.0.0.8" ) )
a . Wait ( ctx )
if got , want := rc . Routes ( ) , wantRoutes ; ! slices . Equal ( got , want ) {
t . Errorf ( "got %v; want %v" , got , want )
}
// a CNAME record chain should result in a route being added if the chain
// matches a routed domain.
a . updateDomains ( [ ] string { "www.example.com" , "example.com" } )
a . ObserveDNSResponse ( dnsCNAMEResponse ( "192.0.0.9" , "www.example.com." , "chain.example.com." , "example.com." ) )
a . Wait ( ctx )
wantRoutes = append ( wantRoutes , netip . MustParsePrefix ( "192.0.0.9/32" ) )
if got , want := rc . Routes ( ) , wantRoutes ; ! slices . Equal ( got , want ) {
t . Errorf ( "got %v; want %v" , got , want )
}
// a CNAME record chain should result in a route being added if the chain
// even if only found in the middle of the chain
a . ObserveDNSResponse ( dnsCNAMEResponse ( "192.0.0.10" , "outside.example.org." , "www.example.com." , "example.org." ) )
a . Wait ( ctx )
wantRoutes = append ( wantRoutes , netip . MustParsePrefix ( "192.0.0.10/32" ) )
if got , want := rc . Routes ( ) , wantRoutes ; ! slices . Equal ( got , want ) {
if got , want := rc . Routes ( ) , wantRoutes ; ! slices . Equal ( got , want ) {
t . Errorf ( "got %v; want %v" , got , want )
t . Errorf ( "got %v; want %v" , got , want )
}
}
@ -130,12 +152,14 @@ func TestObserveDNSResponse(t *testing.T) {
wantRoutes = append ( wantRoutes , netip . MustParsePrefix ( "2001:db8::1/128" ) )
wantRoutes = append ( wantRoutes , netip . MustParsePrefix ( "2001:db8::1/128" ) )
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "2001:db8::1" ) )
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "2001:db8::1" ) )
a . Wait ( ctx )
if got , want := rc . Routes ( ) , wantRoutes ; ! slices . Equal ( got , want ) {
if got , want := rc . Routes ( ) , wantRoutes ; ! slices . Equal ( got , want ) {
t . Errorf ( "got %v; want %v" , got , want )
t . Errorf ( "got %v; want %v" , got , want )
}
}
// don't re-advertise routes that have already been advertised
// don't re-advertise routes that have already been advertised
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "2001:db8::1" ) )
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "2001:db8::1" ) )
a . Wait ( ctx )
if ! slices . Equal ( rc . Routes ( ) , wantRoutes ) {
if ! slices . Equal ( rc . Routes ( ) , wantRoutes ) {
t . Errorf ( "rc.Routes(): got %v; want %v" , rc . Routes ( ) , wantRoutes )
t . Errorf ( "rc.Routes(): got %v; want %v" , rc . Routes ( ) , wantRoutes )
}
}
@ -145,6 +169,7 @@ func TestObserveDNSResponse(t *testing.T) {
a . updateRoutes ( [ ] netip . Prefix { pfx } )
a . updateRoutes ( [ ] netip . Prefix { pfx } )
wantRoutes = append ( wantRoutes , pfx )
wantRoutes = append ( wantRoutes , pfx )
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "192.0.2.1" ) )
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "192.0.2.1" ) )
a . Wait ( ctx )
if ! slices . Equal ( rc . Routes ( ) , wantRoutes ) {
if ! slices . Equal ( rc . Routes ( ) , wantRoutes ) {
t . Errorf ( "rc.Routes(): got %v; want %v" , rc . Routes ( ) , wantRoutes )
t . Errorf ( "rc.Routes(): got %v; want %v" , rc . Routes ( ) , wantRoutes )
}
}
@ -154,11 +179,13 @@ func TestObserveDNSResponse(t *testing.T) {
}
}
func TestWildcardDomains ( t * testing . T ) {
func TestWildcardDomains ( t * testing . T ) {
ctx := context . Background ( )
rc := & appctest . RouteCollector { }
rc := & appctest . RouteCollector { }
a := NewAppConnector ( t . Logf , rc )
a := NewAppConnector ( t . Logf , rc )
a . updateDomains ( [ ] string { "*.example.com" } )
a . updateDomains ( [ ] string { "*.example.com" } )
a . ObserveDNSResponse ( dnsResponse ( "foo.example.com." , "192.0.0.8" ) )
a . ObserveDNSResponse ( dnsResponse ( "foo.example.com." , "192.0.0.8" ) )
a . Wait ( ctx )
if got , want := rc . Routes ( ) , [ ] netip . Prefix { netip . MustParsePrefix ( "192.0.0.8/32" ) } ; ! slices . Equal ( got , want ) {
if got , want := rc . Routes ( ) , [ ] netip . Prefix { netip . MustParsePrefix ( "192.0.0.8/32" ) } ; ! slices . Equal ( got , want ) {
t . Errorf ( "routes: got %v; want %v" , got , want )
t . Errorf ( "routes: got %v; want %v" , got , want )
}
}
@ -218,6 +245,61 @@ func dnsResponse(domain, address string) []byte {
return must . Get ( b . Finish ( ) )
return must . Get ( b . Finish ( ) )
}
}
func dnsCNAMEResponse ( address string , domains ... string ) [ ] byte {
addr := netip . MustParseAddr ( address )
b := dnsmessage . NewBuilder ( nil , dnsmessage . Header { } )
b . EnableCompression ( )
b . StartAnswers ( )
if len ( domains ) >= 2 {
for i , domain := range domains [ : len ( domains ) - 1 ] {
b . CNAMEResource (
dnsmessage . ResourceHeader {
Name : dnsmessage . MustNewName ( domain ) ,
Type : dnsmessage . TypeCNAME ,
Class : dnsmessage . ClassINET ,
TTL : 0 ,
} ,
dnsmessage . CNAMEResource {
CNAME : dnsmessage . MustNewName ( domains [ i + 1 ] ) ,
} ,
)
}
}
domain := domains [ len ( domains ) - 1 ]
switch addr . BitLen ( ) {
case 32 :
b . AResource (
dnsmessage . ResourceHeader {
Name : dnsmessage . MustNewName ( domain ) ,
Type : dnsmessage . TypeA ,
Class : dnsmessage . ClassINET ,
TTL : 0 ,
} ,
dnsmessage . AResource {
A : addr . As4 ( ) ,
} ,
)
case 128 :
b . AAAAResource (
dnsmessage . ResourceHeader {
Name : dnsmessage . MustNewName ( domain ) ,
Type : dnsmessage . TypeAAAA ,
Class : dnsmessage . ClassINET ,
TTL : 0 ,
} ,
dnsmessage . AAAAResource {
AAAA : addr . As16 ( ) ,
} ,
)
default :
panic ( "invalid address length" )
}
return must . Get ( b . Finish ( ) )
}
func prefixEqual ( a , b netip . Prefix ) bool {
func prefixEqual ( a , b netip . Prefix ) bool {
return a == b
return a == b
}
}