Files
sing-box-extended-mirror/cmd/sing-box/cmd_api_tailscale_peer.go
T
2026-08-30 17:41:45 +08:00

162 lines
4.3 KiB
Go

package main
import (
"net/netip"
"slices"
"strings"
"github.com/sagernet/sing-box/daemon"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/spf13/cobra"
)
var commandAPITailscalePeer = &cobra.Command{
Use: "peer",
Short: "Manage Tailscale peers",
}
func init() {
commandAPITailscalePeer.PersistentFlags().StringVar(&commandAPITailscaleFlagEndpoint, "endpoint", "", commandAPITailscaleEndpointUsage)
commandAPITailscale.AddCommand(commandAPITailscalePeer)
}
type tailscalePeerEntry struct {
peer *daemon.TailscalePeer
group *daemon.TailscaleUserGroup
self bool
}
func tailscalePeerEntries(endpoint *daemon.TailscaleEndpointStatus) []tailscalePeerEntry {
var entries []tailscalePeerEntry
if endpoint.GetSelf() != nil {
entries = append(entries, tailscalePeerEntry{peer: endpoint.GetSelf(), self: true})
}
for _, group := range endpoint.GetUserGroups() {
for _, peer := range group.GetPeers() {
entries = append(entries, tailscalePeerEntry{peer: peer, group: group})
}
}
return entries
}
func resolveTailscalePeer(entries []tailscalePeerEntry, selector string) (tailscalePeerEntry, error) {
selectorAddress, addressErr := netip.ParseAddr(selector)
matchers := []func(peer *daemon.TailscalePeer) bool{
func(peer *daemon.TailscalePeer) bool {
return peer.GetStableID() == selector
},
func(peer *daemon.TailscalePeer) bool {
if addressErr != nil {
return false
}
return slices.ContainsFunc(peer.GetTailscaleIPs(), func(it string) bool {
address, parseErr := netip.ParseAddr(it)
if parseErr != nil {
return false
}
return address.Unmap() == selectorAddress.Unmap()
})
},
func(peer *daemon.TailscalePeer) bool {
dnsName := peer.GetDnsName()
if dnsName == "" {
return false
}
return strings.EqualFold(dnsName, selector) || strings.EqualFold(dns.FqdnToDomain(dnsName), selector)
},
func(peer *daemon.TailscalePeer) bool {
label, _, _ := strings.Cut(peer.GetDnsName(), ".")
if label == "" {
return false
}
return strings.EqualFold(label, selector)
},
func(peer *daemon.TailscalePeer) bool {
hostName := peer.GetHostName()
if hostName == "" {
return false
}
return strings.EqualFold(hostName, selector)
},
}
for _, matcher := range matchers {
matches := common.Filter(entries, func(it tailscalePeerEntry) bool {
return matcher(it.peer)
})
if len(matches) == 1 {
return matches[0], nil
}
if len(matches) > 1 {
return tailscalePeerEntry{}, newTailscaleAmbiguousPeerError(selector, matches)
}
}
return tailscalePeerEntry{}, E.New("peer not found: ", selector)
}
func newTailscaleAmbiguousPeerError(selector string, matches []tailscalePeerEntry) error {
sortTailscalePeerEntries(matches)
names := common.Map(matches, func(it tailscalePeerEntry) string {
return tailscalePeerName(it.peer)
})
addresses := common.Map(matches, func(it tailscalePeerEntry) string {
address := tailscalePeerAddress(it.peer)
if address == "" {
return "-"
}
return address
})
nameWidth := len(common.MaxBy(names, func(it string) int {
return len(it)
}))
addressWidth := len(common.MaxBy(addresses, func(it string) int {
return len(it)
}))
var builder strings.Builder
builder.WriteString("ambiguous peer: ")
builder.WriteString(selector)
for index, entry := range matches {
builder.WriteString("\n ")
builder.WriteString(names[index])
builder.WriteString(strings.Repeat(" ", nameWidth-len(names[index])+3))
builder.WriteString(addresses[index])
builder.WriteString(strings.Repeat(" ", addressWidth-len(addresses[index])+3))
builder.WriteString(entry.peer.GetStableID())
}
return E.New(builder.String())
}
func sortTailscalePeerEntries(entries []tailscalePeerEntry) {
common.SortBy(entries, func(it tailscalePeerEntry) string {
return strings.ToLower(tailscalePeerName(it.peer))
})
}
func tailscalePeerName(peer *daemon.TailscalePeer) string {
name := dns.FqdnToDomain(peer.GetDnsName())
if name == "" {
name = peer.GetHostName()
}
if name == "" {
name = peer.GetStableID()
}
return name
}
func tailscalePeerAddress(peer *daemon.TailscalePeer) string {
addresses := peer.GetTailscaleIPs()
if len(addresses) == 0 {
return ""
}
return addresses[0]
}
func formatYesNo(value bool) string {
if value {
return "yes"
}
return "no"
}