appc, feature/conn25: handle exact and wildcard domains correctly (#19202)
Installed SplitDNS routes are always treated as wildcard domains, so the domains that we pass to the local resolver should be normalized and have any leading *. wildcard prefix removed. When looking at DNS responses to see if the domain matches, we need to consider both exact matches and wildcard matches. We now keep separate maps of exact-match domains and wildcard domains, and when we match we check to see if there's a match in the exact-match map, otherwise we check against the wild card match map until we find a match, removing a label after each check. Rather than looking for matching self-hosted domains (domains serviced by the connector being run on the self-node), the apps that are being serviced by the connector on the self-node are tracked instead. When checking to see if a DNS response should be rewritten, it is ignored if any of the matching apps for the domain are in the self-hosted apps set. Fixes tailscale/corp#39272 Signed-off-by: George Jones <george@tailscale.com>
This commit is contained in:
+19
-10
@@ -6,6 +6,7 @@ package appc
|
|||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"tailscale.com/ipn/ipnext"
|
"tailscale.com/ipn/ipnext"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@@ -64,20 +65,28 @@ func PickSplitDNSPeers(hasCap func(c tailcfg.NodeCapability) bool, self tailcfg.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
tagToDomain := make(map[string][]string)
|
|
||||||
|
// We strip the leading *. from any domains because the OS treats all domains
|
||||||
|
// that we pass to it as wildcard domains, and the OS would treat the * character
|
||||||
|
// as a literal domain component instead of treating it as a wildcard.
|
||||||
|
// We also use a Set to deduplicate the domains we pass to the OS in case removing
|
||||||
|
// the *. prefix resulted in duplicate entries.
|
||||||
|
tagToDomain := make(map[string]set.Set[string])
|
||||||
selfTags := set.SetOf(self.Tags().AsSlice())
|
selfTags := set.SetOf(self.Tags().AsSlice())
|
||||||
selfRoutedDomains := set.Set[string]{}
|
selfRoutedDomains := set.Set[string]{}
|
||||||
for _, app := range apps {
|
for _, app := range apps {
|
||||||
|
domains := make(set.Set[string])
|
||||||
|
for _, domain := range app.Domains {
|
||||||
|
domains.Add(strings.ToLower(strings.TrimPrefix(domain, "*.")))
|
||||||
|
}
|
||||||
for _, tag := range app.Connectors {
|
for _, tag := range app.Connectors {
|
||||||
domains := tagToDomain[tag]
|
if tagToDomain[tag] == nil {
|
||||||
domains = slices.Grow(domains, len(app.Domains))
|
tagToDomain[tag] = set.Set[string]{}
|
||||||
for _, d := range app.Domains {
|
}
|
||||||
if isSelfEligibleConnector && selfTags.Contains(tag) {
|
tagToDomain[tag].AddSet(domains)
|
||||||
selfRoutedDomains.Add(d)
|
if isSelfEligibleConnector && selfTags.Contains(tag) {
|
||||||
}
|
selfRoutedDomains.AddSet(domains)
|
||||||
domains = append(domains, d)
|
|
||||||
}
|
}
|
||||||
tagToDomain[tag] = domains
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// NodeIDs are Comparable, and we have a map of NodeID to NodeView anyway, so
|
// NodeIDs are Comparable, and we have a map of NodeID to NodeView anyway, so
|
||||||
@@ -89,7 +98,7 @@ func PickSplitDNSPeers(hasCap func(c tailcfg.NodeCapability) bool, self tailcfg.
|
|||||||
}
|
}
|
||||||
for _, t := range peer.Tags().All() {
|
for _, t := range peer.Tags().All() {
|
||||||
domains := tagToDomain[t]
|
domains := tagToDomain[t]
|
||||||
for _, domain := range domains {
|
for domain := range domains {
|
||||||
if selfRoutedDomains.Contains(domain) {
|
if selfRoutedDomains.Contains(domain) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ func TestPickSplitDNSPeers(t *testing.T) {
|
|||||||
appTwoBytes := getBytesForAttr("app2", []string{"a.example.com"}, []string{"tag:two"})
|
appTwoBytes := getBytesForAttr("app2", []string{"a.example.com"}, []string{"tag:two"})
|
||||||
appThreeBytes := getBytesForAttr("app3", []string{"woo.b.example.com", "hoo.b.example.com"}, []string{"tag:three1", "tag:three2"})
|
appThreeBytes := getBytesForAttr("app3", []string{"woo.b.example.com", "hoo.b.example.com"}, []string{"tag:three1", "tag:three2"})
|
||||||
appFourBytes := getBytesForAttr("app4", []string{"woo.b.example.com", "c.example.com"}, []string{"tag:four1", "tag:four2"})
|
appFourBytes := getBytesForAttr("app4", []string{"woo.b.example.com", "c.example.com"}, []string{"tag:four1", "tag:four2"})
|
||||||
|
appFiveBytes := getBytesForAttr("app5", []string{"*.example.com", "example.com"}, []string{"tag:one"})
|
||||||
|
appSixBytes := getBytesForAttr("app6", []string{"*.Example.com", "EXAMPLE.com", "EXAMPLE.COM"}, []string{"tag:one"})
|
||||||
|
|
||||||
makeNodeView := func(id tailcfg.NodeID, name string, tags []string) tailcfg.NodeView {
|
makeNodeView := func(id tailcfg.NodeID, name string, tags []string) tailcfg.NodeView {
|
||||||
return (&tailcfg.Node{
|
return (&tailcfg.Node{
|
||||||
@@ -192,6 +194,49 @@ func TestPickSplitDNSPeers(t *testing.T) {
|
|||||||
"c.example.com": {nvp2, nvp4},
|
"c.example.com": {nvp2, nvp4},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "wildcards-are-stripped-and-deduped",
|
||||||
|
config: []tailcfg.RawMessage{
|
||||||
|
tailcfg.RawMessage(appOneBytes),
|
||||||
|
tailcfg.RawMessage(appFiveBytes),
|
||||||
|
},
|
||||||
|
peers: []tailcfg.NodeView{
|
||||||
|
nvp1,
|
||||||
|
},
|
||||||
|
want: map[string][]tailcfg.NodeView{
|
||||||
|
// All the domains should be normalized to example.com
|
||||||
|
"example.com": {nvp1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "domains-are-normalized-and-deduped",
|
||||||
|
config: []tailcfg.RawMessage{
|
||||||
|
tailcfg.RawMessage(appSixBytes),
|
||||||
|
},
|
||||||
|
peers: []tailcfg.NodeView{
|
||||||
|
nvp1,
|
||||||
|
},
|
||||||
|
want: map[string][]tailcfg.NodeView{
|
||||||
|
// All the domains should be normalized to example.com
|
||||||
|
"example.com": {nvp1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sub-domains-and-top-domains-do-not-collide",
|
||||||
|
config: []tailcfg.RawMessage{
|
||||||
|
tailcfg.RawMessage(appTwoBytes),
|
||||||
|
tailcfg.RawMessage(appFiveBytes),
|
||||||
|
},
|
||||||
|
peers: []tailcfg.NodeView{
|
||||||
|
nvp1,
|
||||||
|
nvp3,
|
||||||
|
},
|
||||||
|
want: map[string][]tailcfg.NodeView{
|
||||||
|
// The sub.example.com should remain distinct from example.com
|
||||||
|
"example.com": {nvp1},
|
||||||
|
"a.example.com": {nvp3},
|
||||||
|
},
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
selfNode := &tailcfg.Node{}
|
selfNode := &tailcfg.Node{}
|
||||||
|
|||||||
+65
-39
@@ -569,12 +569,13 @@ func emptyIPSets() ipSets {
|
|||||||
// which includes the policy.
|
// which includes the policy.
|
||||||
// config is not safe for concurrent use.
|
// config is not safe for concurrent use.
|
||||||
type config struct {
|
type config struct {
|
||||||
isConfigured bool
|
isConfigured bool
|
||||||
apps []appctype.Conn25Attr
|
apps []appctype.Conn25Attr
|
||||||
appsByName map[string]appctype.Conn25Attr
|
appsByName map[string]appctype.Conn25Attr
|
||||||
appNamesByDomain map[dnsname.FQDN][]string
|
appNamesByDomain map[dnsname.FQDN][]string
|
||||||
selfDomains set.Set[dnsname.FQDN]
|
appNamesByWCDomain map[dnsname.FQDN][]string
|
||||||
ipSets ipSets
|
selfAppNames set.Set[string]
|
||||||
|
ipSets ipSets
|
||||||
}
|
}
|
||||||
|
|
||||||
func configFromNodeView(n tailcfg.NodeView) (*config, error) {
|
func configFromNodeView(n tailcfg.NodeView) (*config, error) {
|
||||||
@@ -587,26 +588,36 @@ func configFromNodeView(n tailcfg.NodeView) (*config, error) {
|
|||||||
}
|
}
|
||||||
selfTags := set.SetOf(n.Tags().AsSlice())
|
selfTags := set.SetOf(n.Tags().AsSlice())
|
||||||
cfg := &config{
|
cfg := &config{
|
||||||
isConfigured: true,
|
isConfigured: true,
|
||||||
apps: apps,
|
apps: apps,
|
||||||
appsByName: map[string]appctype.Conn25Attr{},
|
appsByName: map[string]appctype.Conn25Attr{},
|
||||||
appNamesByDomain: map[dnsname.FQDN][]string{},
|
appNamesByDomain: map[dnsname.FQDN][]string{},
|
||||||
selfDomains: set.Set[dnsname.FQDN]{},
|
appNamesByWCDomain: map[dnsname.FQDN][]string{},
|
||||||
ipSets: emptyIPSets(),
|
selfAppNames: set.Set[string]{},
|
||||||
|
ipSets: emptyIPSets(),
|
||||||
}
|
}
|
||||||
for _, app := range apps {
|
for _, app := range apps {
|
||||||
selfMatchesTags := slices.ContainsFunc(app.Connectors, selfTags.Contains)
|
normalizedDomains := set.Set[dnsname.FQDN]{}
|
||||||
|
normalizedWCDomains := set.Set[dnsname.FQDN]{}
|
||||||
for _, d := range app.Domains {
|
for _, d := range app.Domains {
|
||||||
fqdn, err := normalizeDNSName(d)
|
domain, isWild := strings.CutPrefix(d, "*.")
|
||||||
|
fqdn, err := normalizeDNSName(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &config{}, err
|
return &config{}, err
|
||||||
}
|
}
|
||||||
mak.Set(&cfg.appNamesByDomain, fqdn, append(cfg.appNamesByDomain[fqdn], app.Name))
|
if isWild && !normalizedWCDomains.Contains(fqdn) {
|
||||||
if selfMatchesTags {
|
normalizedWCDomains.Add(fqdn)
|
||||||
cfg.selfDomains.Add(fqdn)
|
mak.Set(&cfg.appNamesByWCDomain, fqdn, append(cfg.appNamesByWCDomain[fqdn], app.Name))
|
||||||
|
} else if !isWild && !normalizedDomains.Contains(fqdn) {
|
||||||
|
normalizedDomains.Add(fqdn)
|
||||||
|
mak.Set(&cfg.appNamesByDomain, fqdn, append(cfg.appNamesByDomain[fqdn], app.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mak.Set(&cfg.appsByName, app.Name, app)
|
mak.Set(&cfg.appsByName, app.Name, app)
|
||||||
|
if slices.ContainsFunc(app.Connectors, selfTags.Contains) {
|
||||||
|
cfg.selfAppNames.Add(app.Name)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(fran) 2026-03-18 we don't yet have a proper way to communicate the
|
// TODO(fran) 2026-03-18 we don't yet have a proper way to communicate the
|
||||||
@@ -702,24 +713,42 @@ func (c *client) reconfig() {
|
|||||||
c.v6TransitIPPool = newIPPool(ipSets.v6Transit)
|
c.v6TransitIPPool = newIPPool(ipSets.v6Transit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isConnectorDomain returns true if the domain is expected
|
// getAppsForConnectorDomain returns the slice of app names which match the
|
||||||
// to be routed through a peer connector, but returns false
|
// provided domain. Apps which match the domain exactly are preferred,
|
||||||
// if the self node is a connector responsible for routing the
|
// otherwise the list of apps comes from the wildcard domain which matches
|
||||||
// domain, and false in all other cases.
|
// the longest suffix of the specified domain. A nil or empty slice is returned
|
||||||
func (cfg *config) isConnectorDomain(domain dnsname.FQDN, prefsAdvertiseConnector bool) bool {
|
// if no match is found or if the list of matching apps would contain an app
|
||||||
if prefsAdvertiseConnector && cfg.selfDomains.Contains(domain) {
|
// which is being handled by the self-node's connector.
|
||||||
return false
|
func (cfg *config) getAppsForConnectorDomain(domain dnsname.FQDN, prefsAdvertiseConnector bool) []string {
|
||||||
|
// Lookup exact matches first
|
||||||
|
appNames := cfg.appNamesByDomain[domain]
|
||||||
|
if len(appNames) == 0 {
|
||||||
|
// No exact match, check wildcard domains
|
||||||
|
// We have made the decision that wildcards will match the base domain.
|
||||||
|
// So example.com will be a match for *.example.com, because we think that
|
||||||
|
// this is most likely what users will expect.
|
||||||
|
for d := domain; d != ""; d = d.Parent() {
|
||||||
|
if appNames = cfg.appNamesByWCDomain[d]; len(appNames) > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appNames, ok := cfg.appNamesByDomain[domain]
|
// If we have a candidate match, make sure that no candidate app is pointing
|
||||||
return ok && len(appNames) > 0
|
// at a connector on the self-node.
|
||||||
|
if len(appNames) == 0 || (prefsAdvertiseConnector && slices.ContainsFunc(appNames, cfg.selfAppNames.Contains)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return appNames
|
||||||
}
|
}
|
||||||
|
|
||||||
// reserveAddresses tries to make an assignment of addrs from the address pools
|
// reserveAddresses tries to make an assignment of addrs from the address pools
|
||||||
// for this domain+dst address, so that this client can use conn25 connectors.
|
// for this domain+dst address, so that this client can use conn25 connectors.
|
||||||
|
// The name of the matching app is also provided, no validation is done to check whether or not
|
||||||
|
// the app name refers to a configured app.
|
||||||
// It checks that this domain should be routed and that this client is not itself a connector for the domain
|
// It checks that this domain should be routed and that this client is not itself a connector for the domain
|
||||||
// and generally if it is valid to make the assignment.
|
// and generally if it is valid to make the assignment.
|
||||||
func (c *client) reserveAddresses(app string, domain dnsname.FQDN, dst netip.Addr) (addrs, error) {
|
func (c *client) reserveAddresses(appName string, domain dnsname.FQDN, dst netip.Addr) (addrs, error) {
|
||||||
if !dst.IsValid() {
|
if !dst.IsValid() {
|
||||||
return addrs{}, errors.New("dst is not valid")
|
return addrs{}, errors.New("dst is not valid")
|
||||||
}
|
}
|
||||||
@@ -756,7 +785,7 @@ func (c *client) reserveAddresses(app string, domain dnsname.FQDN, dst netip.Add
|
|||||||
dst: dst,
|
dst: dst,
|
||||||
magic: mip,
|
magic: mip,
|
||||||
transit: tip,
|
transit: tip,
|
||||||
app: app,
|
app: appName,
|
||||||
domain: domain,
|
domain: domain,
|
||||||
}
|
}
|
||||||
if err := c.assignments.insert(as); err != nil {
|
if err := c.assignments.insert(as); err != nil {
|
||||||
@@ -962,16 +991,13 @@ func (c *Conn25) mapDNSResponse(buf []byte) []byte {
|
|||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.isConnectorDomain(queriedDomain, c.prefsAdvertiseConnector.Load()) {
|
appNames := cfg.getAppsForConnectorDomain(queriedDomain, c.prefsAdvertiseConnector.Load())
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
appNames, _ := cfg.appNamesByDomain[queriedDomain]
|
|
||||||
if len(appNames) == 0 {
|
if len(appNames) == 0 {
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
// only reserve for first app
|
|
||||||
app := appNames[0]
|
// There is guaranteed to be at least one matching app, so just take the first one for now
|
||||||
|
appName := appNames[0]
|
||||||
|
|
||||||
// Now we know this is a dns response we think we should rewrite, we're going to provide our response which
|
// Now we know this is a dns response we think we should rewrite, we're going to provide our response which
|
||||||
// currently means we will:
|
// currently means we will:
|
||||||
@@ -981,7 +1007,7 @@ func (c *Conn25) mapDNSResponse(buf []byte) []byte {
|
|||||||
var answers []dnsResponseRewrite
|
var answers []dnsResponseRewrite
|
||||||
if question.Type != dnsmessage.TypeA && question.Type != dnsmessage.TypeAAAA {
|
if question.Type != dnsmessage.TypeA && question.Type != dnsmessage.TypeAAAA {
|
||||||
c.logf("mapping dns response for connector domain, unsupported type: %v", question.Type)
|
c.logf("mapping dns response for connector domain, unsupported type: %v", question.Type)
|
||||||
newBuf, err := c.client.rewriteDNSResponse(app, hdr, questions, answers)
|
newBuf, err := c.client.rewriteDNSResponse(appName, hdr, questions, answers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logf("error writing empty response for unsupported type: %v", err)
|
c.logf("error writing empty response for unsupported type: %v", err)
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
@@ -1066,7 +1092,7 @@ func (c *Conn25) mapDNSResponse(buf []byte) []byte {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newBuf, err := c.client.rewriteDNSResponse(app, hdr, questions, answers)
|
newBuf, err := c.client.rewriteDNSResponse(appName, hdr, questions, answers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logf("error rewriting dns response: %v", err)
|
c.logf("error rewriting dns response: %v", err)
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
@@ -1074,7 +1100,7 @@ func (c *Conn25) mapDNSResponse(buf []byte) []byte {
|
|||||||
return newBuf
|
return newBuf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) rewriteDNSResponse(app string, hdr dnsmessage.Header, questions []dnsmessage.Question, answers []dnsResponseRewrite) ([]byte, error) {
|
func (c *client) rewriteDNSResponse(appName string, hdr dnsmessage.Header, questions []dnsmessage.Question, answers []dnsResponseRewrite) ([]byte, error) {
|
||||||
b := dnsmessage.NewBuilder(nil, hdr)
|
b := dnsmessage.NewBuilder(nil, hdr)
|
||||||
b.EnableCompression()
|
b.EnableCompression()
|
||||||
if err := b.StartQuestions(); err != nil {
|
if err := b.StartQuestions(); err != nil {
|
||||||
@@ -1091,7 +1117,7 @@ func (c *client) rewriteDNSResponse(app string, hdr dnsmessage.Header, questions
|
|||||||
|
|
||||||
// make an answer for each rewrite
|
// make an answer for each rewrite
|
||||||
for _, rw := range answers {
|
for _, rw := range answers {
|
||||||
as, err := c.reserveAddresses(app, rw.domain, rw.dst)
|
as, err := c.reserveAddresses(appName, rw.domain, rw.dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+265
-35
@@ -393,13 +393,11 @@ func TestHandleConnectorTransitIPRequest(t *testing.T) {
|
|||||||
|
|
||||||
func TestReserveIPs(t *testing.T) {
|
func TestReserveIPs(t *testing.T) {
|
||||||
c := newConn25(logger.Discard)
|
c := newConn25(logger.Discard)
|
||||||
app := "a"
|
const appName = "a"
|
||||||
domainStr := "example.com."
|
domainStr := "example.com."
|
||||||
mbd := map[dnsname.FQDN][]string{}
|
|
||||||
mbd["example.com."] = []string{app}
|
|
||||||
cfg := &config{
|
cfg := &config{
|
||||||
isConfigured: true,
|
isConfigured: true,
|
||||||
appNamesByDomain: mbd,
|
appsByName: map[string]appctype.Conn25Attr{appName: {}},
|
||||||
ipSets: ipSets{
|
ipSets: ipSets{
|
||||||
v4Magic: mustIPSetFromPrefix("100.64.0.0/24"),
|
v4Magic: mustIPSetFromPrefix("100.64.0.0/24"),
|
||||||
v6Magic: mustIPSetFromPrefix("fd7a:115c:a1e0:a99c:0100::/80"),
|
v6Magic: mustIPSetFromPrefix("fd7a:115c:a1e0:a99c:0100::/80"),
|
||||||
@@ -430,7 +428,7 @@ func TestReserveIPs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
addrs, err := c.client.reserveAddresses(app, domain, tt.dst)
|
addrs, err := c.client.reserveAddresses(appName, domain, tt.dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -443,8 +441,8 @@ func TestReserveIPs(t *testing.T) {
|
|||||||
if tt.wantTransit != addrs.transit {
|
if tt.wantTransit != addrs.transit {
|
||||||
t.Errorf("want %v, got %v", tt.wantTransit, addrs.transit)
|
t.Errorf("want %v, got %v", tt.wantTransit, addrs.transit)
|
||||||
}
|
}
|
||||||
if app != addrs.app {
|
if appName != addrs.app {
|
||||||
t.Errorf("want %s, got %s", app, addrs.app)
|
t.Errorf("want %s, got %s", appName, addrs.app)
|
||||||
}
|
}
|
||||||
if domain != addrs.domain {
|
if domain != addrs.domain {
|
||||||
t.Errorf("want %s, got %s", domain, addrs.domain)
|
t.Errorf("want %s, got %s", domain, addrs.domain)
|
||||||
@@ -488,13 +486,14 @@ func TestReconfig(t *testing.T) {
|
|||||||
|
|
||||||
func TestConfigFromNodeView(t *testing.T) {
|
func TestConfigFromNodeView(t *testing.T) {
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
name string
|
name string
|
||||||
rawCfg string
|
rawCfg string
|
||||||
cfg []appctype.Conn25Attr
|
cfg []appctype.Conn25Attr
|
||||||
tags []string
|
tags []string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantAppsByDomain map[dnsname.FQDN][]string
|
wantAppsByDomain map[dnsname.FQDN][]string
|
||||||
wantSelfDomains set.Set[dnsname.FQDN]
|
wantAppsByWCDomain map[dnsname.FQDN][]string
|
||||||
|
wantSelfAppNames set.Set[string]
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "bad-config",
|
name: "bad-config",
|
||||||
@@ -512,7 +511,8 @@ func TestConfigFromNodeView(t *testing.T) {
|
|||||||
"a.example.com.": {"one"},
|
"a.example.com.": {"one"},
|
||||||
"b.example.com.": {"two"},
|
"b.example.com.": {"two"},
|
||||||
},
|
},
|
||||||
wantSelfDomains: set.SetOf([]dnsname.FQDN{"a.example.com."}),
|
wantAppsByWCDomain: map[dnsname.FQDN][]string{},
|
||||||
|
wantSelfAppNames: set.SetOf([]string{"one"}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "more-complex-with-connector-self-domains",
|
name: "more-complex-with-connector-self-domains",
|
||||||
@@ -532,7 +532,8 @@ func TestConfigFromNodeView(t *testing.T) {
|
|||||||
"4.b.example.com.": {"four"},
|
"4.b.example.com.": {"four"},
|
||||||
"4.d.example.com.": {"four"},
|
"4.d.example.com.": {"four"},
|
||||||
},
|
},
|
||||||
wantSelfDomains: set.SetOf([]dnsname.FQDN{"1.a.example.com.", "1.b.example.com.", "4.b.example.com.", "4.d.example.com."}),
|
wantAppsByWCDomain: map[dnsname.FQDN][]string{},
|
||||||
|
wantSelfAppNames: set.SetOf([]string{"one", "four"}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "eligible-connector-no-matching-tag-no-self-domains",
|
name: "eligible-connector-no-matching-tag-no-self-domains",
|
||||||
@@ -545,6 +546,49 @@ func TestConfigFromNodeView(t *testing.T) {
|
|||||||
"a.example.com.": {"one"},
|
"a.example.com.": {"one"},
|
||||||
"b.example.com.": {"two"},
|
"b.example.com.": {"two"},
|
||||||
},
|
},
|
||||||
|
wantAppsByWCDomain: map[dnsname.FQDN][]string{}},
|
||||||
|
{
|
||||||
|
name: "wildcard-collapse-and-deduplication",
|
||||||
|
cfg: []appctype.Conn25Attr{
|
||||||
|
{Name: "one", Domains: []string{"*.example.com", "example.com"}, Connectors: []string{"tag:one"}},
|
||||||
|
{Name: "two", Domains: []string{"example.com", "sub.example.com"}, Connectors: []string{"tag:two"}},
|
||||||
|
},
|
||||||
|
tags: []string{"tag:one", "tag:two"},
|
||||||
|
wantAppsByDomain: map[dnsname.FQDN][]string{
|
||||||
|
"example.com.": {"one", "two"},
|
||||||
|
"sub.example.com.": {"two"},
|
||||||
|
},
|
||||||
|
wantAppsByWCDomain: map[dnsname.FQDN][]string{
|
||||||
|
"example.com.": {"one"},
|
||||||
|
},
|
||||||
|
wantSelfAppNames: set.SetOf([]string{"one", "two"}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Domain names that differ only in case must be treated as the same
|
||||||
|
// domain and the app name must appear exactly once in appNamesByDomain,
|
||||||
|
// not once per case variant.
|
||||||
|
name: "case-variant-exact-domains-deduplicated-within-app",
|
||||||
|
cfg: []appctype.Conn25Attr{
|
||||||
|
{Name: "one", Domains: []string{"EXAMPLE.com", "example.COM", "Example.COM"}, Connectors: []string{"tag:one"}},
|
||||||
|
},
|
||||||
|
tags: []string{"tag:one"},
|
||||||
|
wantAppsByDomain: map[dnsname.FQDN][]string{
|
||||||
|
"example.com.": {"one"},
|
||||||
|
},
|
||||||
|
wantAppsByWCDomain: map[dnsname.FQDN][]string{},
|
||||||
|
wantSelfAppNames: set.SetOf([]string{"one"}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Same as above but for wildcard domains: *.EXAMPLE.com and *.example.COM
|
||||||
|
// must collapse to a single entry in appNamesByWCDomain.
|
||||||
|
name: "case-variant-wildcard-domains-deduplicated-within-app",
|
||||||
|
cfg: []appctype.Conn25Attr{
|
||||||
|
{Name: "one", Domains: []string{"*.EXAMPLE.com", "*.example.COM"}, Connectors: []string{"tag:one"}},
|
||||||
|
},
|
||||||
|
tags: []string{"tag:one"},
|
||||||
|
wantAppsByDomain: map[dnsname.FQDN][]string{},
|
||||||
|
wantAppsByWCDomain: map[dnsname.FQDN][]string{"example.com.": {"one"}},
|
||||||
|
wantSelfAppNames: set.SetOf([]string{"one"}),
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@@ -574,8 +618,114 @@ func TestConfigFromNodeView(t *testing.T) {
|
|||||||
if diff := cmp.Diff(tt.wantAppsByDomain, c.appNamesByDomain); diff != "" {
|
if diff := cmp.Diff(tt.wantAppsByDomain, c.appNamesByDomain); diff != "" {
|
||||||
t.Errorf("appsByDomain diff (-want, +got):\n%s", diff)
|
t.Errorf("appsByDomain diff (-want, +got):\n%s", diff)
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(tt.wantSelfDomains, c.selfDomains); diff != "" {
|
if diff := cmp.Diff(tt.wantAppsByWCDomain, c.appNamesByWCDomain); diff != "" {
|
||||||
t.Errorf("selfDomains diff (-want, +got):\n%s", diff)
|
t.Errorf("appsByWCDomain diff (-want, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(tt.wantSelfAppNames, c.selfAppNames); diff != "" {
|
||||||
|
t.Errorf("selfAppNames diff (-want, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAppsForDomainName(t *testing.T) {
|
||||||
|
defaultSN := makeSelfNode(
|
||||||
|
t,
|
||||||
|
[]appctype.Conn25Attr{
|
||||||
|
{Name: "one", Domains: []string{"*.example.com", "example.com"}, Connectors: []string{"tag:one"}},
|
||||||
|
{Name: "two", Domains: []string{"sub.example.com", "example.com"}, Connectors: []string{"tag:two"}},
|
||||||
|
{Name: "three", Domains: []string{"*.sub.example.com"}, Connectors: []string{"tag:three"}},
|
||||||
|
{Name: "four", Domains: []string{"a.sub.example.com"}, Connectors: []string{"tag:four"}},
|
||||||
|
{Name: "self-routed", Domains: []string{"*.wildcard.com", "exact-match.com"}, Connectors: []string{"tag:self-routed"}},
|
||||||
|
},
|
||||||
|
[]string{"tag:self-routed"},
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
isConnector bool
|
||||||
|
domain dnsname.FQDN
|
||||||
|
wantApps []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no-match",
|
||||||
|
domain: "nomatch.com.",
|
||||||
|
wantApps: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exact-match",
|
||||||
|
domain: "example.com.",
|
||||||
|
wantApps: []string{"one", "two"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wildcard-subdomain-match",
|
||||||
|
domain: "a.example.com.",
|
||||||
|
wantApps: []string{"one"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exact-subdomain-match",
|
||||||
|
domain: "sub.example.com.",
|
||||||
|
wantApps: []string{"two"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wildcard-sub-of-subdomain-match",
|
||||||
|
domain: "b.sub.example.com.",
|
||||||
|
wantApps: []string{"three"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exact-sub-of-subdomain-match",
|
||||||
|
domain: "a.sub.example.com.",
|
||||||
|
wantApps: []string{"four"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exact-domain-matches-wildcard",
|
||||||
|
domain: "wildcard.com.",
|
||||||
|
wantApps: []string{"self-routed"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "self-routed-exact-domain-suppressed",
|
||||||
|
isConnector: true,
|
||||||
|
domain: "exact-match.com.",
|
||||||
|
wantApps: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Self node is an eligible connector for "wildcard-self-app" via
|
||||||
|
// *.wildcard.com, so the wildcard match must also be suppressed.
|
||||||
|
name: "self-routed-wildcard-domain-suppressed",
|
||||||
|
isConnector: true,
|
||||||
|
domain: "sub.wildcard.com.",
|
||||||
|
wantApps: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "other-app" is not on a self-connector tag, so it must not be suppressed.
|
||||||
|
name: "non-self-routed-domain-not-suppressed",
|
||||||
|
isConnector: true,
|
||||||
|
domain: "example.com.",
|
||||||
|
wantApps: []string{"one", "two"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Even though the app's connector tag matches the self node's tags,
|
||||||
|
// if the node is not an eligible connector (Advertise=false) then
|
||||||
|
// isSelfRoutedApp returns false and the domain is forwarded normally.
|
||||||
|
name: "not-eligible-connector-not-suppressed",
|
||||||
|
domain: "exact-match.com.",
|
||||||
|
wantApps: []string{"self-routed"},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := newConn25(logger.Discard)
|
||||||
|
if tt.isConnector {
|
||||||
|
c.prefsAdvertiseConnector.Store(true)
|
||||||
|
}
|
||||||
|
cfg := mustConfig(t, defaultSN)
|
||||||
|
c.reconfig(cfg)
|
||||||
|
cfg, ok := c.getConfig()
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("could not get config")
|
||||||
|
}
|
||||||
|
gotApps := cfg.getAppsForConnectorDomain(tt.domain, tt.isConnector)
|
||||||
|
if diff := cmp.Diff(tt.wantApps, gotApps); diff != "" {
|
||||||
|
t.Errorf("unexpected appNames result: diff (-want, +got):\n%s", diff)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -753,6 +903,7 @@ func makeDNSResponseForSections(t *testing.T, questions []dnsmessage.Question, a
|
|||||||
func TestMapDNSResponseAssignsAddrs(t *testing.T) {
|
func TestMapDNSResponseAssignsAddrs(t *testing.T) {
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
name string
|
name string
|
||||||
|
appDomains []string
|
||||||
domain string
|
domain string
|
||||||
v4Addrs []*dnsmessage.AResource
|
v4Addrs []*dnsmessage.AResource
|
||||||
v6Addrs []*dnsmessage.AAAAResource
|
v6Addrs []*dnsmessage.AAAAResource
|
||||||
@@ -761,9 +912,10 @@ func TestMapDNSResponseAssignsAddrs(t *testing.T) {
|
|||||||
wantByMagicIP map[netip.Addr]addrs
|
wantByMagicIP map[netip.Addr]addrs
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "one-ip-matches",
|
name: "one-ip-matches",
|
||||||
domain: "example.com.",
|
appDomains: []string{"example.com"},
|
||||||
v4Addrs: []*dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}},
|
domain: "example.com.",
|
||||||
|
v4Addrs: []*dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}},
|
||||||
// these are 'expected' because they are the beginning of the provided pools
|
// these are 'expected' because they are the beginning of the provided pools
|
||||||
wantByMagicIP: map[netip.Addr]addrs{
|
wantByMagicIP: map[netip.Addr]addrs{
|
||||||
netip.MustParseAddr("100.64.0.0"): {
|
netip.MustParseAddr("100.64.0.0"): {
|
||||||
@@ -776,8 +928,9 @@ func TestMapDNSResponseAssignsAddrs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "v6-ip-matches",
|
name: "v6-ip-matches",
|
||||||
domain: "example.com.",
|
appDomains: []string{"example.com"},
|
||||||
|
domain: "example.com.",
|
||||||
v6Addrs: []*dnsmessage.AAAAResource{
|
v6Addrs: []*dnsmessage.AAAAResource{
|
||||||
{AAAA: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}},
|
{AAAA: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}},
|
||||||
{AAAA: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}},
|
{AAAA: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}},
|
||||||
@@ -800,8 +953,9 @@ func TestMapDNSResponseAssignsAddrs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple-ip-matches",
|
name: "multiple-ip-matches",
|
||||||
domain: "example.com.",
|
appDomains: []string{"example.com"},
|
||||||
|
domain: "example.com.",
|
||||||
v4Addrs: []*dnsmessage.AResource{
|
v4Addrs: []*dnsmessage.AResource{
|
||||||
{A: [4]byte{1, 0, 0, 0}},
|
{A: [4]byte{1, 0, 0, 0}},
|
||||||
{A: [4]byte{2, 0, 0, 0}},
|
{A: [4]byte{2, 0, 0, 0}},
|
||||||
@@ -824,8 +978,9 @@ func TestMapDNSResponseAssignsAddrs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no-domain-match",
|
name: "no-domain-match",
|
||||||
domain: "x.example.com.",
|
appDomains: []string{"foo.example.com"},
|
||||||
|
domain: "bad.example.com.",
|
||||||
v4Addrs: []*dnsmessage.AResource{
|
v4Addrs: []*dnsmessage.AResource{
|
||||||
{A: [4]byte{1, 0, 0, 0}},
|
{A: [4]byte{1, 0, 0, 0}},
|
||||||
{A: [4]byte{2, 0, 0, 0}},
|
{A: [4]byte{2, 0, 0, 0}},
|
||||||
@@ -833,16 +988,18 @@ func TestMapDNSResponseAssignsAddrs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no-rewrite-self-routed-domain",
|
name: "no-rewrite-self-routed-domain",
|
||||||
|
appDomains: []string{"example.com"},
|
||||||
domain: "example.com.",
|
domain: "example.com.",
|
||||||
v4Addrs: []*dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}},
|
v4Addrs: []*dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}},
|
||||||
selfTags: []string{"tag:woo"},
|
selfTags: []string{"tag:woo"},
|
||||||
isEligibleConnector: true,
|
isEligibleConnector: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "rewrite-tagged-but-not-eligible-connector",
|
name: "rewrite-tagged-but-not-eligible-connector",
|
||||||
domain: "example.com.",
|
appDomains: []string{"example.com"},
|
||||||
v4Addrs: []*dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}},
|
domain: "example.com.",
|
||||||
selfTags: []string{"tag:woo"},
|
v4Addrs: []*dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}},
|
||||||
|
selfTags: []string{"tag:woo"},
|
||||||
// isEligibleConnector is false: tag matches but prefs not set,
|
// isEligibleConnector is false: tag matches but prefs not set,
|
||||||
// so DNS response should be rewritten normally.
|
// so DNS response should be rewritten normally.
|
||||||
wantByMagicIP: map[netip.Addr]addrs{
|
wantByMagicIP: map[netip.Addr]addrs{
|
||||||
@@ -857,6 +1014,7 @@ func TestMapDNSResponseAssignsAddrs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "rewrite-eligible-connector-no-matching-tag",
|
name: "rewrite-eligible-connector-no-matching-tag",
|
||||||
|
appDomains: []string{"example.com"},
|
||||||
domain: "example.com.",
|
domain: "example.com.",
|
||||||
v4Addrs: []*dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}},
|
v4Addrs: []*dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}},
|
||||||
selfTags: []string{"tag:unrelated"},
|
selfTags: []string{"tag:unrelated"},
|
||||||
@@ -873,6 +1031,54 @@ func TestMapDNSResponseAssignsAddrs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "subdomain-matches-wildcard",
|
||||||
|
appDomains: []string{"*.example.com"},
|
||||||
|
domain: "sub.example.com.",
|
||||||
|
v4Addrs: []*dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}},
|
||||||
|
// these are 'expected' because they are the beginning of the provided pools
|
||||||
|
wantByMagicIP: map[netip.Addr]addrs{
|
||||||
|
netip.MustParseAddr("100.64.0.0"): {
|
||||||
|
domain: "sub.example.com.",
|
||||||
|
dst: netip.MustParseAddr("1.0.0.0"),
|
||||||
|
magic: netip.MustParseAddr("100.64.0.0"),
|
||||||
|
transit: netip.MustParseAddr("100.64.0.40"),
|
||||||
|
app: "app1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exact-subdomain-matches",
|
||||||
|
appDomains: []string{"example.com", "sub.example.com"},
|
||||||
|
domain: "sub.example.com.",
|
||||||
|
v4Addrs: []*dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}},
|
||||||
|
// these are 'expected' because they are the beginning of the provided pools
|
||||||
|
wantByMagicIP: map[netip.Addr]addrs{
|
||||||
|
netip.MustParseAddr("100.64.0.0"): {
|
||||||
|
domain: "sub.example.com.",
|
||||||
|
dst: netip.MustParseAddr("1.0.0.0"),
|
||||||
|
magic: netip.MustParseAddr("100.64.0.0"),
|
||||||
|
transit: netip.MustParseAddr("100.64.0.40"),
|
||||||
|
app: "app1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wildcard-subdomain-matches-subdomain",
|
||||||
|
appDomains: []string{"example.com", "*.sub.example.com"},
|
||||||
|
domain: "a.sub.example.com.",
|
||||||
|
v4Addrs: []*dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}},
|
||||||
|
// these are 'expected' because they are the beginning of the provided pools
|
||||||
|
wantByMagicIP: map[netip.Addr]addrs{
|
||||||
|
netip.MustParseAddr("100.64.0.0"): {
|
||||||
|
domain: "a.sub.example.com.",
|
||||||
|
dst: netip.MustParseAddr("1.0.0.0"),
|
||||||
|
magic: netip.MustParseAddr("100.64.0.0"),
|
||||||
|
transit: netip.MustParseAddr("100.64.0.40"),
|
||||||
|
app: "app1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
var dnsResp []byte
|
var dnsResp []byte
|
||||||
@@ -884,7 +1090,7 @@ func TestMapDNSResponseAssignsAddrs(t *testing.T) {
|
|||||||
sn := makeSelfNode(t, []appctype.Conn25Attr{{
|
sn := makeSelfNode(t, []appctype.Conn25Attr{{
|
||||||
Name: "app1",
|
Name: "app1",
|
||||||
Connectors: []string{"tag:woo"},
|
Connectors: []string{"tag:woo"},
|
||||||
Domains: []string{"example.com"},
|
Domains: tt.appDomains,
|
||||||
V4MagicIPPool: []netipx.IPRange{v4RangeFrom("0", "10"), v4RangeFrom("20", "30")},
|
V4MagicIPPool: []netipx.IPRange{v4RangeFrom("0", "10"), v4RangeFrom("20", "30")},
|
||||||
V6MagicIPPool: []netipx.IPRange{v6RangeFrom("0", "10"), v6RangeFrom("20", "30")},
|
V6MagicIPPool: []netipx.IPRange{v6RangeFrom("0", "10"), v6RangeFrom("20", "30")},
|
||||||
V4TransitIPPool: []netipx.IPRange{v4RangeFrom("40", "50")},
|
V4TransitIPPool: []netipx.IPRange{v4RangeFrom("40", "50")},
|
||||||
@@ -910,6 +1116,29 @@ func TestMapDNSResponseAssignsAddrs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizedDNSNames(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
domain string
|
||||||
|
want dnsname.FQDN
|
||||||
|
}{
|
||||||
|
{name: "no-change", domain: "example.com.", want: "example.com."},
|
||||||
|
{name: "mixed-case", domain: "eXAmPle.COM", want: "example.com."},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := normalizeDNSName(tt.domain)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error %v", err)
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("Unexpected result, want %q, got %q", tt.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReserveAddressesDeduplicated(t *testing.T) {
|
func TestReserveAddressesDeduplicated(t *testing.T) {
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
name string
|
name string
|
||||||
@@ -925,6 +1154,7 @@ func TestReserveAddressesDeduplicated(t *testing.T) {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
const appName = "a"
|
||||||
conn25 := newConn25(t.Logf)
|
conn25 := newConn25(t.Logf)
|
||||||
c := conn25.client
|
c := conn25.client
|
||||||
c.v4MagicIPPool = newIPPool(mustIPSetFromPrefix("100.64.0.0/24"))
|
c.v4MagicIPPool = newIPPool(mustIPSetFromPrefix("100.64.0.0/24"))
|
||||||
@@ -932,12 +1162,12 @@ func TestReserveAddressesDeduplicated(t *testing.T) {
|
|||||||
c.v4TransitIPPool = newIPPool(mustIPSetFromPrefix("169.254.0.0/24"))
|
c.v4TransitIPPool = newIPPool(mustIPSetFromPrefix("169.254.0.0/24"))
|
||||||
c.v6TransitIPPool = newIPPool(mustIPSetFromPrefix("fd7a:115c:a1e0:a99c:0200::/80"))
|
c.v6TransitIPPool = newIPPool(mustIPSetFromPrefix("fd7a:115c:a1e0:a99c:0200::/80"))
|
||||||
|
|
||||||
first, err := c.reserveAddresses("a", "example.com.", tt.dst)
|
first, err := c.reserveAddresses(appName, "example.com.", tt.dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
second, err := c.reserveAddresses("a", "example.com.", tt.dst)
|
second, err := c.reserveAddresses(appName, "example.com.", tt.dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user