157 lines
3.2 KiB
Go
157 lines
3.2 KiB
Go
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"
|
|
}
|