mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-02 04:51:29 +02:00
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> Co-authored-by: Dmitri Dolguikh <dmitri.external@netbird.io>
544 lines
19 KiB
Go
544 lines
19 KiB
Go
package nmaptest
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/miekg/dns"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
nbdns "github.com/netbirdio/netbird/dns"
|
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller/cache"
|
|
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
|
"github.com/netbirdio/netbird/management/internals/modules/zones"
|
|
"github.com/netbirdio/netbird/management/internals/modules/zones/records"
|
|
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
|
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
|
|
networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
|
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
|
"github.com/netbirdio/netbird/management/server/posture"
|
|
"github.com/netbirdio/netbird/management/server/types"
|
|
"github.com/netbirdio/netbird/management/server/types/legacynmap"
|
|
nbroute "github.com/netbirdio/netbird/route"
|
|
"github.com/netbirdio/netbird/shared/management/networkmap"
|
|
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
|
"github.com/netbirdio/netbird/shared/management/proto"
|
|
sharedtypes "github.com/netbirdio/netbird/shared/management/types"
|
|
)
|
|
|
|
// legacyInput is the account and the four derived arguments main's computation
|
|
// took alongside it. The controller resolved them from the account before
|
|
// calling; the twin carries them as fields, so the fixture is the source for
|
|
// both halves.
|
|
type legacyInput struct {
|
|
account *types.Account
|
|
accountZones []*zones.Zone
|
|
validatedPeers map[string]struct{}
|
|
resourcePolicies map[string][]*types.Policy
|
|
routers map[string]map[string]*routerTypes.NetworkRouter
|
|
groupIDToUserIDs map[string][]string
|
|
}
|
|
|
|
// legacyInputFromData rebuilds the Account the fixture stands for. A fixture is
|
|
// the value the store returns, and the store's twins carry exactly the state
|
|
// the computation reads, so inverting them reproduces the account main would
|
|
// have loaded — which is what lets one expectation measure all three paths.
|
|
//
|
|
// The inverse is only defined for what a twin carries: fields the builders drop
|
|
// (peer names, policy descriptions, user records behind AllowedUserIDs) come
|
|
// back as the zero value or a minimal stand-in, because no path reads them.
|
|
func legacyInputFromData(accountID string, nmData *networkmap.NetworkMapData) legacyInput {
|
|
account := &types.Account{
|
|
Id: accountID,
|
|
Network: accountNetwork(nmData.Network),
|
|
Settings: accountSettings(nmData.AccountSettings),
|
|
DNSSettings: types.DNSSettings{DisabledManagementGroups: nmData.DNSSettings.DisabledManagementGroups},
|
|
Peers: make(map[string]*nbpeer.Peer, len(nmData.Peers)),
|
|
Groups: make(map[string]*types.Group, len(nmData.Groups)),
|
|
Policies: make([]*types.Policy, 0, len(nmData.Policies)),
|
|
Routes: make(map[nbroute.ID]*nbroute.Route, len(nmData.Routes)),
|
|
NameServerGroups: make(map[string]*nbdns.NameServerGroup, len(nmData.NameServerGroups)),
|
|
NetworkResources: make([]*resourceTypes.NetworkResource, 0, len(nmData.NetworkResources)),
|
|
PostureChecks: make([]*posture.Checks, 0, len(nmData.PostureChecks)),
|
|
Users: make(map[string]*types.User, len(nmData.AllowedUserIDs)),
|
|
Services: accountServices(nmData.Services),
|
|
}
|
|
|
|
for id, p := range nmData.Peers {
|
|
account.Peers[id] = accountPeer(id, p)
|
|
}
|
|
for id, g := range nmData.Groups {
|
|
account.Groups[id] = accountGroup(id, g)
|
|
}
|
|
|
|
policiesByID := make(map[string]*types.Policy, len(nmData.Policies))
|
|
for _, p := range nmData.Policies {
|
|
policy := accountPolicy(p)
|
|
if policy == nil {
|
|
continue
|
|
}
|
|
account.Policies = append(account.Policies, policy)
|
|
policiesByID[policy.ID] = policy
|
|
}
|
|
|
|
for _, r := range nmData.Routes {
|
|
route := accountRoute(r)
|
|
if route != nil {
|
|
account.Routes[route.ID] = route
|
|
}
|
|
}
|
|
for _, nsg := range nmData.NameServerGroups {
|
|
group := accountNSG(nsg)
|
|
if group != nil {
|
|
account.NameServerGroups[group.ID] = group
|
|
}
|
|
}
|
|
for _, res := range nmData.NetworkResources {
|
|
if resource := accountNetworkResource(res); resource != nil {
|
|
account.NetworkResources = append(account.NetworkResources, resource)
|
|
}
|
|
}
|
|
for id, pc := range nmData.PostureChecks {
|
|
if check := accountPostureChecks(id, pc, nmData.PostureCheckXIDToPublicID[id]); check != nil {
|
|
account.PostureChecks = append(account.PostureChecks, check)
|
|
}
|
|
}
|
|
for xid, publicID := range nmData.NetworkXIDToPublicID {
|
|
account.Networks = append(account.Networks, &networkTypes.Network{ID: xid, PublicID: publicID})
|
|
}
|
|
// The twin keeps only the ids of the users a peer may be shared with; the
|
|
// legacy side derives the same set from the account's user records, so a
|
|
// bare non-blocked regular user per id is enough.
|
|
for userID := range nmData.AllowedUserIDs {
|
|
account.Users[userID] = &types.User{Id: userID}
|
|
}
|
|
|
|
// Main's network-map controller synthesised the reverse-proxy ACLs onto the
|
|
// account and only then derived the resource-policy map, so the frozen copy
|
|
// has to be fed in that order to stand for what main produced.
|
|
account.Policies = append(account.Policies, legacynmap.SynthesizeProxyPolicies(account)...)
|
|
|
|
return legacyInput{
|
|
account: account,
|
|
accountZones: accountZones(nmData.AppliedZoneCandidates),
|
|
validatedPeers: nmData.ValidatedPeers,
|
|
resourcePolicies: account.GetResourcePoliciesMap(),
|
|
routers: accountRouters(nmData.Routers),
|
|
groupIDToUserIDs: nmData.GroupIDToUserIDs,
|
|
}
|
|
}
|
|
|
|
// computeLegacy runs the fixture through main's frozen path and its own proto
|
|
// encoder, the one comparison surface the three modes share.
|
|
func computeLegacy(t *testing.T, ctx context.Context, legacy legacyInput, peerID string, zone nmdata.CustomZone, dnsDomain string, dnsFwdPort int64) *proto.NetworkMap {
|
|
t.Helper()
|
|
|
|
require.NotNil(t, legacy.account, "legacy mode needs an account rebuilt from the fixture")
|
|
peer := legacy.account.Peers[peerID]
|
|
require.NotNil(t, peer, "target peer %q not in rebuilt account", peerID)
|
|
|
|
nm := legacynmap.GetPeerNetworkMapFromComponents(
|
|
legacy.account, ctx, peerID, legacyCustomZone(zone), legacy.accountZones, legacy.validatedPeers,
|
|
legacy.resourcePolicies, legacy.routers, nil, legacy.groupIDToUserIDs,
|
|
)
|
|
require.NotNil(t, nm, "legacy path returned no network map for peer %q", peerID)
|
|
|
|
return legacynmap.ToProtoNetworkMap(
|
|
ctx, peer, nm, dnsDomain, legacy.account.Settings, nil, &cache.DNSConfigCache{}, dnsFwdPort,
|
|
)
|
|
}
|
|
|
|
// legacyCustomZone converts the peers custom zone the runner computes once for
|
|
// every mode into the shape main's path took.
|
|
func legacyCustomZone(z nmdata.CustomZone) nbdns.CustomZone {
|
|
zoneRecords := make([]nbdns.SimpleRecord, 0, len(z.Records))
|
|
for _, r := range z.Records {
|
|
zoneRecords = append(zoneRecords, nbdns.SimpleRecord{
|
|
Name: r.Name,
|
|
Type: r.Type,
|
|
Class: r.Class,
|
|
TTL: r.TTL,
|
|
RData: r.RData,
|
|
})
|
|
}
|
|
return nbdns.CustomZone{
|
|
Domain: z.Domain,
|
|
Records: zoneRecords,
|
|
SearchDomainDisabled: z.SearchDomainDisabled,
|
|
NonAuthoritative: z.NonAuthoritative,
|
|
}
|
|
}
|
|
|
|
func accountNetwork(n *nmdata.Network) *types.Network {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
return &types.Network{
|
|
Identifier: n.Identifier,
|
|
Net: n.Net,
|
|
NetV6: n.NetV6,
|
|
Dns: n.Dns,
|
|
Serial: uint64(n.Serial),
|
|
}
|
|
}
|
|
|
|
func accountSettings(s *nmdata.AccountSettingsInfo) *types.Settings {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
return &types.Settings{
|
|
PeerLoginExpirationEnabled: s.PeerLoginExpirationEnabled,
|
|
PeerLoginExpiration: s.PeerLoginExpiration,
|
|
PeerInactivityExpirationEnabled: s.PeerInactivityExpirationEnabled,
|
|
PeerInactivityExpiration: s.PeerInactivityExpiration,
|
|
DNSDomain: s.DNSDomain,
|
|
IPv6EnabledGroups: s.IPv6EnabledGroups,
|
|
RoutingPeerDNSResolutionEnabled: s.RoutingPeerDNSResolutionEnabled,
|
|
LazyConnectionEnabled: s.LazyConnectionEnabled,
|
|
AutoUpdateVersion: s.AutoUpdateVersion,
|
|
AutoUpdateAlways: s.AutoUpdateAlways,
|
|
MetricsPushEnabled: s.MetricsPushEnabled,
|
|
}
|
|
}
|
|
|
|
func accountPeer(id string, p *nmdata.Peer) *nbpeer.Peer {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
networkAddresses := make([]nbpeer.NetworkAddress, 0, len(p.Meta.NetworkAddresses))
|
|
for _, na := range p.Meta.NetworkAddresses {
|
|
networkAddresses = append(networkAddresses, nbpeer.NetworkAddress{NetIP: na.NetIP})
|
|
}
|
|
files := make([]nbpeer.File, 0, len(p.Meta.Files))
|
|
for _, f := range p.Meta.Files {
|
|
files = append(files, nbpeer.File{Path: f.Path, ProcessIsRunning: f.ProcessIsRunning})
|
|
}
|
|
return &nbpeer.Peer{
|
|
ID: id,
|
|
Key: p.Key,
|
|
SSHKey: p.SSHKey,
|
|
DNSLabel: p.DNSLabel,
|
|
UserID: p.UserID,
|
|
SSHEnabled: p.SSHEnabled,
|
|
LoginExpirationEnabled: p.LoginExpirationEnabled,
|
|
LastLogin: p.LastLogin,
|
|
IP: p.IP,
|
|
IPv6: p.IPv6,
|
|
ExtraDNSLabels: p.ExtraDNSLabels,
|
|
ProxyMeta: nbpeer.ProxyMeta{Embedded: p.ProxyMeta.Embedded, Cluster: p.ProxyMeta.Cluster},
|
|
// Connected is what SynthesizePrivateServiceZones gates its records on,
|
|
// and a fixture peer stands for a peer the store returned, so it is one
|
|
// the account would have reported connected.
|
|
Status: &nbpeer.PeerStatus{RequiresApproval: p.RequiresApproval, Connected: true},
|
|
Meta: nbpeer.PeerSystemMeta{
|
|
WtVersion: p.Meta.WtVersion,
|
|
GoOS: p.Meta.GoOS,
|
|
OSVersion: p.Meta.OSVersion,
|
|
KernelVersion: p.Meta.KernelVersion,
|
|
NetworkAddresses: networkAddresses,
|
|
Files: files,
|
|
Capabilities: p.Meta.Capabilities,
|
|
SyncMessageVersion: p.Meta.SyncMessageVersion,
|
|
Flags: nbpeer.Flags{
|
|
ServerSSHAllowed: p.Meta.Flags.ServerSSHAllowed,
|
|
DisableIPv6: p.Meta.Flags.DisableIPv6,
|
|
},
|
|
},
|
|
Location: nbpeer.Location{
|
|
CountryCode: p.Location.CountryCode,
|
|
CityName: p.Location.CityName,
|
|
ConnectionIP: p.Location.ConnectionIP,
|
|
},
|
|
}
|
|
}
|
|
|
|
func accountGroup(id string, g *nmdata.Group) *types.Group {
|
|
if g == nil {
|
|
return nil
|
|
}
|
|
return &types.Group{
|
|
ID: id,
|
|
Name: g.Name,
|
|
PublicID: g.PublicID,
|
|
Peers: g.Peers,
|
|
}
|
|
}
|
|
|
|
func accountPolicy(p *nmdata.Policy) *types.Policy {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
rules := make([]*types.PolicyRule, 0, len(p.Rules))
|
|
for _, r := range p.Rules {
|
|
if r == nil {
|
|
continue
|
|
}
|
|
var portRanges []sharedtypes.RulePortRange
|
|
if r.PortRanges != nil {
|
|
portRanges = make([]sharedtypes.RulePortRange, len(r.PortRanges))
|
|
for i, pr := range r.PortRanges {
|
|
portRanges[i] = sharedtypes.RulePortRange{Start: pr.Start, End: pr.End}
|
|
}
|
|
}
|
|
rules = append(rules, &types.PolicyRule{
|
|
ID: r.ID,
|
|
PolicyID: r.PolicyID,
|
|
Enabled: r.Enabled,
|
|
Action: sharedtypes.PolicyTrafficActionType(r.Action),
|
|
Protocol: sharedtypes.PolicyRuleProtocolType(r.Protocol),
|
|
Bidirectional: r.Bidirectional,
|
|
Sources: r.Sources,
|
|
Destinations: r.Destinations,
|
|
SourceResource: types.Resource{ID: r.SourceResource.ID, Type: sharedtypes.ResourceType(r.SourceResource.Type)},
|
|
DestinationResource: types.Resource{ID: r.DestinationResource.ID, Type: sharedtypes.ResourceType(r.DestinationResource.Type)},
|
|
Ports: r.Ports,
|
|
PortRanges: portRanges,
|
|
AuthorizedGroups: r.AuthorizedGroups,
|
|
AuthorizedUser: r.AuthorizedUser,
|
|
})
|
|
}
|
|
return &types.Policy{
|
|
ID: p.ID,
|
|
PublicID: p.PublicID,
|
|
Enabled: p.Enabled,
|
|
SourcePostureChecks: p.SourcePostureChecks,
|
|
Rules: rules,
|
|
}
|
|
}
|
|
|
|
func accountRoute(r *nmdata.Route) *nbroute.Route {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
return &nbroute.Route{
|
|
ID: nbroute.ID(r.ID),
|
|
AccountID: r.AccountID,
|
|
PublicID: r.PublicID,
|
|
Network: r.Network,
|
|
Domains: r.Domains,
|
|
KeepRoute: r.KeepRoute,
|
|
NetID: nbroute.NetID(r.NetID),
|
|
Description: r.Description,
|
|
Peer: r.Peer,
|
|
PeerID: r.PeerID,
|
|
PeerGroups: r.PeerGroups,
|
|
NetworkType: nbroute.NetworkType(r.NetworkType),
|
|
Masquerade: r.Masquerade,
|
|
Metric: r.Metric,
|
|
Enabled: r.Enabled,
|
|
Groups: r.Groups,
|
|
AccessControlGroups: r.AccessControlGroups,
|
|
SkipAutoApply: r.SkipAutoApply,
|
|
}
|
|
}
|
|
|
|
func accountNSG(n *nmdata.NameServerGroup) *nbdns.NameServerGroup {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
nameServers := make([]nbdns.NameServer, 0, len(n.NameServers))
|
|
for _, ns := range n.NameServers {
|
|
nameServers = append(nameServers, nbdns.NameServer{
|
|
IP: ns.IP,
|
|
NSType: nbdns.NameServerType(ns.NSType),
|
|
Port: ns.Port,
|
|
})
|
|
}
|
|
return &nbdns.NameServerGroup{
|
|
ID: n.ID,
|
|
PublicID: n.PublicID,
|
|
Name: n.Name,
|
|
Description: n.Description,
|
|
NameServers: nameServers,
|
|
Groups: n.Groups,
|
|
Primary: n.Primary,
|
|
Domains: n.Domains,
|
|
Enabled: n.Enabled,
|
|
SearchDomainsEnabled: n.SearchDomainsEnabled,
|
|
}
|
|
}
|
|
|
|
func accountNetworkResource(r *nmdata.NetworkResource) *resourceTypes.NetworkResource {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
return &resourceTypes.NetworkResource{
|
|
ID: r.ID,
|
|
NetworkID: r.NetworkID,
|
|
AccountID: r.AccountID,
|
|
PublicID: r.PublicID,
|
|
Name: r.Name,
|
|
Description: r.Description,
|
|
Type: resourceTypes.NetworkResourceType(r.Type),
|
|
Address: r.Address,
|
|
Domain: r.Domain,
|
|
Prefix: r.Prefix,
|
|
Enabled: r.Enabled,
|
|
}
|
|
}
|
|
|
|
func accountPostureChecks(id string, pc *nmdata.PostureChecks, publicID string) *posture.Checks {
|
|
if pc == nil {
|
|
return nil
|
|
}
|
|
out := &posture.Checks{ID: id, PublicID: publicID}
|
|
def := pc.Checks
|
|
if def.NBVersionCheck != nil {
|
|
out.Checks.NBVersionCheck = &posture.NBVersionCheck{MinVersion: def.NBVersionCheck.MinVersion}
|
|
}
|
|
if def.OSVersionCheck != nil {
|
|
oc := &posture.OSVersionCheck{}
|
|
if def.OSVersionCheck.Android != nil {
|
|
oc.Android = &posture.MinVersionCheck{MinVersion: def.OSVersionCheck.Android.MinVersion}
|
|
}
|
|
if def.OSVersionCheck.Darwin != nil {
|
|
oc.Darwin = &posture.MinVersionCheck{MinVersion: def.OSVersionCheck.Darwin.MinVersion}
|
|
}
|
|
if def.OSVersionCheck.Ios != nil {
|
|
oc.Ios = &posture.MinVersionCheck{MinVersion: def.OSVersionCheck.Ios.MinVersion}
|
|
}
|
|
if def.OSVersionCheck.Linux != nil {
|
|
oc.Linux = &posture.MinKernelVersionCheck{MinKernelVersion: def.OSVersionCheck.Linux.MinKernelVersion}
|
|
}
|
|
if def.OSVersionCheck.Windows != nil {
|
|
oc.Windows = &posture.MinKernelVersionCheck{MinKernelVersion: def.OSVersionCheck.Windows.MinKernelVersion}
|
|
}
|
|
out.Checks.OSVersionCheck = oc
|
|
}
|
|
if def.GeoLocationCheck != nil {
|
|
gc := &posture.GeoLocationCheck{Action: def.GeoLocationCheck.Action}
|
|
for _, loc := range def.GeoLocationCheck.Locations {
|
|
gc.Locations = append(gc.Locations, posture.Location{CountryCode: loc.CountryCode, CityName: loc.CityName})
|
|
}
|
|
out.Checks.GeoLocationCheck = gc
|
|
}
|
|
if def.PeerNetworkRangeCheck != nil {
|
|
out.Checks.PeerNetworkRangeCheck = &posture.PeerNetworkRangeCheck{
|
|
Action: def.PeerNetworkRangeCheck.Action,
|
|
Ranges: def.PeerNetworkRangeCheck.Ranges,
|
|
}
|
|
}
|
|
if def.ProcessCheck != nil {
|
|
procs := make([]posture.Process, 0, len(def.ProcessCheck.Processes))
|
|
for _, p := range def.ProcessCheck.Processes {
|
|
procs = append(procs, posture.Process{LinuxPath: p.LinuxPath, MacPath: p.MacPath, WindowsPath: p.WindowsPath})
|
|
}
|
|
out.Checks.ProcessCheck = &posture.ProcessCheck{Processes: procs}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func accountServices(services []*nmdata.Service) []*service.Service {
|
|
if len(services) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]*service.Service, 0, len(services))
|
|
for _, svc := range services {
|
|
if svc == nil {
|
|
continue
|
|
}
|
|
targets := make([]*service.Target, 0, len(svc.Targets))
|
|
for _, t := range svc.Targets {
|
|
if t == nil {
|
|
continue
|
|
}
|
|
target := &service.Target{
|
|
Enabled: t.Enabled,
|
|
Port: t.Port,
|
|
Protocol: t.Protocol,
|
|
TargetId: t.TargetID,
|
|
TargetType: service.TargetType(t.TargetType),
|
|
}
|
|
if t.Path != "" {
|
|
path := t.Path
|
|
target.Path = &path
|
|
}
|
|
targets = append(targets, target)
|
|
}
|
|
out = append(out, &service.Service{
|
|
ID: svc.ID,
|
|
Enabled: svc.Enabled,
|
|
Private: svc.Private,
|
|
Mode: svc.Mode,
|
|
ProxyCluster: svc.ProxyCluster,
|
|
AccessGroups: svc.AccessGroups,
|
|
Targets: targets,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// accountZones inverts buildAppliedZoneCandidates. Records come back with the
|
|
// record type the builder mapped them from; a candidate only ever carries the
|
|
// three types it converts.
|
|
func accountZones(candidates []networkmap.AppliedZoneCandidate) []*zones.Zone {
|
|
if len(candidates) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]*zones.Zone, 0, len(candidates))
|
|
for _, candidate := range candidates {
|
|
zoneRecords := make([]*records.Record, 0, len(candidate.Zone.Records))
|
|
for _, r := range candidate.Zone.Records {
|
|
recordType, ok := zoneRecordType(r.Type)
|
|
if !ok {
|
|
continue
|
|
}
|
|
zoneRecords = append(zoneRecords, &records.Record{
|
|
Name: strings.TrimSuffix(r.Name, "."),
|
|
Type: recordType,
|
|
Content: r.RData,
|
|
TTL: r.TTL,
|
|
})
|
|
}
|
|
out = append(out, &zones.Zone{
|
|
ID: candidate.Zone.Domain,
|
|
Domain: strings.TrimSuffix(candidate.Zone.Domain, "."),
|
|
Enabled: true,
|
|
EnableSearchDomain: !candidate.Zone.SearchDomainDisabled,
|
|
DistributionGroups: candidate.DistributionGroups,
|
|
Records: zoneRecords,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func zoneRecordType(recordType int) (records.RecordType, bool) {
|
|
switch uint16(recordType) {
|
|
case dns.TypeA:
|
|
return records.RecordTypeA, true
|
|
case dns.TypeAAAA:
|
|
return records.RecordTypeAAAA, true
|
|
case dns.TypeCNAME:
|
|
return records.RecordTypeCNAME, true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
func accountRouters(routers map[string]map[string]*nmdata.NetworkRouter) map[string]map[string]*routerTypes.NetworkRouter {
|
|
if len(routers) == 0 {
|
|
return nil
|
|
}
|
|
out := make(map[string]map[string]*routerTypes.NetworkRouter, len(routers))
|
|
for networkID, inner := range routers {
|
|
converted := make(map[string]*routerTypes.NetworkRouter, len(inner))
|
|
for peerID, router := range inner {
|
|
if router == nil {
|
|
continue
|
|
}
|
|
converted[peerID] = &routerTypes.NetworkRouter{
|
|
NetworkID: networkID,
|
|
PublicID: router.PublicID,
|
|
Peer: peerID,
|
|
PeerGroups: router.PeerGroups,
|
|
Masquerade: router.Masquerade,
|
|
Metric: router.Metric,
|
|
Enabled: router.Enabled,
|
|
}
|
|
}
|
|
out[networkID] = converted
|
|
}
|
|
return out
|
|
}
|