Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[management] Refactor account retrieval to use store methods #2671

Draft
wants to merge 69 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
258b30c
refactor access control middleware and user access by JWT groups
bcmmbaga Sep 16, 2024
3cf1b02
refactor jwt groups extractor
bcmmbaga Sep 17, 2024
e5d55d3
refactor handlers to get account when necessary
bcmmbaga Sep 17, 2024
ccab3b4
refactor getAccountFromToken
bcmmbaga Sep 18, 2024
720d36a
refactor getAccountWithAuthorizationClaims
bcmmbaga Sep 18, 2024
a4c4158
Merge branch 'main' into refactor-get-account-by-token
bcmmbaga Sep 18, 2024
021fc8f
fix merge
bcmmbaga Sep 18, 2024
f60a423
revert handles change
bcmmbaga Sep 18, 2024
8f9c54f
remove GetUserByID from account manager
bcmmbaga Sep 18, 2024
9631cb4
fix tests
bcmmbaga Sep 18, 2024
4d9bb7e
refactor getAccountWithAuthorizationClaims to return account id
bcmmbaga Sep 20, 2024
26dd045
Merge branch 'main' into refactor-get-account-by-token
bcmmbaga Sep 20, 2024
8f98add
refactor handlers to use GetAccountIDFromToken
bcmmbaga Sep 22, 2024
7601a17
fix tests
bcmmbaga Sep 22, 2024
d9f612d
remove locks
bcmmbaga Sep 23, 2024
2884038
refactor
bcmmbaga Sep 24, 2024
1ffe89d
add GetGroupByName from store
bcmmbaga Sep 24, 2024
7561706
add GetGroupByID from store and refactor
bcmmbaga Sep 24, 2024
eab8564
Refactor retrieval of policy and posture checks
bcmmbaga Sep 24, 2024
d14b855
Refactor user permissions and retrieves PAT
bcmmbaga Sep 24, 2024
16174f0
Refactor route, setupkey, nameserver and dns to get record(s) from store
bcmmbaga Sep 25, 2024
41b212f
Refactor store
bcmmbaga Sep 25, 2024
b815393
fix lint
bcmmbaga Sep 25, 2024
c384874
fix tests
bcmmbaga Sep 25, 2024
87c8430
add store policy save and method
bcmmbaga Sep 25, 2024
3b4bcdf
refactor posture checks save and deletion
bcmmbaga Sep 26, 2024
dc82c2d
fix add missing policy source posture checks
bcmmbaga Sep 26, 2024
30253b0
Merge branch 'refactor-get-account-by-token' into refactor/get-accoun…
bcmmbaga Sep 26, 2024
871595d
Merge branch 'main' into refactor-get-account-by-token
bcmmbaga Sep 26, 2024
ca6a9fd
Merge branch 'refactor-get-account-by-token' into refactor/get-accoun…
bcmmbaga Sep 26, 2024
4575ae2
add store lock
bcmmbaga Sep 26, 2024
f61c914
Merge branch 'refactor-get-account-by-token' into refactor/get-accoun…
bcmmbaga Sep 26, 2024
73be8c8
fix merge
bcmmbaga Sep 26, 2024
96f18c2
fix tests
bcmmbaga Sep 26, 2024
b1b2b0a
fix tests
bcmmbaga Sep 26, 2024
d87fe02
Merge branch 'refactor-get-account-by-token' into refactor/get-accoun…
bcmmbaga Sep 26, 2024
bc52041
Merge branch 'main' into refactor/get-account-usage
bcmmbaga Sep 27, 2024
edf6767
fix merge
bcmmbaga Sep 27, 2024
9e47c94
refactor setup keys
bcmmbaga Sep 30, 2024
43eb726
refactor account and dns settings
bcmmbaga Sep 30, 2024
d36d30d
refactor name server groups
bcmmbaga Sep 30, 2024
1a37b12
refactor user PAT
bcmmbaga Sep 30, 2024
f43a006
Fix posture check name uniqueness per account
bcmmbaga Sep 30, 2024
f9ed25f
wip refactor peer methods
bcmmbaga Sep 30, 2024
78e2386
refactor groups methods
bcmmbaga Oct 1, 2024
0297b5f
wip: refactoring
bcmmbaga Oct 2, 2024
a8c8b77
Merge branch 'main' into refactor/get-account-usage
bcmmbaga Oct 14, 2024
1123729
fix merge
bcmmbaga Oct 15, 2024
d7c63d5
Remove get account from groups ops
bcmmbaga Oct 16, 2024
d7a6996
check user accounts for setup keys
bcmmbaga Oct 17, 2024
b66f331
get the first element when get record by ID
bcmmbaga Oct 17, 2024
408d0cd
Refactor policy save and delete
bcmmbaga Oct 17, 2024
b0edc5f
Merge branch 'main' into refactor/get-account-usage
bcmmbaga Oct 17, 2024
ee96a81
fix handler tests
bcmmbaga Oct 17, 2024
83be99c
refactor get peers posture checks
bcmmbaga Oct 17, 2024
a82b5ce
Merge branch 'main' into refactor/get-account-usage
bcmmbaga Oct 17, 2024
97dbdd7
fix group tests
bcmmbaga Oct 18, 2024
0bdcb41
Refactor peer expiry, inactivity, location and status update to remov…
bcmmbaga Oct 23, 2024
313e158
Refactor route
bcmmbaga Oct 25, 2024
9bf0bf4
wip: refactor get account in peers
bcmmbaga Oct 28, 2024
7278a21
refactor get account in peers
bcmmbaga Oct 29, 2024
901d283
Merge branch 'main' into refactor-get-account-by-token
bcmmbaga Oct 30, 2024
b7525d9
Merge branch 'main' into refactor/get-account-usage
bcmmbaga Oct 30, 2024
6b94f6e
Refactor ephemeral peers and mark PAT as used
bcmmbaga Oct 31, 2024
8cacdae
Merge branch 'main' into refactor/get-account-usage
bcmmbaga Oct 31, 2024
e73b5da
Refactor update account peers
bcmmbaga Oct 31, 2024
fed48de
Refactor auth middleware
bcmmbaga Nov 1, 2024
fa5db7d
Refactor service user handling, user cache lookup, and cache loading
bcmmbaga Nov 1, 2024
74246d1
Merge branch 'main' into refactor/get-account-usage
bcmmbaga Nov 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions client/internal/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import (
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
"github.com/netbirdio/netbird/client/internal/statemanager"


nbssh "github.com/netbirdio/netbird/client/ssh"
"github.com/netbirdio/netbird/client/system"
nbdns "github.com/netbirdio/netbird/dns"
Expand Down Expand Up @@ -171,7 +170,7 @@ type Engine struct {

relayManager *relayClient.Manager
stateManager *statemanager.Manager
srWatcher *guard.SRWatcher
srWatcher *guard.SRWatcher
}

// Peer is an instance of the Connection Peer
Expand Down
371 changes: 160 additions & 211 deletions management/server/account.go

Large diffs are not rendered by default.

44 changes: 19 additions & 25 deletions management/server/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"time"

"github.com/golang-jwt/jwt"
"github.com/rs/xid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
Expand Down Expand Up @@ -767,7 +768,7 @@
Store: store,
}

