2024-02-02 18:45:32 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
package ipnlocal
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2024-03-08 16:43:32 +00:00
|
|
|
"os"
|
|
|
|
"slices"
|
2024-02-02 18:45:32 +00:00
|
|
|
"strings"
|
|
|
|
|
2024-04-02 20:32:30 +00:00
|
|
|
"tailscale.com/drive"
|
2024-02-02 18:45:32 +00:00
|
|
|
"tailscale.com/ipn"
|
|
|
|
"tailscale.com/tailcfg"
|
|
|
|
"tailscale.com/types/netmap"
|
2024-03-07 16:56:11 +00:00
|
|
|
"tailscale.com/types/views"
|
2024-02-02 18:45:32 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2024-04-03 17:09:58 +00:00
|
|
|
// DriveLocalPort is the port on which the Taildrive listens for location
|
2024-02-02 18:45:32 +00:00
|
|
|
// connections on quad 100.
|
2024-04-03 17:09:58 +00:00
|
|
|
DriveLocalPort = 8080
|
2024-02-02 18:45:32 +00:00
|
|
|
)
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
// DriveSharingEnabled reports whether sharing to remote nodes via Taildrive is
|
2024-04-04 20:07:58 +00:00
|
|
|
// enabled. This is currently based on checking for the drive:share node
|
2024-02-02 18:45:32 +00:00
|
|
|
// attribute.
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) DriveSharingEnabled() bool {
|
2024-02-02 18:45:32 +00:00
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2024-04-03 17:09:58 +00:00
|
|
|
return b.driveSharingEnabledLocked()
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) driveSharingEnabledLocked() bool {
|
2024-04-04 20:07:58 +00:00
|
|
|
return b.netMap != nil && b.netMap.SelfNode.HasCap(tailcfg.NodeAttrsTaildriveShare)
|
2024-02-09 22:23:42 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
// DriveAccessEnabled reports whether accessing Taildrive shares on remote nodes
|
2024-04-04 20:07:58 +00:00
|
|
|
// is enabled. This is currently based on checking for the drive:access node
|
2024-02-09 22:23:42 +00:00
|
|
|
// attribute.
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) DriveAccessEnabled() bool {
|
2024-02-09 22:23:42 +00:00
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2024-04-03 17:09:58 +00:00
|
|
|
return b.driveAccessEnabledLocked()
|
2024-02-09 22:23:42 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) driveAccessEnabledLocked() bool {
|
2024-04-04 20:07:58 +00:00
|
|
|
return b.netMap != nil && b.netMap.SelfNode.HasCap(tailcfg.NodeAttrsTaildriveAccess)
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
// DriveSetServerAddr tells Taildrive to use the given address for connecting
|
|
|
|
// to the drive.FileServer that's exposing local files as an unprivileged
|
2024-02-02 18:45:32 +00:00
|
|
|
// user.
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) DriveSetServerAddr(addr string) error {
|
|
|
|
fs, ok := b.sys.DriveForRemote.GetOK()
|
2024-02-09 17:26:43 +00:00
|
|
|
if !ok {
|
2024-04-05 18:43:13 +00:00
|
|
|
return drive.ErrDriveNotEnabled
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fs.SetFileServerAddr(addr)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
// DriveSetShare adds the given share if no share with that name exists, or
|
2024-03-08 16:43:32 +00:00
|
|
|
// replaces the existing share if one with the same name already exists. To
|
|
|
|
// avoid potential incompatibilities across file systems, share names are
|
2024-02-02 18:45:32 +00:00
|
|
|
// limited to alphanumeric characters and the underscore _.
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) DriveSetShare(share *drive.Share) error {
|
2024-02-02 18:45:32 +00:00
|
|
|
var err error
|
2024-04-05 18:43:13 +00:00
|
|
|
share.Name, err = drive.NormalizeShareName(share.Name)
|
2024-02-02 18:45:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
b.mu.Lock()
|
2024-04-03 17:09:58 +00:00
|
|
|
shares, err := b.driveSetShareLocked(share)
|
2024-02-02 18:45:32 +00:00
|
|
|
b.mu.Unlock()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
b.driveNotifyShares(shares)
|
2024-02-02 18:45:32 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) driveSetShareLocked(share *drive.Share) (views.SliceView[*drive.Share, drive.ShareView], error) {
|
|
|
|
existingShares := b.pm.prefs.DriveShares()
|
2024-03-07 16:56:11 +00:00
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
fs, ok := b.sys.DriveForRemote.GetOK()
|
2024-02-09 17:26:43 +00:00
|
|
|
if !ok {
|
2024-04-05 18:43:13 +00:00
|
|
|
return existingShares, drive.ErrDriveNotEnabled
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2024-03-07 16:56:11 +00:00
|
|
|
addedShare := false
|
2024-04-02 20:32:30 +00:00
|
|
|
var shares []*drive.Share
|
2024-03-07 16:56:11 +00:00
|
|
|
for i := 0; i < existingShares.Len(); i++ {
|
|
|
|
existing := existingShares.At(i)
|
|
|
|
if existing.Name() != share.Name {
|
|
|
|
if !addedShare && existing.Name() > share.Name {
|
|
|
|
// Add share in order
|
|
|
|
shares = append(shares, share)
|
|
|
|
addedShare = true
|
|
|
|
}
|
|
|
|
shares = append(shares, existing.AsStruct())
|
|
|
|
}
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
2024-03-07 16:56:11 +00:00
|
|
|
if !addedShare {
|
|
|
|
shares = append(shares, share)
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
2024-03-07 16:56:11 +00:00
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
err := b.driveSetSharesLocked(shares)
|
2024-02-02 18:45:32 +00:00
|
|
|
if err != nil {
|
2024-03-07 16:56:11 +00:00
|
|
|
return existingShares, err
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
2024-02-09 17:26:43 +00:00
|
|
|
fs.SetShares(shares)
|
2024-02-02 18:45:32 +00:00
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
return b.pm.prefs.DriveShares(), nil
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
// DriveRenameShare renames the share at old name to new name. To avoid
|
2024-03-08 16:43:32 +00:00
|
|
|
// potential incompatibilities across file systems, the new share name is
|
|
|
|
// limited to alphanumeric characters and the underscore _.
|
|
|
|
// Any of the following will result in an error.
|
|
|
|
// - no share found under old name
|
|
|
|
// - new share name contains disallowed characters
|
|
|
|
// - share already exists under new name
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) DriveRenameShare(oldName, newName string) error {
|
2024-03-08 16:43:32 +00:00
|
|
|
var err error
|
2024-04-05 18:43:13 +00:00
|
|
|
newName, err = drive.NormalizeShareName(newName)
|
2024-03-08 16:43:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
b.mu.Lock()
|
2024-04-03 17:09:58 +00:00
|
|
|
shares, err := b.driveRenameShareLocked(oldName, newName)
|
2024-03-08 16:43:32 +00:00
|
|
|
b.mu.Unlock()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
b.driveNotifyShares(shares)
|
2024-03-08 16:43:32 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) driveRenameShareLocked(oldName, newName string) (views.SliceView[*drive.Share, drive.ShareView], error) {
|
|
|
|
existingShares := b.pm.prefs.DriveShares()
|
2024-03-08 16:43:32 +00:00
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
fs, ok := b.sys.DriveForRemote.GetOK()
|
2024-03-08 16:43:32 +00:00
|
|
|
if !ok {
|
2024-04-05 18:43:13 +00:00
|
|
|
return existingShares, drive.ErrDriveNotEnabled
|
2024-03-08 16:43:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
found := false
|
2024-04-02 20:32:30 +00:00
|
|
|
var shares []*drive.Share
|
2024-03-08 16:43:32 +00:00
|
|
|
for i := 0; i < existingShares.Len(); i++ {
|
|
|
|
existing := existingShares.At(i)
|
|
|
|
if existing.Name() == newName {
|
|
|
|
return existingShares, os.ErrExist
|
|
|
|
}
|
|
|
|
if existing.Name() == oldName {
|
|
|
|
share := existing.AsStruct()
|
|
|
|
share.Name = newName
|
|
|
|
shares = append(shares, share)
|
|
|
|
found = true
|
|
|
|
} else {
|
|
|
|
shares = append(shares, existing.AsStruct())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
return existingShares, os.ErrNotExist
|
|
|
|
}
|
|
|
|
|
2024-04-02 20:32:30 +00:00
|
|
|
slices.SortFunc(shares, drive.CompareShares)
|
2024-04-03 17:09:58 +00:00
|
|
|
err := b.driveSetSharesLocked(shares)
|
2024-03-08 16:43:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return existingShares, err
|
|
|
|
}
|
|
|
|
fs.SetShares(shares)
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
return b.pm.prefs.DriveShares(), nil
|
2024-03-08 16:43:32 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
// DriveRemoveShare removes the named share. Share names are forced to
|
2024-02-02 18:45:32 +00:00
|
|
|
// lowercase.
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) DriveRemoveShare(name string) error {
|
2024-02-02 18:45:32 +00:00
|
|
|
// Force all share names to lowercase to avoid potential incompatibilities
|
|
|
|
// with clients that don't support case-sensitive filenames.
|
2024-02-09 17:26:43 +00:00
|
|
|
var err error
|
2024-04-05 18:43:13 +00:00
|
|
|
name, err = drive.NormalizeShareName(name)
|
2024-02-09 17:26:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-02-02 18:45:32 +00:00
|
|
|
|
|
|
|
b.mu.Lock()
|
2024-04-03 17:09:58 +00:00
|
|
|
shares, err := b.driveRemoveShareLocked(name)
|
2024-02-02 18:45:32 +00:00
|
|
|
b.mu.Unlock()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
b.driveNotifyShares(shares)
|
2024-02-02 18:45:32 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) driveRemoveShareLocked(name string) (views.SliceView[*drive.Share, drive.ShareView], error) {
|
|
|
|
existingShares := b.pm.prefs.DriveShares()
|
2024-03-07 16:56:11 +00:00
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
fs, ok := b.sys.DriveForRemote.GetOK()
|
2024-02-09 17:26:43 +00:00
|
|
|
if !ok {
|
2024-04-05 18:43:13 +00:00
|
|
|
return existingShares, drive.ErrDriveNotEnabled
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2024-03-08 16:43:32 +00:00
|
|
|
found := false
|
2024-04-02 20:32:30 +00:00
|
|
|
var shares []*drive.Share
|
2024-03-07 16:56:11 +00:00
|
|
|
for i := 0; i < existingShares.Len(); i++ {
|
|
|
|
existing := existingShares.At(i)
|
|
|
|
if existing.Name() != name {
|
|
|
|
shares = append(shares, existing.AsStruct())
|
2024-03-08 16:43:32 +00:00
|
|
|
} else {
|
|
|
|
found = true
|
2024-03-07 16:56:11 +00:00
|
|
|
}
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
2024-03-07 16:56:11 +00:00
|
|
|
|
2024-03-08 16:43:32 +00:00
|
|
|
if !found {
|
|
|
|
return existingShares, os.ErrNotExist
|
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
err := b.driveSetSharesLocked(shares)
|
2024-02-02 18:45:32 +00:00
|
|
|
if err != nil {
|
2024-03-07 16:56:11 +00:00
|
|
|
return existingShares, err
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
2024-02-09 17:26:43 +00:00
|
|
|
fs.SetShares(shares)
|
2024-02-02 18:45:32 +00:00
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
return b.pm.prefs.DriveShares(), nil
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) driveSetSharesLocked(shares []*drive.Share) error {
|
2024-03-07 16:56:11 +00:00
|
|
|
prefs := b.pm.prefs.AsStruct()
|
|
|
|
prefs.ApplyEdits(&ipn.MaskedPrefs{
|
|
|
|
Prefs: ipn.Prefs{
|
2024-04-03 17:09:58 +00:00
|
|
|
DriveShares: shares,
|
2024-03-07 16:56:11 +00:00
|
|
|
},
|
2024-04-03 17:09:58 +00:00
|
|
|
DriveSharesSet: true,
|
2024-03-07 16:56:11 +00:00
|
|
|
})
|
|
|
|
return b.pm.setPrefsLocked(prefs.View())
|
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
// driveNotifyShares notifies IPN bus listeners (e.g. Mac Application process)
|
2024-03-07 16:56:11 +00:00
|
|
|
// about the latest list of shares.
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) driveNotifyShares(shares views.SliceView[*drive.Share, drive.ShareView]) {
|
2024-03-20 15:15:05 +00:00
|
|
|
// Ensures shares is not nil to distinguish "no shares" from "not notifying shares"
|
|
|
|
if shares.IsNil() {
|
2024-04-02 20:32:30 +00:00
|
|
|
shares = views.SliceOfViews(make([]*drive.Share, 0))
|
2024-03-20 15:15:05 +00:00
|
|
|
}
|
2024-04-03 17:09:58 +00:00
|
|
|
b.send(ipn.Notify{DriveShares: shares})
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
// driveNotifyCurrentSharesLocked sends an ipn.Notify if the current set of
|
2024-03-07 16:56:11 +00:00
|
|
|
// shares has changed since the last notification.
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) driveNotifyCurrentSharesLocked() {
|
2024-04-02 20:32:30 +00:00
|
|
|
var shares views.SliceView[*drive.Share, drive.ShareView]
|
2024-04-03 17:09:58 +00:00
|
|
|
if b.driveSharingEnabledLocked() {
|
2024-03-07 16:56:11 +00:00
|
|
|
// Only populate shares if sharing is enabled.
|
2024-04-03 17:09:58 +00:00
|
|
|
shares = b.pm.prefs.DriveShares()
|
2024-03-07 16:56:11 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
lastNotified := b.lastNotifiedDriveShares.Load()
|
|
|
|
if lastNotified == nil || !driveShareViewsEqual(lastNotified, shares) {
|
2024-02-29 19:49:45 +00:00
|
|
|
// Do the below on a goroutine to avoid deadlocking on b.mu in b.send().
|
2024-04-03 17:09:58 +00:00
|
|
|
go b.driveNotifyShares(shares)
|
2024-03-07 16:56:11 +00:00
|
|
|
}
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
func driveShareViewsEqual(a *views.SliceView[*drive.Share, drive.ShareView], b views.SliceView[*drive.Share, drive.ShareView]) bool {
|
2024-03-07 16:56:11 +00:00
|
|
|
if a == nil {
|
|
|
|
return false
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2024-03-07 16:56:11 +00:00
|
|
|
if a.Len() != b.Len() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < a.Len(); i++ {
|
2024-04-02 20:32:30 +00:00
|
|
|
if !drive.ShareViewsEqual(a.At(i), b.At(i)) {
|
2024-03-07 16:56:11 +00:00
|
|
|
return false
|
|
|
|
}
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2024-03-07 16:56:11 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
// DriveGetShares gets the current list of Taildrive shares, sorted by name.
|
|
|
|
func (b *LocalBackend) DriveGetShares() views.SliceView[*drive.Share, drive.ShareView] {
|
2024-03-07 16:56:11 +00:00
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
return b.pm.prefs.DriveShares()
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
// updateDrivePeersLocked sets all applicable peers from the netmap as Taildrive
|
2024-02-02 18:45:32 +00:00
|
|
|
// remotes.
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) updateDrivePeersLocked(nm *netmap.NetworkMap) {
|
|
|
|
fs, ok := b.sys.DriveForLocal.GetOK()
|
2024-02-02 18:45:32 +00:00
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
var driveRemotes []*drive.Remote
|
|
|
|
if b.driveAccessEnabledLocked() {
|
2024-03-07 16:56:11 +00:00
|
|
|
// Only populate peers if access is enabled, otherwise leave blank.
|
2024-04-03 17:09:58 +00:00
|
|
|
driveRemotes = b.driveRemotesFromPeers(nm)
|
2024-03-07 16:56:11 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
fs.SetRemotes(b.netMap.Domain, driveRemotes, &driveTransport{b: b})
|
2024-03-07 16:56:11 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 17:09:58 +00:00
|
|
|
func (b *LocalBackend) driveRemotesFromPeers(nm *netmap.NetworkMap) []*drive.Remote {
|
|
|
|
driveRemotes := make([]*drive.Remote, 0, len(nm.Peers))
|
2024-02-02 18:45:32 +00:00
|
|
|
for _, p := range nm.Peers {
|
2024-04-03 17:09:58 +00:00
|
|
|
// Exclude mullvad exit nodes from list of Taildrive peers
|
2024-03-06 17:46:11 +00:00
|
|
|
// TODO(oxtoacart) - once we have a better mechanism for finding only accessible sharers
|
|
|
|
// (see below) we can remove this logic.
|
|
|
|
if strings.HasSuffix(p.Name(), ".mullvad.ts.net.") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-02-02 18:45:32 +00:00
|
|
|
peerID := p.ID()
|
2024-04-04 20:07:58 +00:00
|
|
|
url := fmt.Sprintf("%s/%s", peerAPIBase(nm, p), taildrivePrefix[1:])
|
2024-04-03 17:09:58 +00:00
|
|
|
driveRemotes = append(driveRemotes, &drive.Remote{
|
2024-02-02 18:45:32 +00:00
|
|
|
Name: p.DisplayName(false),
|
|
|
|
URL: url,
|
|
|
|
Available: func() bool {
|
|
|
|
// TODO(oxtoacart): need to figure out a performant and reliable way to only
|
|
|
|
// show the peers that have shares to which we have access
|
|
|
|
// This will require work on the control server to transmit the inverse
|
2024-04-04 20:07:58 +00:00
|
|
|
// of the "tailscale.com/cap/drive" capability.
|
2024-02-02 18:45:32 +00:00
|
|
|
// For now, at least limit it only to nodes that are online.
|
|
|
|
// Note, we have to iterate the latest netmap because the peer we got from the first iteration may not be it
|
|
|
|
b.mu.Lock()
|
|
|
|
latestNetMap := b.netMap
|
|
|
|
b.mu.Unlock()
|
|
|
|
|
|
|
|
for _, candidate := range latestNetMap.Peers {
|
|
|
|
if candidate.ID() == peerID {
|
|
|
|
online := candidate.Online()
|
|
|
|
// TODO(oxtoacart): for some reason, this correctly
|
|
|
|
// catches when a node goes from offline to online,
|
|
|
|
// but not the other way around...
|
|
|
|
return online != nil && *online
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// peer not found, must not be available
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
2024-04-03 17:09:58 +00:00
|
|
|
return driveRemotes
|
2024-02-02 18:45:32 +00:00
|
|
|
}
|