2023-10-27 21:20:10 +00:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package appc
import (
2024-01-18 18:18:25 +00:00
"context"
2023-10-27 21:20:10 +00:00
"net/netip"
2023-11-15 19:50:13 +00:00
"reflect"
2023-10-27 21:20:10 +00:00
"slices"
"testing"
2024-05-22 16:56:03 +00:00
"time"
2023-10-27 21:20:10 +00:00
xmaps "golang.org/x/exp/maps"
"golang.org/x/net/dns/dnsmessage"
2024-01-23 00:57:31 +00:00
"tailscale.com/appc/appctest"
2024-05-22 16:56:03 +00:00
"tailscale.com/tstest"
2024-01-17 19:35:55 +00:00
"tailscale.com/util/mak"
2023-10-27 21:20:10 +00:00
"tailscale.com/util/must"
)
2024-04-11 17:12:13 +00:00
func fakeStoreRoutes ( * RouteInfo ) error { return nil }
2023-10-27 21:20:10 +00:00
2024-04-11 17:12:13 +00:00
func TestUpdateDomains ( t * testing . T ) {
for _ , shouldStore := range [ ] bool { false , true } {
ctx := context . Background ( )
var a * AppConnector
if shouldStore {
2024-04-15 17:16:02 +00:00
a = NewAppConnector ( t . Logf , & appctest . RouteCollector { } , & RouteInfo { } , fakeStoreRoutes )
2024-04-11 17:12:13 +00:00
} else {
2024-04-15 17:16:02 +00:00
a = NewAppConnector ( t . Logf , & appctest . RouteCollector { } , nil , nil )
2024-04-11 17:12:13 +00:00
}
a . UpdateDomains ( [ ] string { "example.com" } )
a . Wait ( ctx )
if got , want := a . Domains ( ) . AsSlice ( ) , [ ] string { "example.com" } ; ! slices . Equal ( got , want ) {
t . Errorf ( "got %v; want %v" , got , want )
}
addr := netip . MustParseAddr ( "192.0.0.8" )
a . domains [ "example.com" ] = append ( a . domains [ "example.com" ] , addr )
a . UpdateDomains ( [ ] string { "example.com" } )
a . Wait ( ctx )
if got , want := a . domains [ "example.com" ] , [ ] netip . Addr { addr } ; ! slices . Equal ( got , want ) {
t . Errorf ( "got %v; want %v" , got , want )
}
// domains are explicitly downcased on set.
a . UpdateDomains ( [ ] string { "UP.EXAMPLE.COM" } )
a . Wait ( ctx )
if got , want := xmaps . Keys ( a . domains ) , [ ] string { "up.example.com" } ; ! slices . Equal ( got , want ) {
t . Errorf ( "got %v; want %v" , got , want )
}
2023-10-27 21:20:10 +00:00
}
}
2024-01-17 19:35:55 +00:00
func TestUpdateRoutes ( t * testing . T ) {
2024-04-11 17:12:13 +00:00
for _ , shouldStore := range [ ] bool { false , true } {
ctx := context . Background ( )
rc := & appctest . RouteCollector { }
var a * AppConnector
if shouldStore {
a = NewAppConnector ( t . Logf , rc , & RouteInfo { } , fakeStoreRoutes )
} else {
a = NewAppConnector ( t . Logf , rc , nil , nil )
}
a . updateDomains ( [ ] string { "*.example.com" } )
// This route should be collapsed into the range
a . ObserveDNSResponse ( dnsResponse ( "a.example.com." , "192.0.2.1" ) )
a . Wait ( ctx )
if ! slices . Equal ( rc . Routes ( ) , [ ] netip . Prefix { netip . MustParsePrefix ( "192.0.2.1/32" ) } ) {
t . Fatalf ( "got %v, want %v" , rc . Routes ( ) , [ ] netip . Prefix { netip . MustParsePrefix ( "192.0.2.1/32" ) } )
}
// This route should not be collapsed or removed
a . ObserveDNSResponse ( dnsResponse ( "b.example.com." , "192.0.0.1" ) )
a . Wait ( ctx )
routes := [ ] netip . Prefix { netip . MustParsePrefix ( "192.0.2.0/24" ) , netip . MustParsePrefix ( "192.0.0.1/32" ) }
a . updateRoutes ( routes )
slices . SortFunc ( rc . Routes ( ) , prefixCompare )
rc . SetRoutes ( slices . Compact ( rc . Routes ( ) ) )
slices . SortFunc ( routes , prefixCompare )
// Ensure that the non-matching /32 is preserved, even though it's in the domains table.
if ! slices . EqualFunc ( routes , rc . Routes ( ) , prefixEqual ) {
t . Errorf ( "added routes: got %v, want %v" , rc . Routes ( ) , routes )
}
// Ensure that the contained /32 is removed, replaced by the /24.
wantRemoved := [ ] netip . Prefix { netip . MustParsePrefix ( "192.0.2.1/32" ) }
if ! slices . EqualFunc ( rc . RemovedRoutes ( ) , wantRemoved , prefixEqual ) {
t . Fatalf ( "unexpected removed routes: %v" , rc . RemovedRoutes ( ) )
}
2024-01-17 19:35:55 +00:00
}
}
func TestUpdateRoutesUnadvertisesContainedRoutes ( t * testing . T ) {
2024-04-11 17:12:13 +00:00
for _ , shouldStore := range [ ] bool { false , true } {
rc := & appctest . RouteCollector { }
var a * AppConnector
if shouldStore {
a = NewAppConnector ( t . Logf , rc , & RouteInfo { } , fakeStoreRoutes )
} else {
a = NewAppConnector ( t . Logf , rc , nil , nil )
}
mak . Set ( & a . domains , "example.com" , [ ] netip . Addr { netip . MustParseAddr ( "192.0.2.1" ) } )
rc . SetRoutes ( [ ] netip . Prefix { netip . MustParsePrefix ( "192.0.2.1/32" ) } )
routes := [ ] netip . Prefix { netip . MustParsePrefix ( "192.0.2.0/24" ) }
a . updateRoutes ( routes )
if ! slices . EqualFunc ( routes , rc . Routes ( ) , prefixEqual ) {
t . Fatalf ( "got %v, want %v" , rc . Routes ( ) , routes )
}
2024-01-17 19:35:55 +00:00
}
}
2023-11-15 19:50:13 +00:00
func TestDomainRoutes ( t * testing . T ) {
2024-04-11 17:12:13 +00:00
for _ , shouldStore := range [ ] bool { false , true } {
rc := & appctest . RouteCollector { }
var a * AppConnector
if shouldStore {
a = NewAppConnector ( t . Logf , rc , & RouteInfo { } , fakeStoreRoutes )
} else {
a = NewAppConnector ( t . Logf , rc , nil , nil )
}
a . updateDomains ( [ ] string { "example.com" } )
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "192.0.0.8" ) )
a . Wait ( context . Background ( ) )
want := map [ string ] [ ] netip . Addr {
"example.com" : { netip . MustParseAddr ( "192.0.0.8" ) } ,
}
if got := a . DomainRoutes ( ) ; ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "DomainRoutes: got %v, want %v" , got , want )
}
2023-11-15 19:50:13 +00:00
}
}
2023-10-27 21:20:10 +00:00
func TestObserveDNSResponse ( t * testing . T ) {
2024-04-11 17:12:13 +00:00
for _ , shouldStore := range [ ] bool { false , true } {
ctx := context . Background ( )
rc := & appctest . RouteCollector { }
var a * AppConnector
if shouldStore {
a = NewAppConnector ( t . Logf , rc , & RouteInfo { } , fakeStoreRoutes )
} else {
a = NewAppConnector ( t . Logf , rc , nil , nil )
}
// a has no domains configured, so it should not advertise any routes
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "192.0.0.8" ) )
if got , want := rc . Routes ( ) , ( [ ] netip . Prefix ) ( nil ) ; ! slices . Equal ( got , want ) {
t . Errorf ( "got %v; want %v" , got , want )
}
wantRoutes := [ ] netip . Prefix { netip . MustParsePrefix ( "192.0.0.8/32" ) }
a . updateDomains ( [ ] string { "example.com" } )
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 ) {
t . Errorf ( "got %v; want %v" , got , want )
}
wantRoutes = append ( wantRoutes , netip . MustParsePrefix ( "2001:db8::1/128" ) )
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "2001:db8::1" ) )
a . Wait ( ctx )
if got , want := rc . Routes ( ) , wantRoutes ; ! slices . Equal ( got , want ) {
t . Errorf ( "got %v; want %v" , got , want )
}
// don't re-advertise routes that have already been advertised
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "2001:db8::1" ) )
a . Wait ( ctx )
if ! slices . Equal ( rc . Routes ( ) , wantRoutes ) {
t . Errorf ( "rc.Routes(): got %v; want %v" , rc . Routes ( ) , wantRoutes )
}
// don't advertise addresses that are already in a control provided route
pfx := netip . MustParsePrefix ( "192.0.2.0/24" )
a . updateRoutes ( [ ] netip . Prefix { pfx } )
wantRoutes = append ( wantRoutes , pfx )
a . ObserveDNSResponse ( dnsResponse ( "example.com." , "192.0.2.1" ) )
a . Wait ( ctx )
if ! slices . Equal ( rc . Routes ( ) , wantRoutes ) {
t . Errorf ( "rc.Routes(): got %v; want %v" , rc . Routes ( ) , wantRoutes )
}
if ! slices . Contains ( a . domains [ "example.com" ] , netip . MustParseAddr ( "192.0.2.1" ) ) {
t . Errorf ( "missing %v from %v" , "192.0.2.1" , a . domains [ "exmaple.com" ] )
}
2023-10-27 21:20:10 +00:00
}
}
2023-11-08 18:57:16 +00:00
func TestWildcardDomains ( t * testing . T ) {
2024-04-11 17:12:13 +00:00
for _ , shouldStore := range [ ] bool { false , true } {
ctx := context . Background ( )
rc := & appctest . RouteCollector { }
var a * AppConnector
if shouldStore {
a = NewAppConnector ( t . Logf , rc , & RouteInfo { } , fakeStoreRoutes )
} else {
a = NewAppConnector ( t . Logf , rc , nil , nil )
}
a . updateDomains ( [ ] string { "*.example.com" } )
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 ) {
t . Errorf ( "routes: got %v; want %v" , got , want )
}
if got , want := a . wildcards , [ ] string { "example.com" } ; ! slices . Equal ( got , want ) {
t . Errorf ( "wildcards: got %v; want %v" , got , want )
}
a . updateDomains ( [ ] string { "*.example.com" , "example.com" } )
if _ , ok := a . domains [ "foo.example.com" ] ; ! ok {
t . Errorf ( "expected foo.example.com to be preserved in domains due to wildcard" )
}
if got , want := a . wildcards , [ ] string { "example.com" } ; ! slices . Equal ( got , want ) {
t . Errorf ( "wildcards: got %v; want %v" , got , want )
}
// There was an early regression where the wildcard domain was added repeatedly, this guards against that.
a . updateDomains ( [ ] string { "*.example.com" , "example.com" } )
if len ( a . wildcards ) != 1 {
t . Errorf ( "expected only one wildcard domain, got %v" , a . wildcards )
}
2023-11-10 00:05:50 +00:00
}
2023-11-08 18:57:16 +00:00
}
2023-10-27 21:20:10 +00:00
// dnsResponse is a test helper that creates a DNS response buffer for the given domain and address
func dnsResponse ( domain , address string ) [ ] byte {
addr := netip . MustParseAddr ( address )
b := dnsmessage . NewBuilder ( nil , dnsmessage . Header { } )
b . EnableCompression ( )
b . StartAnswers ( )
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 ( ) )
}
2024-01-31 22:33:14 +00:00
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 ( ) )
}
2024-01-17 19:35:55 +00:00
func prefixEqual ( a , b netip . Prefix ) bool {
2024-01-23 00:57:31 +00:00
return a == b
2024-01-17 19:35:55 +00:00
}
2024-01-23 00:18:57 +00:00
func prefixCompare ( a , b netip . Prefix ) int {
if a . Addr ( ) . Compare ( b . Addr ( ) ) == 0 {
return a . Bits ( ) - b . Bits ( )
}
return a . Addr ( ) . Compare ( b . Addr ( ) )
}
2024-04-15 17:16:02 +00:00
func prefixes ( in ... string ) [ ] netip . Prefix {
toRet := make ( [ ] netip . Prefix , len ( in ) )
for i , s := range in {
toRet [ i ] = netip . MustParsePrefix ( s )
}
return toRet
}
func TestUpdateRouteRouteRemoval ( t * testing . T ) {
for _ , shouldStore := range [ ] bool { false , true } {
ctx := context . Background ( )
rc := & appctest . RouteCollector { }
assertRoutes := func ( prefix string , routes , removedRoutes [ ] netip . Prefix ) {
if ! slices . Equal ( routes , rc . Routes ( ) ) {
t . Fatalf ( "%s: (shouldStore=%t) routes want %v, got %v" , prefix , shouldStore , routes , rc . Routes ( ) )
}
if ! slices . Equal ( removedRoutes , rc . RemovedRoutes ( ) ) {
t . Fatalf ( "%s: (shouldStore=%t) removedRoutes want %v, got %v" , prefix , shouldStore , removedRoutes , rc . RemovedRoutes ( ) )
}
}
var a * AppConnector
if shouldStore {
a = NewAppConnector ( t . Logf , rc , & RouteInfo { } , fakeStoreRoutes )
} else {
a = NewAppConnector ( t . Logf , rc , nil , nil )
}
// nothing has yet been advertised
assertRoutes ( "appc init" , [ ] netip . Prefix { } , [ ] netip . Prefix { } )
a . UpdateDomainsAndRoutes ( [ ] string { } , prefixes ( "1.2.3.1/32" , "1.2.3.2/32" ) )
a . Wait ( ctx )
// the routes passed to UpdateDomainsAndRoutes have been advertised
assertRoutes ( "simple update" , prefixes ( "1.2.3.1/32" , "1.2.3.2/32" ) , [ ] netip . Prefix { } )
// one route the same, one different
a . UpdateDomainsAndRoutes ( [ ] string { } , prefixes ( "1.2.3.1/32" , "1.2.3.3/32" ) )
a . Wait ( ctx )
// old behavior: routes are not removed, resulting routes are both old and new
// (we have dupe 1.2.3.1 routes because the test RouteAdvertiser doesn't have the deduplication
// the real one does)
wantRoutes := prefixes ( "1.2.3.1/32" , "1.2.3.2/32" , "1.2.3.1/32" , "1.2.3.3/32" )
wantRemovedRoutes := [ ] netip . Prefix { }
if shouldStore {
// new behavior: routes are removed, resulting routes are new only
wantRoutes = prefixes ( "1.2.3.1/32" , "1.2.3.1/32" , "1.2.3.3/32" )
wantRemovedRoutes = prefixes ( "1.2.3.2/32" )
}
assertRoutes ( "removal" , wantRoutes , wantRemovedRoutes )
}
}
func TestUpdateDomainRouteRemoval ( t * testing . T ) {
for _ , shouldStore := range [ ] bool { false , true } {
ctx := context . Background ( )
rc := & appctest . RouteCollector { }
assertRoutes := func ( prefix string , routes , removedRoutes [ ] netip . Prefix ) {
if ! slices . Equal ( routes , rc . Routes ( ) ) {
t . Fatalf ( "%s: (shouldStore=%t) routes want %v, got %v" , prefix , shouldStore , routes , rc . Routes ( ) )
}
if ! slices . Equal ( removedRoutes , rc . RemovedRoutes ( ) ) {
t . Fatalf ( "%s: (shouldStore=%t) removedRoutes want %v, got %v" , prefix , shouldStore , removedRoutes , rc . RemovedRoutes ( ) )
}
}
var a * AppConnector
if shouldStore {
a = NewAppConnector ( t . Logf , rc , & RouteInfo { } , fakeStoreRoutes )
} else {
a = NewAppConnector ( t . Logf , rc , nil , nil )
}
assertRoutes ( "appc init" , [ ] netip . Prefix { } , [ ] netip . Prefix { } )
a . UpdateDomainsAndRoutes ( [ ] string { "a.example.com" , "b.example.com" } , [ ] netip . Prefix { } )
a . Wait ( ctx )
// adding domains doesn't immediately cause any routes to be advertised
assertRoutes ( "update domains" , [ ] netip . Prefix { } , [ ] netip . Prefix { } )
a . ObserveDNSResponse ( dnsResponse ( "a.example.com." , "1.2.3.1" ) )
a . ObserveDNSResponse ( dnsResponse ( "a.example.com." , "1.2.3.2" ) )
a . ObserveDNSResponse ( dnsResponse ( "b.example.com." , "1.2.3.3" ) )
a . ObserveDNSResponse ( dnsResponse ( "b.example.com." , "1.2.3.4" ) )
a . Wait ( ctx )
// observing dns responses causes routes to be advertised
assertRoutes ( "observed dns" , prefixes ( "1.2.3.1/32" , "1.2.3.2/32" , "1.2.3.3/32" , "1.2.3.4/32" ) , [ ] netip . Prefix { } )
a . UpdateDomainsAndRoutes ( [ ] string { "a.example.com" } , [ ] netip . Prefix { } )
a . Wait ( ctx )
// old behavior, routes are not removed
wantRoutes := prefixes ( "1.2.3.1/32" , "1.2.3.2/32" , "1.2.3.3/32" , "1.2.3.4/32" )
wantRemovedRoutes := [ ] netip . Prefix { }
if shouldStore {
// new behavior, routes are removed for b.example.com
wantRoutes = prefixes ( "1.2.3.1/32" , "1.2.3.2/32" )
wantRemovedRoutes = prefixes ( "1.2.3.3/32" , "1.2.3.4/32" )
}
assertRoutes ( "removal" , wantRoutes , wantRemovedRoutes )
}
}
func TestUpdateWildcardRouteRemoval ( t * testing . T ) {
for _ , shouldStore := range [ ] bool { false , true } {
ctx := context . Background ( )
rc := & appctest . RouteCollector { }
assertRoutes := func ( prefix string , routes , removedRoutes [ ] netip . Prefix ) {
if ! slices . Equal ( routes , rc . Routes ( ) ) {
t . Fatalf ( "%s: (shouldStore=%t) routes want %v, got %v" , prefix , shouldStore , routes , rc . Routes ( ) )
}
if ! slices . Equal ( removedRoutes , rc . RemovedRoutes ( ) ) {
t . Fatalf ( "%s: (shouldStore=%t) removedRoutes want %v, got %v" , prefix , shouldStore , removedRoutes , rc . RemovedRoutes ( ) )
}
}
var a * AppConnector
if shouldStore {
a = NewAppConnector ( t . Logf , rc , & RouteInfo { } , fakeStoreRoutes )
} else {
a = NewAppConnector ( t . Logf , rc , nil , nil )
}
assertRoutes ( "appc init" , [ ] netip . Prefix { } , [ ] netip . Prefix { } )
a . UpdateDomainsAndRoutes ( [ ] string { "a.example.com" , "*.b.example.com" } , [ ] netip . Prefix { } )
a . Wait ( ctx )
// adding domains doesn't immediately cause any routes to be advertised
assertRoutes ( "update domains" , [ ] netip . Prefix { } , [ ] netip . Prefix { } )
a . ObserveDNSResponse ( dnsResponse ( "a.example.com." , "1.2.3.1" ) )
a . ObserveDNSResponse ( dnsResponse ( "a.example.com." , "1.2.3.2" ) )
a . ObserveDNSResponse ( dnsResponse ( "1.b.example.com." , "1.2.3.3" ) )
a . ObserveDNSResponse ( dnsResponse ( "2.b.example.com." , "1.2.3.4" ) )
a . Wait ( ctx )
// observing dns responses causes routes to be advertised
assertRoutes ( "observed dns" , prefixes ( "1.2.3.1/32" , "1.2.3.2/32" , "1.2.3.3/32" , "1.2.3.4/32" ) , [ ] netip . Prefix { } )
a . UpdateDomainsAndRoutes ( [ ] string { "a.example.com" } , [ ] netip . Prefix { } )
a . Wait ( ctx )
// old behavior, routes are not removed
wantRoutes := prefixes ( "1.2.3.1/32" , "1.2.3.2/32" , "1.2.3.3/32" , "1.2.3.4/32" )
wantRemovedRoutes := [ ] netip . Prefix { }
if shouldStore {
// new behavior, routes are removed for *.b.example.com
wantRoutes = prefixes ( "1.2.3.1/32" , "1.2.3.2/32" )
wantRemovedRoutes = prefixes ( "1.2.3.3/32" , "1.2.3.4/32" )
}
assertRoutes ( "removal" , wantRoutes , wantRemovedRoutes )
}
}
func TestRoutesWithout ( t * testing . T ) {
assert := func ( msg string , got , want [ ] netip . Prefix ) {
if ! slices . Equal ( want , got ) {
t . Errorf ( "%s: want %v, got %v" , msg , want , got )
}
}
assert ( "empty routes" , routesWithout ( [ ] netip . Prefix { } , [ ] netip . Prefix { } ) , [ ] netip . Prefix { } )
assert ( "a empty" , routesWithout ( [ ] netip . Prefix { } , prefixes ( "1.1.1.1/32" , "1.1.1.2/32" ) ) , [ ] netip . Prefix { } )
assert ( "b empty" , routesWithout ( prefixes ( "1.1.1.1/32" , "1.1.1.2/32" ) , [ ] netip . Prefix { } ) , prefixes ( "1.1.1.1/32" , "1.1.1.2/32" ) )
assert ( "no overlap" , routesWithout ( prefixes ( "1.1.1.1/32" , "1.1.1.2/32" ) , prefixes ( "1.1.1.3/32" , "1.1.1.4/32" ) ) , prefixes ( "1.1.1.1/32" , "1.1.1.2/32" ) )
assert ( "a has fewer" , routesWithout ( prefixes ( "1.1.1.1/32" , "1.1.1.2/32" ) , prefixes ( "1.1.1.1/32" , "1.1.1.2/32" , "1.1.1.3/32" , "1.1.1.4/32" ) ) , [ ] netip . Prefix { } )
assert ( "a has more" , routesWithout ( prefixes ( "1.1.1.1/32" , "1.1.1.2/32" , "1.1.1.3/32" , "1.1.1.4/32" ) , prefixes ( "1.1.1.1/32" , "1.1.1.3/32" ) ) , prefixes ( "1.1.1.2/32" , "1.1.1.4/32" ) )
}
2024-05-22 16:56:03 +00:00
func TestRateLogger ( t * testing . T ) {
clock := tstest . Clock { }
wasCalled := false
rl := newRateLogger ( func ( ) time . Time { return clock . Now ( ) } , 1 * time . Second , func ( count int64 , _ time . Time ) {
if count != 3 {
t . Fatalf ( "count for prev period: got %d, want 3" , count )
}
wasCalled = true
} )
for i := 0 ; i < 3 ; i ++ {
clock . Advance ( 1 * time . Millisecond )
rl . update ( )
if wasCalled {
t . Fatalf ( "wasCalled: got true, want false" )
}
}
clock . Advance ( 1 * time . Second )
rl . update ( )
if ! wasCalled {
t . Fatalf ( "wasCalled: got false, want true" )
}
wasCalled = false
rl = newRateLogger ( func ( ) time . Time { return clock . Now ( ) } , 1 * time . Hour , func ( count int64 , _ time . Time ) {
if count != 3 {
t . Fatalf ( "count for prev period: got %d, want 3" , count )
}
wasCalled = true
} )
for i := 0 ; i < 3 ; i ++ {
clock . Advance ( 1 * time . Minute )
rl . update ( )
if wasCalled {
t . Fatalf ( "wasCalled: got true, want false" )
}
}
clock . Advance ( 1 * time . Hour )
rl . update ( )
if ! wasCalled {
t . Fatalf ( "wasCalled: got false, want true" )
}
}