mirror of
https://github.com/tailscale/tailscale.git
synced 2026-04-03 22:25:27 -04:00
net/tsdial: add VIP service names to dnsmap with correct address family selection
Adds VIP service name resolution to the MagicDNS map so that service names like "mydb" and "mydb.<tailnet>" resolve to the service VIP address. Uses the same address family iteration as the peer loop to avoid inserting unreachable IPv4 addresses on IPv6-only nodes. Fixes #19097 Signed-off-by: Raj Singh <raj@tailscale.com>
This commit is contained in:
@@ -74,6 +74,32 @@ func dnsMapFromNetworkMap(nm *netmap.NetworkMap) dnsMap {
|
||||
}
|
||||
ret[canonMapKey(rec.Name)] = ip
|
||||
}
|
||||
// Map VIP service names into the DNS map so that MagicDNS can
|
||||
// resolve e.g. "mydb" or "mydb.<tailnet>" to the service VIP.
|
||||
// suffix is derived from the self node name; it's only empty when
|
||||
// the self node is invalid, in which case there's nothing to do.
|
||||
if suffix != "" {
|
||||
for svcName, svcAddrs := range nm.GetVIPServiceIPMap() {
|
||||
bare := svcName.WithoutPrefix()
|
||||
if bare == "" {
|
||||
continue
|
||||
}
|
||||
var ip netip.Addr
|
||||
for _, a := range svcAddrs {
|
||||
if a.Is4() && !have4 {
|
||||
continue
|
||||
}
|
||||
ip = a
|
||||
break
|
||||
}
|
||||
if !ip.IsValid() {
|
||||
continue
|
||||
}
|
||||
fqdn := bare + "." + suffix
|
||||
ret[canonMapKey(fqdn)] = ip
|
||||
ret[canonMapKey(bare)] = ip
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package tsdial
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -12,6 +13,15 @@
|
||||
"tailscale.com/types/netmap"
|
||||
)
|
||||
|
||||
func mustMarshal(t *testing.T, v any) []byte {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func nodeViews(v []*tailcfg.Node) []tailcfg.NodeView {
|
||||
nv := make([]tailcfg.NodeView, len(v))
|
||||
for i, n := range v {
|
||||
@@ -113,6 +123,127 @@ func TestDNSMapFromNetworkMap(t *testing.T) {
|
||||
"b.tailnet": ip("100::202"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "vip_services",
|
||||
nm: &netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
Name: "foo.tailnet.",
|
||||
Addresses: []netip.Prefix{
|
||||
pfx("100.102.103.104/32"),
|
||||
pfx("100::123/128"),
|
||||
},
|
||||
CapMap: tailcfg.NodeCapMap{
|
||||
tailcfg.NodeAttrServiceHost: []tailcfg.RawMessage{
|
||||
tailcfg.RawMessage(mustMarshal(t, tailcfg.ServiceIPMappings{
|
||||
"svc:mydb": {
|
||||
netip.MustParseAddr("100.65.32.1"),
|
||||
netip.MustParseAddr("fd7a:115c:a1e0::1234"),
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
}).View(),
|
||||
},
|
||||
want: dnsMap{
|
||||
"foo": ip("100.102.103.104"),
|
||||
"foo.tailnet": ip("100.102.103.104"),
|
||||
"mydb.tailnet": ip("100.65.32.1"),
|
||||
"mydb": ip("100.65.32.1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "vip_services_v6_only_self",
|
||||
nm: &netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
Name: "foo.tailnet.",
|
||||
Addresses: []netip.Prefix{
|
||||
pfx("100::123/128"),
|
||||
},
|
||||
CapMap: tailcfg.NodeCapMap{
|
||||
tailcfg.NodeAttrServiceHost: []tailcfg.RawMessage{
|
||||
tailcfg.RawMessage(mustMarshal(t, tailcfg.ServiceIPMappings{
|
||||
"svc:mydb": {
|
||||
netip.MustParseAddr("100.65.32.1"),
|
||||
netip.MustParseAddr("fd7a:115c:a1e0::1234"),
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
}).View(),
|
||||
},
|
||||
want: dnsMap{
|
||||
"foo": ip("100::123"),
|
||||
"foo.tailnet": ip("100::123"),
|
||||
"mydb.tailnet": ip("fd7a:115c:a1e0::1234"),
|
||||
"mydb": ip("fd7a:115c:a1e0::1234"),
|
||||
},
|
||||
},
|
||||
{
|
||||
// VIP service has only IPv4 addrs but self is IPv6-only.
|
||||
// Should be excluded entirely since no reachable address exists.
|
||||
name: "vip_services_v4_only_addrs_v6_only_self",
|
||||
nm: &netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
Name: "foo.tailnet.",
|
||||
Addresses: []netip.Prefix{
|
||||
pfx("100::123/128"),
|
||||
},
|
||||
CapMap: tailcfg.NodeCapMap{
|
||||
tailcfg.NodeAttrServiceHost: []tailcfg.RawMessage{
|
||||
tailcfg.RawMessage(mustMarshal(t, tailcfg.ServiceIPMappings{
|
||||
"svc:mydb": {
|
||||
netip.MustParseAddr("100.65.32.1"),
|
||||
netip.MustParseAddr("100.65.32.2"),
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
}).View(),
|
||||
},
|
||||
want: dnsMap{
|
||||
"foo": ip("100::123"),
|
||||
"foo.tailnet": ip("100::123"),
|
||||
// mydb should NOT appear — both addrs are IPv4 and self is v6-only
|
||||
},
|
||||
},
|
||||
{
|
||||
// VIP service name collides with a peer name.
|
||||
// VIP runs after peers so it overwrites.
|
||||
name: "vip_service_overwrites_peer",
|
||||
nm: &netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
Name: "foo.tailnet.",
|
||||
Addresses: []netip.Prefix{
|
||||
pfx("100.102.103.104/32"),
|
||||
pfx("100::123/128"),
|
||||
},
|
||||
CapMap: tailcfg.NodeCapMap{
|
||||
tailcfg.NodeAttrServiceHost: []tailcfg.RawMessage{
|
||||
tailcfg.RawMessage(mustMarshal(t, tailcfg.ServiceIPMappings{
|
||||
"svc:a": {
|
||||
netip.MustParseAddr("100.65.32.1"),
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
}).View(),
|
||||
Peers: nodeViews([]*tailcfg.Node{
|
||||
{
|
||||
Name: "a.tailnet",
|
||||
Addresses: []netip.Prefix{
|
||||
pfx("100.0.0.201/32"),
|
||||
pfx("100::201/128"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
want: dnsMap{
|
||||
"foo": ip("100.102.103.104"),
|
||||
"foo.tailnet": ip("100.102.103.104"),
|
||||
"a": ip("100.65.32.1"), // VIP overwrites peer
|
||||
"a.tailnet": ip("100.65.32.1"), // VIP overwrites peer
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user