package app import ( "encoding/json" "net/netip" "os" "strings" "time" egressutilpkg "selective-vpn-api/app/egressutil" ) func egressIPEndpoints() []string { return egressutilpkg.IPEndpoints(os.Getenv("SVPN_EGRESS_IP_ENDPOINTS")) } func egressGeoEndpointsForIP(ip string) []string { return egressutilpkg.GeoEndpointsForIP(os.Getenv("SVPN_EGRESS_GEO_ENDPOINTS"), ip) } func egressLimitEndpointsForNetns(in []string) []string { maxN := envInt("SVPN_EGRESS_NETNS_MAX_ENDPOINTS", 1) return egressutilpkg.LimitEndpoints(in, maxN) } func egressJoinErrorsCompact(errs []string) string { return egressutilpkg.JoinErrorsCompact(errs) } func egressSingBoxSOCKSProxyURL(client TransportClient) string { if client.Kind != TransportClientSingBox { return "" } path := transportSingBoxConfigPath(client) if strings.TrimSpace(path) == "" { return "" } data, err := os.ReadFile(path) if err != nil || len(data) == 0 { return "" } var root map[string]any if err := json.Unmarshal(data, &root); err != nil { return "" } return egressutilpkg.ParseSingBoxSOCKSProxyURL(root) } func egressInterfaceBindAddress(iface string) string { iface = strings.TrimSpace(iface) if iface == "" { return "" } stdout, _, code, err := runCommandTimeout(1500*time.Millisecond, "ip", "-4", "-o", "addr", "show", "dev", iface, "scope", "global") if err != nil || code != 0 { return "" } for _, line := range strings.Split(stdout, "\n") { fields := strings.Fields(strings.TrimSpace(line)) for i := 0; i < len(fields); i++ { if fields[i] != "inet" || i+1 >= len(fields) { continue } ip := strings.TrimSpace(fields[i+1]) if slash := strings.Index(ip, "/"); slash > 0 { ip = ip[:slash] } if addr, err := netip.ParseAddr(ip); err == nil && addr.Is4() { return addr.String() } } } return "" }