platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
156
selective-vpn-api/app/trafficmode/interfaces.go
Normal file
156
selective-vpn-api/app/trafficmode/interfaces.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package trafficmode
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func NormalizePreferredIface(raw string) string {
|
||||
v := strings.TrimSpace(raw)
|
||||
l := strings.ToLower(v)
|
||||
if l == "" || l == "auto" || l == "-" || l == "default" {
|
||||
return ""
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func IfaceExists(iface string, run RunCommandSimpleFunc) bool {
|
||||
iface = strings.TrimSpace(iface)
|
||||
if iface == "" || run == nil {
|
||||
return false
|
||||
}
|
||||
_, _, code, _ := run("ip", "link", "show", iface)
|
||||
return code == 0
|
||||
}
|
||||
|
||||
func ParseUpIfaces(ipLinkOutput string) []string {
|
||||
seen := map[string]struct{}{}
|
||||
outIfaces := make([]string, 0, 8)
|
||||
for _, line := range strings.Split(ipLinkOutput, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, ":", 3)
|
||||
if len(parts) < 3 {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSpace(parts[1])
|
||||
name = strings.SplitN(name, "@", 2)[0]
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" || name == "lo" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[name]; ok {
|
||||
continue
|
||||
}
|
||||
seen[name] = struct{}{}
|
||||
outIfaces = append(outIfaces, name)
|
||||
}
|
||||
return outIfaces
|
||||
}
|
||||
|
||||
func ListUpIfaces(run RunCommandSimpleFunc) []string {
|
||||
if run == nil {
|
||||
return nil
|
||||
}
|
||||
out, _, code, _ := run("ip", "-o", "link", "show", "up")
|
||||
if code != 0 {
|
||||
return nil
|
||||
}
|
||||
return ParseUpIfaces(out)
|
||||
}
|
||||
|
||||
func IsVPNLikeIface(iface string) bool {
|
||||
l := strings.ToLower(strings.TrimSpace(iface))
|
||||
return strings.HasPrefix(l, "tun") ||
|
||||
strings.HasPrefix(l, "wg") ||
|
||||
strings.HasPrefix(l, "ppp") ||
|
||||
strings.HasPrefix(l, "tap") ||
|
||||
strings.HasPrefix(l, "utun") ||
|
||||
strings.HasPrefix(l, "vpn")
|
||||
}
|
||||
|
||||
func ListSelectableIfaces(up []string, preferred string) []string {
|
||||
seen := map[string]struct{}{}
|
||||
var vpnLike []string
|
||||
var other []string
|
||||
|
||||
add := func(dst *[]string, iface string) {
|
||||
iface = strings.TrimSpace(iface)
|
||||
if iface == "" {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[iface]; ok {
|
||||
return
|
||||
}
|
||||
seen[iface] = struct{}{}
|
||||
*dst = append(*dst, iface)
|
||||
}
|
||||
|
||||
for _, iface := range up {
|
||||
if IsVPNLikeIface(iface) {
|
||||
add(&vpnLike, iface)
|
||||
}
|
||||
}
|
||||
for _, iface := range up {
|
||||
if !IsVPNLikeIface(iface) {
|
||||
add(&other, iface)
|
||||
}
|
||||
}
|
||||
sort.Strings(vpnLike)
|
||||
sort.Strings(other)
|
||||
|
||||
selected := make([]string, 0, len(vpnLike)+len(other)+1)
|
||||
selected = append(selected, vpnLike...)
|
||||
selected = append(selected, other...)
|
||||
|
||||
pref := NormalizePreferredIface(preferred)
|
||||
if pref != "" {
|
||||
if _, ok := seen[pref]; !ok {
|
||||
selected = append([]string{pref}, selected...)
|
||||
}
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
||||
func ResolveTrafficIface(
|
||||
preferred string,
|
||||
ifaceExists func(string) bool,
|
||||
statusIface func() string,
|
||||
listUp func() []string,
|
||||
) (string, string) {
|
||||
exists := ifaceExists
|
||||
if exists == nil {
|
||||
exists = func(string) bool { return false }
|
||||
}
|
||||
status := statusIface
|
||||
if status == nil {
|
||||
status = func() string { return "" }
|
||||
}
|
||||
upIfaces := listUp
|
||||
if upIfaces == nil {
|
||||
upIfaces = func() []string { return nil }
|
||||
}
|
||||
|
||||
pref := NormalizePreferredIface(preferred)
|
||||
if pref != "" && exists(pref) {
|
||||
return pref, "preferred"
|
||||
}
|
||||
|
||||
statusResolved := strings.TrimSpace(status())
|
||||
if statusResolved != "" && exists(statusResolved) {
|
||||
return statusResolved, "status"
|
||||
}
|
||||
|
||||
for _, iface := range upIfaces() {
|
||||
if IsVPNLikeIface(iface) {
|
||||
return iface, "auto-vpn-like"
|
||||
}
|
||||
}
|
||||
|
||||
if pref != "" {
|
||||
return "", "preferred-not-found"
|
||||
}
|
||||
return "", "iface-not-found"
|
||||
}
|
||||
Reference in New Issue
Block a user