Files

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"
}