account, user, pat, err := am.GetAccountFromPAT(context.Background(), token)

Check failure on line 771 in management/server/account_test.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

am.GetAccountFromPAT undefined (type DefaultAccountManager has no field or method GetAccountFromPAT)

Check failure on line 771 in management/server/account_test.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

am.GetAccountFromPAT undefined (type DefaultAccountManager has no field or method GetAccountFromPAT)

Check failure on line 771 in management/server/account_test.go

View workflow job for this annotation

GitHub Actions / test (sqlite)

am.GetAccountFromPAT undefined (type DefaultAccountManager has no field or method GetAccountFromPAT)

Check failure on line 771 in management/server/account_test.go

View workflow job for this annotation

GitHub Actions / test (386, sqlite)

am.GetAccountFromPAT undefined (type DefaultAccountManager has no field or method GetAccountFromPAT)

Check failure on line 771 in management/server/account_test.go

View workflow job for this annotation

GitHub Actions / test (386, postgres)

am.GetAccountFromPAT undefined (type DefaultAccountManager has no field or method GetAccountFromPAT)

Check failure on line 771 in management/server/account_test.go

View workflow job for this annotation

GitHub Actions / test

am.GetAccountFromPAT undefined (type DefaultAccountManager has no field or method GetAccountFromPAT)
if err != nil {
t.Fatalf("Error when getting Account from PAT: %s", err)
}
Expand Down Expand Up @@ -1131,10 +1132,12 @@
}

