[management] expose peer MAC addresses and make peers searchable by MAC (#6553)

This commit is contained in:
Allan ELKAIM
2026-10-02 15:00:26 +02:00
committed by GitHub
parent 0712a5a5b9
commit e2678d4e05
21 changed files with 265 additions and 46 deletions
@@ -44,7 +44,7 @@ func (r *repository) GetAccountNetwork(ctx context.Context, accountID string) (*
} }
func (r *repository) GetAccountPeers(ctx context.Context, accountID string) ([]*peer.Peer, error) { func (r *repository) GetAccountPeers(ctx context.Context, accountID string) ([]*peer.Peer, error) {
return r.store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "") return r.store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "", "")
} }
func (r *repository) GetAccountByPeerID(ctx context.Context, peerID string) (*types.Account, error) { func (r *repository) GetAccountByPeerID(ctx context.Context, peerID string) (*types.Account, error) {
@@ -97,7 +97,7 @@ func (m *managerImpl) GetAllPeers(ctx context.Context, accountID, userID string)
return m.store.GetUserPeers(ctx, store.LockingStrengthNone, accountID, userID) return m.store.GetUserPeers(ctx, store.LockingStrengthNone, accountID, userID)
} }
return m.store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "") return m.store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "", "")
} }
func (m *managerImpl) GetPeerAccountID(ctx context.Context, peerID string) (string, error) { func (m *managerImpl) GetPeerAccountID(ctx context.Context, peerID string) (string, error) {
+5 -5
View File
@@ -2391,7 +2391,7 @@ func (am *DefaultAccountManager) reallocateAccountPeerIPs(ctx context.Context, t
return err return err
} }
peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthUpdate, accountID, "", "") peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthUpdate, accountID, "", "", "")
if err != nil { if err != nil {
return err return err
} }
@@ -2428,7 +2428,7 @@ func (am *DefaultAccountManager) reallocateAccountPeerIPs(ctx context.Context, t
// v6 address get one allocated. When disabled, all v6 addresses are cleared. // v6 address get one allocated. When disabled, all v6 addresses are cleared.
// When the v6 range changes, all v6 addresses are reallocated. // When the v6 range changes, all v6 addresses are reallocated.
func (am *DefaultAccountManager) checkIPv6Collision(ctx context.Context, transaction store.Store, accountID, peerID string, newIPv6 netip.Addr) error { func (am *DefaultAccountManager) checkIPv6Collision(ctx context.Context, transaction store.Store, accountID, peerID string, newIPv6 netip.Addr) error {
peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, "", "") peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, "", "", "")
if err != nil { if err != nil {
return fmt.Errorf("get peers: %w", err) return fmt.Errorf("get peers: %w", err)
} }
@@ -2441,7 +2441,7 @@ func (am *DefaultAccountManager) checkIPv6Collision(ctx context.Context, transac
} }
func (am *DefaultAccountManager) updatePeerIPv6Addresses(ctx context.Context, transaction store.Store, accountID string, settings *types.Settings) error { func (am *DefaultAccountManager) updatePeerIPv6Addresses(ctx context.Context, transaction store.Store, accountID string, settings *types.Settings) error {
peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthUpdate, accountID, "", "") peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthUpdate, accountID, "", "", "")
if err != nil { if err != nil {
return fmt.Errorf("get peers: %w", err) return fmt.Errorf("get peers: %w", err)
} }
@@ -2602,7 +2602,7 @@ func (am *DefaultAccountManager) buildIPv6AllowedPeers(ctx context.Context, tran
// Embedded proxy peers sit outside regular group membership but must // Embedded proxy peers sit outside regular group membership but must
// participate in any v6-enabled overlay to reach v6-only peers. // participate in any v6-enabled overlay to reach v6-only peers.
peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "") peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "", "")
if err != nil { if err != nil {
return nil, fmt.Errorf("get peers: %w", err) return nil, fmt.Errorf("get peers: %w", err)
} }
@@ -2673,7 +2673,7 @@ func (am *DefaultAccountManager) updatePeerIPInTransaction(ctx context.Context,
return nil return nil
} }
peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, "", "") peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, "", "", "")
if err != nil { if err != nil {
return fmt.Errorf("get account peers: %w", err) return fmt.Errorf("get account peers: %w", err)
} }
+1 -1
View File
@@ -62,7 +62,7 @@ type Manager interface {
GetUserByID(ctx context.Context, id string) (*types.User, error) GetUserByID(ctx context.Context, id string) (*types.User, error)
GetUserFromUserAuth(ctx context.Context, userAuth auth.UserAuth) (*types.User, error) GetUserFromUserAuth(ctx context.Context, userAuth auth.UserAuth) (*types.User, error)
ListUsers(ctx context.Context, accountID string) ([]*types.User, error) ListUsers(ctx context.Context, accountID string) ([]*types.User, error)
GetPeers(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) GetPeers(ctx context.Context, accountID, userID, nameFilter, ipFilter, macFilter string) ([]*nbpeer.Peer, error)
MarkPeerConnected(ctx context.Context, peerKey string, accountID string, sessionStartedAt int64, nmap *types.NetworkMap) error MarkPeerConnected(ctx context.Context, peerKey string, accountID string, sessionStartedAt int64, nmap *types.NetworkMap) error
MarkPeerDisconnected(ctx context.Context, peerKey string, accountID string, sessionStartedAt int64) error MarkPeerDisconnected(ctx context.Context, peerKey string, accountID string, sessionStartedAt int64) error
DeletePeer(ctx context.Context, accountID, peerID, userID string) error DeletePeer(ctx context.Context, accountID, peerID, userID string) error
+4 -4
View File
@@ -982,18 +982,18 @@ func (mr *MockManagerMockRecorder) GetPeerNetwork(ctx, peerID any) *gomock.Call
} }
// GetPeers mocks base method. // GetPeers mocks base method.
func (m *MockManager) GetPeers(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*peer.Peer, error) { func (m *MockManager) GetPeers(ctx context.Context, accountID, userID, nameFilter, ipFilter, macFilter string) ([]*peer.Peer, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPeers", ctx, accountID, userID, nameFilter, ipFilter) ret := m.ctrl.Call(m, "GetPeers", ctx, accountID, userID, nameFilter, ipFilter, macFilter)
ret0, _ := ret[0].([]*peer.Peer) ret0, _ := ret[0].([]*peer.Peer)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetPeers indicates an expected call of GetPeers. // GetPeers indicates an expected call of GetPeers.
func (mr *MockManagerMockRecorder) GetPeers(ctx, accountID, userID, nameFilter, ipFilter any) *gomock.Call { func (mr *MockManagerMockRecorder) GetPeers(ctx, accountID, userID, nameFilter, ipFilter, macFilter any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeers", reflect.TypeOf((*MockManager)(nil).GetPeers), ctx, accountID, userID, nameFilter, ipFilter) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeers", reflect.TypeOf((*MockManager)(nil).GetPeers), ctx, accountID, userID, nameFilter, ipFilter, macFilter)
} }
// GetPolicy mocks base method. // GetPolicy mocks base method.
+9 -9
View File
@@ -2557,7 +2557,7 @@ func TestDefaultAccountManager_UpdateAccountSettings_PeerApproval(t *testing.T)
_, err = manager.UpdateAccountSettings(ctx, accountID, userID, newSettings) _, err = manager.UpdateAccountSettings(ctx, accountID, userID, newSettings)
require.NoError(t, err) require.NoError(t, err)
accountPeers, err := manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "") accountPeers, err := manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "", "")
require.NoError(t, err) require.NoError(t, err)
for _, peer := range accountPeers { for _, peer := range accountPeers {
@@ -4557,7 +4557,7 @@ func TestDefaultAccountManager_UpdateAccountSettings_NetworkRangePreserved(t *te
}) })
require.NoError(t, err) require.NoError(t, err)
peers, err := manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, account.Id, "", "") peers, err := manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, account.Id, "", "", "")
require.NoError(t, err) require.NoError(t, err)
require.Len(t, peers, len(before)) require.Len(t, peers, len(before))
for _, p := range peers { for _, p := range peers {
@@ -4575,7 +4575,7 @@ func TestDefaultAccountManager_UpdateAccountSettings_NetworkRangePreserved(t *te
}) })
require.NoError(t, err) require.NoError(t, err)
peers, err = manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, account.Id, "", "") peers, err = manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, account.Id, "", "", "")
require.NoError(t, err) require.NoError(t, err)
for _, p := range peers { for _, p := range peers {
assert.Equal(t, before[p.ID], p.IP, "peer %s IP should not change for host-bit-set equivalent range", p.ID) assert.Equal(t, before[p.ID], p.IP, "peer %s IP should not change for host-bit-set equivalent range", p.ID)
@@ -4589,7 +4589,7 @@ func TestDefaultAccountManager_UpdateAccountSettings_NetworkRangePreserved(t *te
}) })
require.NoError(t, err) require.NoError(t, err)
peers, err = manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, account.Id, "", "") peers, err = manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, account.Id, "", "", "")
require.NoError(t, err) require.NoError(t, err)
for _, p := range peers { for _, p := range peers {
assert.Equal(t, before[p.ID], p.IP, "peer %s IP should not change when NetworkRange omitted", p.ID) assert.Equal(t, before[p.ID], p.IP, "peer %s IP should not change when NetworkRange omitted", p.ID)
@@ -4605,7 +4605,7 @@ func TestDefaultAccountManager_UpdateAccountSettings_NetworkRangePreserved(t *te
}) })
require.NoError(t, err) require.NoError(t, err)
peers, err = manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, account.Id, "", "") peers, err = manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, account.Id, "", "", "")
require.NoError(t, err) require.NoError(t, err)
for _, p := range peers { for _, p := range peers {
assert.True(t, newRange.Contains(p.IP), "peer %s should be in new range %s, got %s", p.ID, newRange, p.IP) assert.True(t, newRange.Contains(p.IP), "peer %s should be in new range %s, got %s", p.ID, newRange, p.IP)
@@ -4623,7 +4623,7 @@ func TestDefaultAccountManager_UpdateAccountSettings_IPv6EnabledGroups(t *testin
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, settings.IPv6EnabledGroups, "new account should have IPv6 enabled for All group") require.NotEmpty(t, settings.IPv6EnabledGroups, "new account should have IPv6 enabled for All group")
peers, err := manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "") peers, err := manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "", "")
require.NoError(t, err) require.NoError(t, err)
for _, p := range peers { for _, p := range peers {
assert.True(t, p.IPv6.IsValid(), "peer %s should have IPv6 with All group enabled", p.ID) assert.True(t, p.IPv6.IsValid(), "peer %s should have IPv6 with All group enabled", p.ID)
@@ -4651,7 +4651,7 @@ func TestDefaultAccountManager_UpdateAccountSettings_IPv6EnabledGroups(t *testin
assert.Equal(t, []string{partialGroup.ID}, updatedSettings.IPv6EnabledGroups) assert.Equal(t, []string{partialGroup.ID}, updatedSettings.IPv6EnabledGroups)
// peer1 and peer2 should have IPv6; peer3 should not. // peer1 and peer2 should have IPv6; peer3 should not.
peers, err = manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "") peers, err = manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "", "")
require.NoError(t, err) require.NoError(t, err)
peerMap := make(map[string]*nbpeer.Peer, len(peers)) peerMap := make(map[string]*nbpeer.Peer, len(peers))
for _, p := range peers { for _, p := range peers {
@@ -4671,7 +4671,7 @@ func TestDefaultAccountManager_UpdateAccountSettings_IPv6EnabledGroups(t *testin
require.NoError(t, err) require.NoError(t, err)
assert.Empty(t, updatedSettings.IPv6EnabledGroups) assert.Empty(t, updatedSettings.IPv6EnabledGroups)
peers, err = manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "") peers, err = manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "", "")
require.NoError(t, err) require.NoError(t, err)
for _, p := range peers { for _, p := range peers {
assert.False(t, p.IPv6.IsValid(), "peer %s should have no IPv6 when groups cleared", p.ID) assert.False(t, p.IPv6.IsValid(), "peer %s should have no IPv6 when groups cleared", p.ID)
@@ -4686,7 +4686,7 @@ func TestDefaultAccountManager_UpdateAccountSettings_IPv6EnabledGroups(t *testin
}) })
require.NoError(t, err) require.NoError(t, err)
peers, err = manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "") peers, err = manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "", "")
require.NoError(t, err) require.NoError(t, err)
peerMap = make(map[string]*nbpeer.Peer, len(peers)) peerMap = make(map[string]*nbpeer.Peer, len(peers))
for _, p := range peers { for _, p := range peers {
@@ -127,7 +127,7 @@ func (h *handler) validateNetworkRange(ctx context.Context, accountID, userID st
} }
func (h *handler) validateCapacity(ctx context.Context, accountID, userID string, prefix netip.Prefix) error { func (h *handler) validateCapacity(ctx context.Context, accountID, userID string, prefix netip.Prefix) error {
peers, err := h.accountManager.GetPeers(ctx, accountID, userID, "", "") peers, err := h.accountManager.GetPeers(ctx, accountID, userID, "", "", "")
if err != nil { if err != nil {
return status.Errorf(status.Internal, "get peer count: %v", err) return status.Errorf(status.Internal, "get peer count: %v", err)
} }
@@ -58,7 +58,7 @@ func (h *handler) getAllGroups(w http.ResponseWriter, r *http.Request) {
return return
} }
accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "") accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "", "")
if err != nil { if err != nil {
util.WriteError(r.Context(), err, w) util.WriteError(r.Context(), err, w)
return return
@@ -77,7 +77,7 @@ func (h *handler) getAllGroups(w http.ResponseWriter, r *http.Request) {
return return
} }
accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "") accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "", "")
if err != nil { if err != nil {
util.WriteError(r.Context(), err, w) util.WriteError(r.Context(), err, w)
return return
@@ -169,7 +169,7 @@ func (h *handler) updateGroup(w http.ResponseWriter, r *http.Request) {
return return
} }
accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "") accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "", "")
if err != nil { if err != nil {
util.WriteError(r.Context(), err, w) util.WriteError(r.Context(), err, w)
return return
@@ -226,7 +226,7 @@ func (h *handler) createGroup(w http.ResponseWriter, r *http.Request) {
return return
} }
accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "") accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "", "")
if err != nil { if err != nil {
util.WriteError(r.Context(), err, w) util.WriteError(r.Context(), err, w)
return return
@@ -287,7 +287,7 @@ func (h *handler) getGroup(w http.ResponseWriter, r *http.Request) {
return return
} }
accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "") accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "", "")
if err != nil { if err != nil {
util.WriteError(r.Context(), err, w) util.WriteError(r.Context(), err, w)
return return
@@ -78,7 +78,7 @@ func initGroupTestData(initGroups ...*types.Group) *handler {
return nil, status.Errorf(status.NotFound, "unknown group name") return nil, status.Errorf(status.NotFound, "unknown group name")
}, },
GetPeersFunc: func(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) { GetPeersFunc: func(ctx context.Context, accountID, userID, nameFilter, ipFilter, macFilter string) ([]*nbpeer.Peer, error) {
return maps.Values(TestPeers), nil return maps.Values(TestPeers), nil
}, },
DeleteGroupFunc: func(_ context.Context, accountID, userId, groupID string) error { DeleteGroupFunc: func(_ context.Context, accountID, userId, groupID string) error {
@@ -317,10 +317,11 @@ func (h *Handler) GetAllPeers(w http.ResponseWriter, r *http.Request) {
nameFilter := r.URL.Query().Get("name") nameFilter := r.URL.Query().Get("name")
ipFilter := r.URL.Query().Get("ip") ipFilter := r.URL.Query().Get("ip")
macFilter := r.URL.Query().Get("mac")
accountID, userID := userAuth.AccountId, userAuth.UserId accountID, userID := userAuth.AccountId, userAuth.UserId
peers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, nameFilter, ipFilter) peers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, nameFilter, ipFilter, macFilter)
if err != nil { if err != nil {
util.WriteError(r.Context(), err, w) util.WriteError(r.Context(), err, w)
return return
@@ -571,6 +572,17 @@ func peerToAccessiblePeer(peer *nbpeer.Peer, dnsDomain string) api.AccessiblePee
} }
} }
func toNetworkAddresses(addrs []nbpeer.NetworkAddress) *[]api.NetworkAddress {
if len(addrs) == 0 {
return nil
}
out := make([]api.NetworkAddress, 0, len(addrs))
for _, a := range addrs {
out = append(out, api.NetworkAddress{NetIp: a.NetIP.String(), Mac: a.Mac})
}
return &out
}
func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, approved bool, reason string) *api.Peer { func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, approved bool, reason string) *api.Peer {
osVersion := peer.Meta.OSVersion osVersion := peer.Meta.OSVersion
if osVersion == "" { if osVersion == "" {
@@ -583,6 +595,7 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD
Name: peer.Name, Name: peer.Name,
Ip: peer.IP.String(), Ip: peer.IP.String(),
Ipv6: peerIPv6String(peer), Ipv6: peerIPv6String(peer),
NetworkAddresses: toNetworkAddresses(peer.Meta.NetworkAddresses),
ConnectionIp: peer.Location.ConnectionIP.String(), ConnectionIp: peer.Location.ConnectionIP.String(),
Connected: peer.Status.Connected, Connected: peer.Status.Connected,
LastSeen: peer.Status.LastSeen, LastSeen: peer.Status.LastSeen,
@@ -639,6 +652,7 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn
Name: peer.Name, Name: peer.Name,
Ip: peer.IP.String(), Ip: peer.IP.String(),
Ipv6: peerIPv6String(peer), Ipv6: peerIPv6String(peer),
NetworkAddresses: toNetworkAddresses(peer.Meta.NetworkAddresses),
ConnectionIp: peer.Location.ConnectionIP.String(), ConnectionIp: peer.Location.ConnectionIP.String(),
Connected: peer.Status.Connected, Connected: peer.Status.Connected,
LastSeen: peer.Status.LastSeen, LastSeen: peer.Status.LastSeen,
@@ -173,7 +173,7 @@ func initTestMetaData(t *testing.T, peers ...*nbpeer.Peer) *Handler {
return nil, fmt.Errorf("user not found") return nil, fmt.Errorf("user not found")
} }
}, },
GetPeersFunc: func(_ context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) { GetPeersFunc: func(_ context.Context, accountID, userID, nameFilter, ipFilter, macFilter string) ([]*nbpeer.Peer, error) {
return peers, nil return peers, nil
}, },
GetPeerGroupsFunc: func(ctx context.Context, accountID, peerID string) ([]*types.Group, error) { GetPeerGroupsFunc: func(ctx context.Context, accountID, peerID string) ([]*types.Group, error) {
@@ -364,6 +364,50 @@ func TestGetPeers(t *testing.T) {
} }
} }
func TestPeerResponseNetworkAddresses(t *testing.T) {
tests := []struct {
name string
addresses []nbpeer.NetworkAddress
wantJSON string
}{
{name: "not reported"},
{name: "empty", addresses: []nbpeer.NetworkAddress{}},
{
name: "multiple interfaces",
addresses: []nbpeer.NetworkAddress{
{NetIP: netip.MustParsePrefix("192.168.0.11/24"), Mac: "00:93:37:bd:83:0f"},
{NetIP: netip.MustParsePrefix("2001:db8::123/64"), Mac: "00:93:37:bd:83:10"},
},
wantJSON: `[{"net_ip":"192.168.0.11/24","mac":"00:93:37:bd:83:0f"},{"net_ip":"2001:db8::123/64","mac":"00:93:37:bd:83:10"}]`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
peer := &nbpeer.Peer{
Status: &nbpeer.PeerStatus{},
Meta: nbpeer.PeerSystemMeta{NetworkAddresses: tt.addresses},
}
responses := map[string]any{
"single peer": toSinglePeerResponse(peer, nil, "example.com", true, ""),
"peer list": toPeerListItemResponse(peer, nil, "example.com", 0),
}
for name, response := range responses {
t.Run(name, func(t *testing.T) {
body, err := json.Marshal(response)
require.NoError(t, err)
var fields map[string]json.RawMessage
require.NoError(t, json.Unmarshal(body, &fields))
if tt.wantJSON == "" {
assert.NotContains(t, fields, "network_addresses", "unreported interfaces should be omitted")
return
}
assert.JSONEq(t, tt.wantJSON, string(fields["network_addresses"]), "response should preserve interface addresses and MACs")
})
}
})
}
}
func TestGetAccessiblePeers(t *testing.T) { func TestGetAccessiblePeers(t *testing.T) {
peer1 := &nbpeer.Peer{ peer1 := &nbpeer.Peer{
ID: "peer1", ID: "peer1",
+1 -1
View File
@@ -100,7 +100,7 @@ func (am *DefaultAccountManager) GetValidatedPeers(ctx context.Context, accountI
return nil, nil, err return nil, nil, err
} }
peers, err = am.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "") peers, err = am.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "", "")
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -39,7 +39,7 @@ type MockAccountManager struct {
GetAccountIDByUserIdFunc func(ctx context.Context, userAuth auth.UserAuth) (string, error) GetAccountIDByUserIdFunc func(ctx context.Context, userAuth auth.UserAuth) (string, error)
GetUserFromUserAuthFunc func(ctx context.Context, userAuth auth.UserAuth) (*types.User, error) GetUserFromUserAuthFunc func(ctx context.Context, userAuth auth.UserAuth) (*types.User, error)
ListUsersFunc func(ctx context.Context, accountID string) ([]*types.User, error) ListUsersFunc func(ctx context.Context, accountID string) ([]*types.User, error)
GetPeersFunc func(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) GetPeersFunc func(ctx context.Context, accountID, userID, nameFilter, ipFilter, macFilter string) ([]*nbpeer.Peer, error)
MarkPeerConnectedFunc func(ctx context.Context, peerKey string, accountID string, sessionStartedAt int64, nmap *types.NetworkMap) error MarkPeerConnectedFunc func(ctx context.Context, peerKey string, accountID string, sessionStartedAt int64, nmap *types.NetworkMap) error
MarkPeerDisconnectedFunc func(ctx context.Context, peerKey string, accountID string, sessionStartedAt int64) error MarkPeerDisconnectedFunc func(ctx context.Context, peerKey string, accountID string, sessionStartedAt int64) error
SyncAndMarkPeerFunc func(ctx context.Context, accountID string, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP, syncTime time.Time) (*nbpeer.Peer, *types.NetworkMap, []*nmdata.PostureChecks, int64, error) SyncAndMarkPeerFunc func(ctx context.Context, accountID string, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP, syncTime time.Time) (*nbpeer.Peer, *types.NetworkMap, []*nmdata.PostureChecks, int64, error)
@@ -807,9 +807,9 @@ func (am *MockAccountManager) GetAccountIDFromUserAuth(ctx context.Context, user
} }
// GetPeers mocks GetPeers of the AccountManager interface // GetPeers mocks GetPeers of the AccountManager interface
func (am *MockAccountManager) GetPeers(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) { func (am *MockAccountManager) GetPeers(ctx context.Context, accountID, userID, nameFilter, ipFilter, macFilter string) ([]*nbpeer.Peer, error) {
if am.GetPeersFunc != nil { if am.GetPeersFunc != nil {
return am.GetPeersFunc(ctx, accountID, userID, nameFilter, ipFilter) return am.GetPeersFunc(ctx, accountID, userID, nameFilter, ipFilter, macFilter)
} }
return nil, status.Errorf(codes.Unimplemented, "method GetPeers is not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetPeers is not implemented")
} }
+2 -2
View File
@@ -47,7 +47,7 @@ const (
// GetPeers returns peers visible to the user within an account. // GetPeers returns peers visible to the user within an account.
// Users with "peers:read" see all peers. Otherwise, users see only their own peers, or none if restricted by account settings. // Users with "peers:read" see all peers. Otherwise, users see only their own peers, or none if restricted by account settings.
func (am *DefaultAccountManager) GetPeers(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) { func (am *DefaultAccountManager) GetPeers(ctx context.Context, accountID, userID, nameFilter, ipFilter, macFilter string) ([]*nbpeer.Peer, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthNone, userID) user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthNone, userID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -59,7 +59,7 @@ func (am *DefaultAccountManager) GetPeers(ctx context.Context, accountID, userID
} }
if allowed { if allowed {
return am.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, nameFilter, ipFilter) return am.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, nameFilter, ipFilter, macFilter)
} }
settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthNone, accountID) settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthNone, accountID)
+74 -2
View File
@@ -4,10 +4,14 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
b64 "encoding/base64" b64 "encoding/base64"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http"
"net/http/httptest"
"net/netip" "net/netip"
"net/url"
"os" "os"
"runtime" "runtime"
"strconv" "strconv"
@@ -33,12 +37,15 @@ import (
"github.com/netbirdio/netbird/management/internals/server/config" "github.com/netbirdio/netbird/management/internals/server/config"
"github.com/netbirdio/netbird/management/internals/shared/grpc" "github.com/netbirdio/netbird/management/internals/shared/grpc"
nbcache "github.com/netbirdio/netbird/management/server/cache" nbcache "github.com/netbirdio/netbird/management/server/cache"
nbcontext "github.com/netbirdio/netbird/management/server/context"
peershandler "github.com/netbirdio/netbird/management/server/http/handlers/peers"
"github.com/netbirdio/netbird/management/server/http/testing/testing_tools" "github.com/netbirdio/netbird/management/server/http/testing/testing_tools"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding" "github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
"github.com/netbirdio/netbird/management/server/job" "github.com/netbirdio/netbird/management/server/job"
"github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/settings" "github.com/netbirdio/netbird/management/server/settings"
"github.com/netbirdio/netbird/shared/auth" "github.com/netbirdio/netbird/shared/auth"
"github.com/netbirdio/netbird/shared/management/http/api"
"github.com/netbirdio/netbird/shared/management/status" "github.com/netbirdio/netbird/shared/management/status"
"github.com/netbirdio/netbird/management/server/util" "github.com/netbirdio/netbird/management/server/util"
@@ -718,7 +725,7 @@ func TestDefaultAccountManager_GetPeers(t *testing.T) {
return return
} }
peers, err := manager.GetPeers(context.Background(), accountID, someUser, "", "") peers, err := manager.GetPeers(context.Background(), accountID, someUser, "", "", "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
return return
@@ -731,6 +738,71 @@ func TestDefaultAccountManager_GetPeers(t *testing.T) {
} }
} }
func TestDefaultAccountManager_GetPeers_FilterByMac(t *testing.T) {
ctx := context.Background()
manager, _, err := createManager(t)
require.NoError(t, err)
account := newAccountWithId(ctx, "mac-account", "mac-admin", "", "", "", false)
account.Peers["matching"] = &nbpeer.Peer{
ID: "matching", Key: "matching-key", Name: "laptop", DNSLabel: "laptop",
IP: netip.MustParseAddr("100.64.0.10"), Status: &nbpeer.PeerStatus{LastSeen: time.Now()},
Meta: nbpeer.PeerSystemMeta{NetworkAddresses: []nbpeer.NetworkAddress{
{NetIP: netip.MustParsePrefix("192.168.0.11/24"), Mac: "00:93:37:bd:83:0f"},
{NetIP: netip.MustParsePrefix("192.168.1.11/24"), Mac: "aa:bb:cc:dd:ee:ff"},
}},
}
account.Peers["other"] = &nbpeer.Peer{
ID: "other", Key: "other-key", Name: "desktop", DNSLabel: "desktop",
IP: netip.MustParseAddr("100.64.0.20"), Status: &nbpeer.PeerStatus{LastSeen: time.Now()},
}
require.NoError(t, manager.Store.SaveAccount(ctx, account))
otherAccount := newAccountWithId(ctx, "other-account", "other-admin", "", "", "", false)
otherPeer := account.Peers["matching"].Copy()
otherPeer.ID, otherPeer.Key = "outside-account", "outside-key"
otherAccount.Peers[otherPeer.ID] = otherPeer
require.NoError(t, manager.Store.SaveAccount(ctx, otherAccount))
handler := peershandler.NewHandler(manager, manager.networkMapController, manager.permissionsManager)
tests := []struct {
name, nameFilter, ipFilter, macFilter string
wantIDs []string
}{
{name: "no filter", wantIDs: []string{"matching", "other"}},
{name: "full MAC", macFilter: "00:93:37:bd:83:0f", wantIDs: []string{"matching"}},
{name: "partial MAC", macFilter: "93:37:bd", wantIDs: []string{"matching"}},
{name: "second interface", macFilter: "aa:bb:cc:dd:ee:ff", wantIDs: []string{"matching"}},
{name: "unknown MAC", macFilter: "11:22:33:44:55:66"},
{name: "combined filters", nameFilter: "laptop", ipFilter: "100.64.0.10", macFilter: "00:93:37", wantIDs: []string{"matching"}},
{name: "name mismatch", nameFilter: "desktop", macFilter: "00:93:37"},
{name: "IP mismatch", ipFilter: "100.64.0.20", macFilter: "00:93:37"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
peers, err := manager.GetPeers(ctx, account.Id, "mac-admin", tt.nameFilter, tt.ipFilter, tt.macFilter)
require.NoError(t, err)
ids := make([]string, 0, len(peers))
for _, peer := range peers {
ids = append(ids, peer.ID)
}
assert.ElementsMatch(t, tt.wantIDs, ids, "filters should return only matching peers in the account")
query := url.Values{"name": {tt.nameFilter}, "ip": {tt.ipFilter}, "mac": {tt.macFilter}}
req := httptest.NewRequest(http.MethodGet, "/api/peers?"+query.Encode(), nil)
req = nbcontext.SetUserAuthInRequest(req, auth.UserAuth{AccountId: account.Id, UserId: "mac-admin"})
recorder := httptest.NewRecorder()
handler.GetAllPeers(recorder, req)
require.Equal(t, http.StatusOK, recorder.Code, "peer listing should succeed: %s", recorder.Body.String())
var response []api.PeerBatch
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &response))
responseIDs := make([]string, 0, len(response))
for _, peer := range response {
responseIDs = append(responseIDs, peer.Id)
}
assert.ElementsMatch(t, tt.wantIDs, responseIDs, "HTTP query filters should reach the store")
})
}
}
func setupTestAccountManager(b testing.TB, peers int, groups int) (*DefaultAccountManager, *update_channel.PeersUpdateManager, string, string, error) { func setupTestAccountManager(b testing.TB, peers int, groups int) (*DefaultAccountManager, *update_channel.PeersUpdateManager, string, string, error) {
b.Helper() b.Helper()
@@ -934,7 +1006,7 @@ func BenchmarkGetPeers(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err := manager.GetPeers(context.Background(), accountID, userID, "", "") _, err := manager.GetPeers(context.Background(), accountID, userID, "", "", "")
if err != nil { if err != nil {
b.Fatalf("GetPeers failed: %v", err) b.Fatalf("GetPeers failed: %v", err)
} }
+6 -1
View File
@@ -492,7 +492,7 @@ func (s *SqlStore) GetPeerByPeerPubKey(ctx context.Context, lockStrength Locking
} }
// GetAccountPeers retrieves peers for an account. // GetAccountPeers retrieves peers for an account.
func (s *SqlStore) GetAccountPeers(ctx context.Context, lockStrength LockingStrength, accountID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) { func (s *SqlStore) GetAccountPeers(ctx context.Context, lockStrength LockingStrength, accountID, nameFilter, ipFilter, macFilter string) ([]*nbpeer.Peer, error) {
var peers []*nbpeer.Peer var peers []*nbpeer.Peer
tx := s.db tx := s.db
if lockStrength != LockingStrengthNone { if lockStrength != LockingStrengthNone {
@@ -506,6 +506,11 @@ func (s *SqlStore) GetAccountPeers(ctx context.Context, lockStrength LockingStre
if ipFilter != "" { if ipFilter != "" {
query = query.Where("ip LIKE ? OR ipv6 LIKE ?", "%"+ipFilter+"%", "%"+ipFilter+"%") query = query.Where("ip LIKE ? OR ipv6 LIKE ?", "%"+ipFilter+"%", "%"+ipFilter+"%")
} }
// MAC addresses live in the JSON-serialized meta_network_addresses column,
// so we match the raw JSON text rather than a dedicated column.
if macFilter != "" {
query = query.Where("meta_network_addresses LIKE ?", "%"+macFilter+"%")
}
if err := query.Find(&peers).Error; err != nil { if err := query.Find(&peers).Error; err != nil {
log.WithContext(ctx).Errorf("failed to get peers from the store: %s", err) log.WithContext(ctx).Errorf("failed to get peers from the store: %s", err)
+44 -2
View File
@@ -512,7 +512,7 @@ func TestSqlStore_GetAccountPeers(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
peers, err := store.GetAccountPeers(context.Background(), LockingStrengthNone, tt.accountID, tt.nameFilter, tt.ipFilter) peers, err := store.GetAccountPeers(context.Background(), LockingStrengthNone, tt.accountID, tt.nameFilter, tt.ipFilter, "")
require.NoError(t, err) require.NoError(t, err)
require.Len(t, peers, tt.expectedCount) require.Len(t, peers, tt.expectedCount)
}) })
@@ -520,6 +520,48 @@ func TestSqlStore_GetAccountPeers(t *testing.T) {
} }
func TestSqlStore_GetAccountPeers_FilterByMac(t *testing.T) {
ctx := context.Background()
store, cleanup, err := NewTestStoreFromSQL(ctx, "", t.TempDir())
require.NoError(t, err)
t.Cleanup(cleanup)
accountID := "test-account-mac"
userID := "test-user-mac"
account := newAccountWithId(ctx, accountID, userID, "example.com")
account.Peers["peer-mac-1"] = &nbpeer.Peer{
ID: "peer-mac-1",
AccountID: accountID,
Key: "peer-mac-key-1",
Name: "macpeer",
IP: netip.MustParseAddr("100.64.0.10"),
Meta: nbpeer.PeerSystemMeta{
NetworkAddresses: []nbpeer.NetworkAddress{
{NetIP: netip.MustParsePrefix("192.168.0.11/24"), Mac: "00:93:37:bd:83:0f"},
},
},
}
require.NoError(t, store.SaveAccount(ctx, account))
tests := []struct {
name string
macFilter string
expectedCount int
}{
{name: "full mac matches", macFilter: "00:93:37:bd:83:0f", expectedCount: 1},
{name: "mac prefix matches", macFilter: "00:93:37", expectedCount: 1},
{name: "unknown mac does not match", macFilter: "11:22:33:44:55:66", expectedCount: 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
peers, err := store.GetAccountPeers(ctx, LockingStrengthNone, accountID, "", "", tt.macFilter)
require.NoError(t, err)
require.Len(t, peers, tt.expectedCount)
})
}
}
func TestSqlStore_GetAccountPeersWithExpiration(t *testing.T) { func TestSqlStore_GetAccountPeersWithExpiration(t *testing.T) {
store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/store_with_expired_peers.sql", t.TempDir()) store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/store_with_expired_peers.sql", t.TempDir())
t.Cleanup(cleanup) t.Cleanup(cleanup)
@@ -878,7 +920,7 @@ func TestSqlStore_ApproveAccountPeers(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 2, count) assert.Equal(t, 2, count)
allPeers, err := store.GetAccountPeers(ctx, LockingStrengthNone, accountID, "", "") allPeers, err := store.GetAccountPeers(ctx, LockingStrengthNone, accountID, "", "", "")
require.NoError(t, err) require.NoError(t, err)
for _, peer := range allPeers { for _, peer := range allPeers {
+1 -1
View File
@@ -160,7 +160,7 @@ type Store interface {
RemoveResourceFromGroup(ctx context.Context, accountId string, groupID string, resourceID string) error RemoveResourceFromGroup(ctx context.Context, accountId string, groupID string, resourceID string) error
AddPeerToAccount(ctx context.Context, peer *nbpeer.Peer) error AddPeerToAccount(ctx context.Context, peer *nbpeer.Peer) error
GetPeerByPeerPubKey(ctx context.Context, lockStrength LockingStrength, peerKey string) (*nbpeer.Peer, error) GetPeerByPeerPubKey(ctx context.Context, lockStrength LockingStrength, peerKey string) (*nbpeer.Peer, error)
GetAccountPeers(ctx context.Context, lockStrength LockingStrength, accountID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) GetAccountPeers(ctx context.Context, lockStrength LockingStrength, accountID, nameFilter, ipFilter, macFilter string) ([]*nbpeer.Peer, error)
GetUserPeers(ctx context.Context, lockStrength LockingStrength, accountID, userID string) ([]*nbpeer.Peer, error) GetUserPeers(ctx context.Context, lockStrength LockingStrength, accountID, userID string) ([]*nbpeer.Peer, error)
GetPeerByID(ctx context.Context, lockStrength LockingStrength, accountID string, peerID string) (*nbpeer.Peer, error) GetPeerByID(ctx context.Context, lockStrength LockingStrength, accountID string, peerID string) (*nbpeer.Peer, error)
GetPeersByIDs(ctx context.Context, lockStrength LockingStrength, accountID string, peerIDs []string) (map[string]*nbpeer.Peer, error) GetPeersByIDs(ctx context.Context, lockStrength LockingStrength, accountID string, peerIDs []string) (map[string]*nbpeer.Peer, error)
+4 -4
View File
@@ -1330,18 +1330,18 @@ func (mr *MockStoreMockRecorder) GetAccountOwner(ctx, lockStrength, accountID an
} }
// GetAccountPeers mocks base method. // GetAccountPeers mocks base method.
func (m *MockStore) GetAccountPeers(ctx context.Context, lockStrength LockingStrength, accountID, nameFilter, ipFilter string) ([]*peer.Peer, error) { func (m *MockStore) GetAccountPeers(ctx context.Context, lockStrength LockingStrength, accountID, nameFilter, ipFilter, macFilter string) ([]*peer.Peer, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccountPeers", ctx, lockStrength, accountID, nameFilter, ipFilter) ret := m.ctrl.Call(m, "GetAccountPeers", ctx, lockStrength, accountID, nameFilter, ipFilter, macFilter)
ret0, _ := ret[0].([]*peer.Peer) ret0, _ := ret[0].([]*peer.Peer)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetAccountPeers indicates an expected call of GetAccountPeers. // GetAccountPeers indicates an expected call of GetAccountPeers.
func (mr *MockStoreMockRecorder) GetAccountPeers(ctx, lockStrength, accountID, nameFilter, ipFilter any) *gomock.Call { func (mr *MockStoreMockRecorder) GetAccountPeers(ctx, lockStrength, accountID, nameFilter, ipFilter, macFilter any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountPeers", reflect.TypeOf((*MockStore)(nil).GetAccountPeers), ctx, lockStrength, accountID, nameFilter, ipFilter) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountPeers", reflect.TypeOf((*MockStore)(nil).GetAccountPeers), ctx, lockStrength, accountID, nameFilter, ipFilter, macFilter)
} }
// GetAccountPeersWithExpiration mocks base method. // GetAccountPeersWithExpiration mocks base method.
+24
View File
@@ -826,6 +826,20 @@ components:
- ssh_enabled - ssh_enabled
- login_expiration_enabled - login_expiration_enabled
- inactivity_expiration_enabled - inactivity_expiration_enabled
NetworkAddress:
type: object
properties:
net_ip:
description: IP address with CIDR of the interface
type: string
example: 192.168.0.11/24
mac:
description: MAC address of the interface
type: string
example: "00:93:37:bd:83:0f"
required:
- net_ip
- mac
Peer: Peer:
allOf: allOf:
- $ref: '#/components/schemas/PeerMinimum' - $ref: '#/components/schemas/PeerMinimum'
@@ -845,6 +859,11 @@ components:
type: string type: string
format: ipv6 format: ipv6
example: "fd00:4e42:ab12::1" example: "fd00:4e42:ab12::1"
network_addresses:
description: Network interfaces (IP + MAC) reported by the peer
type: array
items:
$ref: '#/components/schemas/NetworkAddress'
connection_ip: connection_ip:
description: Peer's public connection IP address description: Peer's public connection IP address
type: string type: string
@@ -7516,6 +7535,11 @@ paths:
schema: schema:
type: string type: string
description: Filter peers by IP address description: Filter peers by IP address
- in: query
name: mac
schema:
type: string
description: Filter peers by MAC address of a network interface
security: security:
- BearerAuth: [ ] - BearerAuth: [ ]
- TokenAuth: [ ] - TokenAuth: [ ]
+18
View File
@@ -3829,6 +3829,15 @@ type Network struct {
RoutingPeersCount int `json:"routing_peers_count"` RoutingPeersCount int `json:"routing_peers_count"`
} }
// NetworkAddress defines model for NetworkAddress.
type NetworkAddress struct {
// Mac MAC address of the interface
Mac string `json:"mac"`
// NetIp IP address with CIDR of the interface
NetIp string `json:"net_ip"`
}
// NetworkRequest defines model for NetworkRequest. // NetworkRequest defines model for NetworkRequest.
type NetworkRequest struct { type NetworkRequest struct {
// Description Network description // Description Network description
@@ -4278,6 +4287,9 @@ type Peer struct {
// Name Peer's hostname // Name Peer's hostname
Name string `json:"name"` Name string `json:"name"`
// NetworkAddresses Network interfaces (IP + MAC) reported by the peer
NetworkAddresses *[]NetworkAddress `json:"network_addresses,omitempty"`
// Os Peer's operating system and version // Os Peer's operating system and version
Os string `json:"os"` Os string `json:"os"`
@@ -4372,6 +4384,9 @@ type PeerBatch struct {
// Name Peer's hostname // Name Peer's hostname
Name string `json:"name"` Name string `json:"name"`
// NetworkAddresses Network interfaces (IP + MAC) reported by the peer
NetworkAddresses *[]NetworkAddress `json:"network_addresses,omitempty"`
// Os Peer's operating system and version // Os Peer's operating system and version
Os string `json:"os"` Os string `json:"os"`
@@ -6294,6 +6309,9 @@ type GetApiPeersParams struct {
// Ip Filter peers by IP address // Ip Filter peers by IP address
Ip *string `form:"ip,omitempty" json:"ip,omitempty"` Ip *string `form:"ip,omitempty" json:"ip,omitempty"`
// Mac Filter peers by MAC address of a network interface
Mac *string `form:"mac,omitempty" json:"mac,omitempty"`
} }
// GetApiPeersPeerIdIngressPortsParams defines parameters for GetApiPeersPeerIdIngressPorts. // GetApiPeersPeerIdIngressPortsParams defines parameters for GetApiPeersPeerIdIngressPorts.