mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-09-15 21:00:27 +00:00
162 lines
4.3 KiB
Go
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"
|
|
}
|