policy := Policy{
ID: "policy",
Enabled: true,
ID: xid.New().String(),
AccountID: account.Id,
Enabled: true,
Rules: []*PolicyRule{
{
ID: xid.New().String(),
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupA"},
Expand Down Expand Up @@ -1748,13 +1751,10 @@
accountID, err := manager.GetAccountIDByUserID(context.Background(), userID, "")
require.NoError(t, err, "unable to get the account")

account, err := manager.Store.GetAccount(context.Background(), accountID)
require.NoError(t, err, "unable to get the account")

err = manager.MarkPeerConnected(context.Background(), key.PublicKey().String(), true, nil, account)
err = manager.MarkPeerConnected(context.Background(), key.PublicKey().String(), true, nil, accountID)
require.NoError(t, err, "unable to mark peer connected")

account, err = manager.UpdateAccountSettings(context.Background(), accountID, userID, &Settings{
_, err = manager.UpdateAccountSettings(context.Background(), accountID, userID, &Settings{
PeerLoginExpiration: time.Hour,
PeerLoginExpirationEnabled: true,
})
Expand All @@ -1774,11 +1774,11 @@
// disable expiration first
update := peer.Copy()
update.LoginExpirationEnabled = false
_, err = manager.UpdatePeer(context.Background(), account.Id, userID, update)
_, err = manager.UpdatePeer(context.Background(), accountID, userID, update)
require.NoError(t, err, "unable to update peer")
// enabling expiration should trigger the routine
update.LoginExpirationEnabled = true
_, err = manager.UpdatePeer(context.Background(), account.Id, userID, update)
_, err = manager.UpdatePeer(context.Background(), accountID, userID, update)
require.NoError(t, err, "unable to update peer")

failed := waitTimeout(wg, time.Second)
Expand Down Expand Up @@ -1822,11 +1822,8 @@
accountID, err = manager.GetAccountIDByUserID(context.Background(), userID, "")
require.NoError(t, err, "unable to get the account")

account, err := manager.Store.GetAccount(context.Background(), accountID)
require.NoError(t, err, "unable to get the account")

// when we mark peer as connected, the peer login expiration routine should trigger
err = manager.MarkPeerConnected(context.Background(), key.PublicKey().String(), true, nil, account)
err = manager.MarkPeerConnected(context.Background(), key.PublicKey().String(), true, nil, accountID)
require.NoError(t, err, "unable to mark peer connected")

failed := waitTimeout(wg, time.Second)
Expand Down Expand Up @@ -1854,10 +1851,7 @@
accountID, err := manager.GetAccountIDByUserID(context.Background(), userID, "")
require.NoError(t, err, "unable to get the account")

account, err := manager.Store.GetAccount(context.Background(), accountID)
require.NoError(t, err, "unable to get the account")

err = manager.MarkPeerConnected(context.Background(), key.PublicKey().String(), true, nil, account)
err = manager.MarkPeerConnected(context.Background(), key.PublicKey().String(), true, nil, accountID)
require.NoError(t, err, "unable to mark peer connected")

wg := &sync.WaitGroup{}
Expand All @@ -1871,7 +1865,7 @@
},
}
// enabling PeerLoginExpirationEnabled should trigger the expiration job
account, err = manager.UpdateAccountSettings(context.Background(), account.Id, userID, &Settings{
_, err = manager.UpdateAccountSettings(context.Background(), accountID, userID, &Settings{
PeerLoginExpiration: time.Hour,
PeerLoginExpirationEnabled: true,
})
Expand All @@ -1884,7 +1878,7 @@
wg.Add(1)

// disabling PeerLoginExpirationEnabled should trigger cancel
_, err = manager.UpdateAccountSettings(context.Background(), account.Id, userID, &Settings{
_, err = manager.UpdateAccountSettings(context.Background(), accountID, userID, &Settings{
PeerLoginExpiration: time.Hour,
PeerLoginExpirationEnabled: false,
})
Expand All @@ -1902,13 +1896,13 @@
accountID, err := manager.GetAccountIDByUserID(context.Background(), userID, "")
require.NoError(t, err, "unable to create an account")

updated, err := manager.UpdateAccountSettings(context.Background(), accountID, userID, &Settings{
updatedSettings, err := manager.UpdateAccountSettings(context.Background(), accountID, userID, &Settings{
PeerLoginExpiration: time.Hour,
PeerLoginExpirationEnabled: false,
})
require.NoError(t, err, "expecting to update account settings successfully but got error")
assert.False(t, updated.Settings.PeerLoginExpirationEnabled)
assert.Equal(t, updated.Settings.PeerLoginExpiration, time.Hour)
assert.False(t, updatedSettings.PeerLoginExpirationEnabled)
assert.Equal(t, updatedSettings.PeerLoginExpiration, time.Hour)

settings, err := manager.Store.GetAccountSettings(context.Background(), LockingStrengthShare, accountID)
require.NoError(t, err, "unable to get account settings")
Expand Down Expand Up @@ -2606,7 +2600,7 @@
assert.NoError(t, err, "unable to get user")
assert.Len(t, user.AutoGroups, 0)

group1, err := manager.Store.GetGroupByID(context.Background(), LockingStrengthShare, "group1", "accountID")
group1, err := manager.Store.GetGroupByID(context.Background(), LockingStrengthShare, "accountID", "group1")
assert.NoError(t, err, "unable to get group")
assert.Equal(t, group1.Issued, group.GroupIssuedAPI, "group should be api issued")
})
Expand All @@ -2626,7 +2620,7 @@
assert.NoError(t, err, "unable to get user")
assert.Len(t, user.AutoGroups, 1)

group1, err := manager.Store.GetGroupByID(context.Background(), LockingStrengthShare, "group1", "accountID")
group1, err := manager.Store.GetGroupByID(context.Background(), LockingStrengthShare, "accountID", "group1")
assert.NoError(t, err, "unable to get group")
assert.Equal(t, group1.Issued, group.GroupIssuedAPI, "group should be api issued")
})
Expand Down Expand Up @@ -2665,7 +2659,7 @@
err = manager.syncJWTGroups(context.Background(), "accountID", claims)
assert.NoError(t, err, "unable to sync jwt groups")

groups, err := manager.Store.GetAccountGroups(context.Background(), "accountID")
groups, err := manager.Store.GetAccountGroups(context.Background(), LockingStrengthShare, "accountID")
assert.NoError(t, err)
assert.Len(t, groups, 3, "new group3 should be added")

Expand Down
96 changes: 71 additions & 25 deletions management/server/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strconv"
"sync"

nbgroup "github.com/netbirdio/netbird/management/server/group"
log "github.com/sirupsen/logrus"

nbdns "github.com/netbirdio/netbird/dns"
Expand Down Expand Up @@ -85,73 +86,118 @@ func (am *DefaultAccountManager) GetDNSSettings(ctx context.Context, accountID s
return nil, err
}

if !user.IsAdminOrServiceUser() || user.AccountID != accountID {
return nil, status.Errorf(status.PermissionDenied, "only users with admin power are allowed to view DNS settings")
if user.AccountID != accountID {
return nil, status.NewUserNotPartOfAccountError()
}

if user.IsRegularUser() {
return nil, status.NewUnauthorizedToViewDNSSettingsError()
}

return am.Store.GetAccountDNSSettings(ctx, LockingStrengthShare, accountID)
}

// SaveDNSSettings validates a user role and updates the account's DNS settings
func (am *DefaultAccountManager) SaveDNSSettings(ctx context.Context, accountID string, userID string, dnsSettingsToSave *DNSSettings) error {
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
if dnsSettingsToSave == nil {
return status.Errorf(status.InvalidArgument, "the dns settings provided are nil")
}

account, err := am.Store.GetAccount(ctx, accountID)
user, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, userID)
if err != nil {
return err
}

user, err := account.FindUser(userID)
if err != nil {
return err
if user.AccountID != accountID {
return status.NewUserNotPartOfAccountError()
}

if !user.HasAdminPower() {
return status.Errorf(status.PermissionDenied, "only users with admin power are allowed to update DNS settings")
return status.NewUnauthorizedToViewDNSSettingsError()
}

if dnsSettingsToSave == nil {
return status.Errorf(status.InvalidArgument, "the dns settings provided are nil")
oldSettings, err := am.Store.GetAccountDNSSettings(ctx, LockingStrengthUpdate, accountID)
if err != nil {
return err
}

groups, err := am.Store.GetAccountGroups(ctx, LockingStrengthShare, accountID)
if err != nil {
return err
}

if len(dnsSettingsToSave.DisabledManagementGroups) != 0 {
err = validateGroups(dnsSettingsToSave.DisabledManagementGroups, account.Groups)
err = validateGroups(dnsSettingsToSave.DisabledManagementGroups, groups)
if err != nil {
return err
}
}

oldSettings := account.DNSSettings.Copy()
account.DNSSettings = dnsSettingsToSave.Copy()

addedGroups := difference(dnsSettingsToSave.DisabledManagementGroups, oldSettings.DisabledManagementGroups)
removedGroups := difference(oldSettings.DisabledManagementGroups, dnsSettingsToSave.DisabledManagementGroups)

account.Network.IncSerial()
if err = am.Store.SaveAccount(ctx, account); err != nil {
updateAccountPeers, err := am.areDNSSettingChangesAffectPeers(ctx, accountID, addedGroups, removedGroups)
if err != nil {
return fmt.Errorf("failed to check if dns settings changes affect peers: %w", err)
}

err = am.Store.ExecuteInTransaction(ctx, func(transaction Store) error {
if err = transaction.IncrementNetworkSerial(ctx, LockingStrengthUpdate, accountID); err != nil {
return fmt.Errorf("failed to increment network serial: %w", err)
}

if err = transaction.SaveDNSSettings(ctx, LockingStrengthUpdate, accountID, dnsSettingsToSave); err != nil {
return fmt.Errorf("failed to update dns settings: %w", err)
}

return nil
})
if err != nil {
return err
}

groupMap := make(map[string]*nbgroup.Group, len(groups))
for _, g := range groups {
groupMap[g.ID] = g
}

for _, id := range addedGroups {
group := account.GetGroup(id)
meta := map[string]any{"group": group.Name, "group_id": group.ID}
am.StoreEvent(ctx, userID, accountID, accountID, activity.GroupAddedToDisabledManagementGroups, meta)
group, ok := groupMap[id]
if ok {
meta := map[string]any{"group": group.Name, "group_id": group.ID}
am.StoreEvent(ctx, userID, accountID, accountID, activity.GroupAddedToDisabledManagementGroups, meta)
}
}

for _, id := range removedGroups {
group := account.GetGroup(id)
meta := map[string]any{"group": group.Name, "group_id": group.ID}
am.StoreEvent(ctx, userID, accountID, accountID, activity.GroupRemovedFromDisabledManagementGroups, meta)
group, ok := groupMap[id]
if ok {
meta := map[string]any{"group": group.Name, "group_id": group.ID}
am.StoreEvent(ctx, userID, accountID, accountID, activity.GroupRemovedFromDisabledManagementGroups, meta)
}
}

if anyGroupHasPeers(account, addedGroups) || anyGroupHasPeers(account, removedGroups) {
am.updateAccountPeers(ctx, account)
if updateAccountPeers {
am.updateAccountPeers(ctx, accountID)
}

return nil
}

// areDNSSettingChangesAffectPeers checks if the DNS settings changes affect any peers.
func (am *DefaultAccountManager) areDNSSettingChangesAffectPeers(ctx context.Context, accountID string, addedGroups, removedGroups []string) (bool, error) {
hasPeers, err := am.anyGroupHasPeers(ctx, accountID, addedGroups)
if err != nil {
return false, err
}

if hasPeers {
return true, nil
}

return am.anyGroupHasPeers(ctx, accountID, removedGroups)
}

// toProtocolDNSConfig converts nbdns.Config to proto.DNSConfig using the cache
func toProtocolDNSConfig(update nbdns.Config, cache *DNSConfigCache) *proto.DNSConfig {
protoUpdate := &proto.DNSConfig{
Expand Down
44 changes: 21 additions & 23 deletions management/server/ephemeral.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ var (
)

type ephemeralPeer struct {
id string
account *Account
deadline time.Time
next *ephemeralPeer
id string
accountID string
deadline time.Time
next *ephemeralPeer
}

// todo: consider to remove peer from ephemeral list when the peer has been deleted via API. If we do not do it
Expand Down Expand Up @@ -104,20 +104,14 @@ func (e *EphemeralManager) OnPeerDisconnected(ctx context.Context, peer *nbpeer.

log.WithContext(ctx).Tracef("add peer to ephemeral list: %s", peer.ID)

a, err := e.store.GetAccountByPeerID(context.Background(), peer.ID)
if err != nil {
log.WithContext(ctx).Errorf("failed to add peer to ephemeral list: %s", err)
return
}

e.peersLock.Lock()
defer e.peersLock.Unlock()

if e.isPeerOnList(peer.ID) {
return
}

e.addPeer(peer.ID, a, newDeadLine())
e.addPeer(peer.AccountID, peer.ID, newDeadLine())
if e.timer == nil {
e.timer = time.AfterFunc(e.headPeer.deadline.Sub(timeNow()), func() {
e.cleanup(ctx)
Expand All @@ -126,17 +120,21 @@ func (e *EphemeralManager) OnPeerDisconnected(ctx context.Context, peer *nbpeer.
}

func (e *EphemeralManager) loadEphemeralPeers(ctx context.Context) {
accounts := e.store.GetAllAccounts(context.Background())
peers, err := e.store.GetAllEphemeralPeers(ctx, LockingStrengthShare)
if err != nil {
log.WithContext(ctx).Debugf("failed to load ephemeral peers: %s", err)
return
}

t := newDeadLine()
count := 0
for _, a := range accounts {
for id, p := range a.Peers {
if p.Ephemeral {
count++
e.addPeer(id, a, t)
}
for _, p := range peers {
if p.Ephemeral {
count++
e.addPeer(p.AccountID, p.ID, t)
}
}

log.WithContext(ctx).Debugf("loaded ephemeral peer(s): %d", count)
}

Expand Down Expand Up @@ -170,18 +168,18 @@ func (e *EphemeralManager) cleanup(ctx context.Context) {

for id, p := range deletePeers {
log.WithContext(ctx).Debugf("delete ephemeral peer: %s", id)
err := e.accountManager.DeletePeer(ctx, p.account.Id, id, activity.SystemInitiator)
err := e.accountManager.DeletePeer(ctx, p.accountID, id, activity.SystemInitiator)
if err != nil {
log.WithContext(ctx).Errorf("failed to delete ephemeral peer: %s", err)
}
}
}

func (e *EphemeralManager) addPeer(id string, account *Account, deadline time.Time) {
func (e *EphemeralManager) addPeer(accountID string, peerID string, deadline time.Time) {
ep := &ephemeralPeer{
id: id,
account: account,
deadline: deadline,
id: peerID,
accountID: accountID,
deadline: deadline,
}

if e.headPeer == nil {
Expand Down
Loading
Loading