mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-01 20:41:28 +02:00
125 lines
3.8 KiB
Go
125 lines
3.8 KiB
Go
package networkmap
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/miekg/dns"
|
|
|
|
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
|
)
|
|
|
|
// privateServiceDNSRecordTTL is short so proxy-peer changes propagate quickly.
|
|
const privateServiceDNSRecordTTL = 5
|
|
|
|
// BuildPrivateServiceCandidates derives the per-service DNS records a private
|
|
// service publishes, from the twin's own services. It is the counterpart of
|
|
// InjectProxyPolicies: that one synthesises the ACL half of a private service,
|
|
// this one the DNS half, and both read nmd.Services so a service added to the
|
|
// twin after it was loaded — an agent-network service is synthesised in memory
|
|
// and never persisted — reaches the peer with both halves rather than one.
|
|
//
|
|
// The per-peer access-group gate and the merge by apex stay in the components
|
|
// calculation; this only precomputes what is account-wide.
|
|
func (nmd *NetworkMapData) BuildPrivateServiceCandidates() {
|
|
if len(nmd.Services) == 0 {
|
|
nmd.PrivateServiceCandidates = nil
|
|
return
|
|
}
|
|
|
|
proxyPeersByCluster := nmd.connectedProxyPeersByCluster()
|
|
if len(proxyPeersByCluster) == 0 {
|
|
nmd.PrivateServiceCandidates = nil
|
|
return
|
|
}
|
|
|
|
var out []PrivateServiceCandidate
|
|
for _, svc := range nmd.Services {
|
|
if svc == nil || !svc.Enabled || !svc.Private || len(svc.AccessGroups) == 0 || svc.Domain == "" {
|
|
continue
|
|
}
|
|
proxyPeers := proxyPeersByCluster[svc.ProxyCluster]
|
|
if len(proxyPeers) == 0 {
|
|
continue
|
|
}
|
|
apex := nmd.privateServiceApex(svc)
|
|
if apex == "" {
|
|
continue
|
|
}
|
|
|
|
records := make([]nmdata.SimpleRecord, 0, len(proxyPeers))
|
|
for _, p := range proxyPeers {
|
|
records = append(records, nmdata.SimpleRecord{
|
|
Name: dns.Fqdn(svc.Domain),
|
|
Type: int(dns.TypeA),
|
|
Class: "IN",
|
|
TTL: privateServiceDNSRecordTTL,
|
|
RData: p.IP.String(),
|
|
})
|
|
}
|
|
|
|
out = append(out, PrivateServiceCandidate{
|
|
AccessGroups: svc.AccessGroups,
|
|
Zone: nmdata.CustomZone{
|
|
// NonAuthoritative keeps the zone match-only, so names without
|
|
// an explicit record fall through to the upstream resolver
|
|
// instead of returning NXDOMAIN for the whole apex.
|
|
Domain: dns.Fqdn(apex),
|
|
Records: records,
|
|
NonAuthoritative: true,
|
|
SearchDomainDisabled: true,
|
|
},
|
|
})
|
|
}
|
|
|
|
nmd.PrivateServiceCandidates = out
|
|
}
|
|
|
|
// privateServiceApex resolves the zone a service's record hangs under: the
|
|
// cluster when the service sits directly beneath it, otherwise the longest
|
|
// registered custom domain pointing at that same cluster. A service whose
|
|
// domain matches no registered apex publishes nothing, since a zone the client
|
|
// never intercepts cannot answer the query.
|
|
func (nmd *NetworkMapData) privateServiceApex(svc *nmdata.Service) string {
|
|
if domainUnderSuffix(svc.Domain, svc.ProxyCluster) {
|
|
return svc.ProxyCluster
|
|
}
|
|
|
|
apex := ""
|
|
for _, d := range nmd.Domains {
|
|
if d.TargetCluster != svc.ProxyCluster {
|
|
continue
|
|
}
|
|
if domainUnderSuffix(svc.Domain, d.Domain) && len(d.Domain) > len(apex) {
|
|
apex = d.Domain
|
|
}
|
|
}
|
|
return apex
|
|
}
|
|
|
|
func domainUnderSuffix(domain, suffix string) bool {
|
|
if suffix == "" {
|
|
return false
|
|
}
|
|
return domain == suffix || strings.HasSuffix(domain, "."+suffix)
|
|
}
|
|
|
|
// connectedProxyPeersByCluster groups the account's embedded proxy peers by the
|
|
// cluster they serve, keeping only connected ones.
|
|
func (nmd *NetworkMapData) connectedProxyPeersByCluster() map[string][]*nmdata.Peer {
|
|
var out map[string][]*nmdata.Peer
|
|
for _, peer := range nmd.Peers {
|
|
if peer == nil || !peer.ProxyMeta.Embedded || !peer.Connected || !peer.IP.IsValid() {
|
|
continue
|
|
}
|
|
if out == nil {
|
|
out = make(map[string][]*nmdata.Peer)
|
|
}
|
|
out[peer.ProxyMeta.Cluster] = append(out[peer.ProxyMeta.Cluster], peer)
|
|
}
|
|
for _, peers := range out {
|
|
slices.SortFunc(peers, func(a, b *nmdata.Peer) int { return strings.Compare(a.ID, b.ID) })
|
|
}
|
|
return out
|
|
}
|