Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

101 lines
2.4 KiB
Go

package networkchange
import (
"context"
"crypto/sha256"
"errors"
"net"
"net/netip"
"sort"
"strconv"
"strings"
)
type defaultRouteSample struct {
entries []string
supported bool
}
func sampleLocalNetwork(ctx context.Context) (Fingerprint, error) {
if err := contextErr(ctx); err != nil {
return Fingerprint{}, err
}
interfaces, err := net.Interfaces()
if err != nil {
return Fingerprint{}, errors.New("network interface sample failed")
}
parts := make([]string, 0, len(interfaces)*3)
for _, iface := range interfaces {
if err := contextErr(ctx); err != nil {
return Fingerprint{}, err
}
// Interface names and addresses are input to the digest only. They
// never leave this function or appear in an error/event.
parts = append(parts, iface.Name+"\x00"+strconv.Itoa(iface.Index)+"\x00"+strconv.FormatUint(uint64(iface.Flags), 10))
addresses, err := iface.Addrs()
if err != nil {
return Fingerprint{}, errors.New("network address sample failed")
}
for _, address := range addresses {
canonical, ok := canonicalAddress(address)
if !ok {
return Fingerprint{}, errors.New("network address sample failed")
}
parts = append(parts, iface.Name+"\x00"+canonical)
}
}
routes, err := sampleDefaultRoutes(ctx)
if err != nil {
return Fingerprint{}, err
}
if routes.supported {
if len(routes.entries) == 0 {
parts = append(parts, "default-route:none")
} else {
parts = append(parts, routes.entries...)
}
} else {
parts = append(parts, "default-route:unsupported")
}
sort.Strings(parts)
return sha256.Sum256([]byte(strings.Join(parts, "\n"))), nil
}
func canonicalAddress(address net.Addr) (string, bool) {
if address == nil {
return "", false
}
raw := strings.TrimSpace(address.String())
if raw == "" {
return "", false
}
slash := strings.LastIndexByte(raw, '/')
addressPart := raw
prefixLength := ""
if slash >= 0 {
addressPart = raw[:slash]
prefixLength = raw[slash+1:]
if _, err := strconv.Atoi(prefixLength); err != nil {
return "", false
}
}
if zone := strings.LastIndexByte(addressPart, '%'); zone >= 0 {
addressPart = addressPart[:zone]
}
ip, err := netip.ParseAddr(addressPart)
if err != nil {
return "", false
}
if prefixLength == "" {
return ip.String(), true
}
return ip.String() + "/" + prefixLength, true
}
func contextErr(ctx context.Context) error {
if ctx == nil {
return nil
}
return ctx.Err()
}