platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
128
selective-vpn-api/app/trafficmode/autolocal.go
Normal file
128
selective-vpn-api/app/trafficmode/autolocal.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package trafficmode
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var cgnatPrefix = netip.MustParsePrefix("100.64.0.0/10")
|
||||
|
||||
type AutoLocalRoute struct {
|
||||
Dst string
|
||||
Dev string
|
||||
}
|
||||
|
||||
func ParseAutoBypassRoutes(mainRoutes string, vpnIface string, isVPNLikeIface func(string) bool) []AutoLocalRoute {
|
||||
vpnIface = strings.TrimSpace(vpnIface)
|
||||
seen := map[string]struct{}{}
|
||||
routes := make([]AutoLocalRoute, 0, 8)
|
||||
|
||||
add := func(dst, dev string) {
|
||||
dst = strings.TrimSpace(dst)
|
||||
dev = strings.TrimSpace(dev)
|
||||
if dst == "" || dev == "" {
|
||||
return
|
||||
}
|
||||
key := dst + "|" + dev
|
||||
if _, ok := seen[key]; ok {
|
||||
return
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
routes = append(routes, AutoLocalRoute{Dst: dst, Dev: dev})
|
||||
}
|
||||
|
||||
for _, raw := range strings.Split(mainRoutes, "\n") {
|
||||
line := strings.TrimSpace(raw)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if RouteLineIsLinkDown(line) {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 0 {
|
||||
continue
|
||||
}
|
||||
dst := strings.TrimSpace(fields[0])
|
||||
if dst == "" || dst == "default" {
|
||||
continue
|
||||
}
|
||||
dev := ParseRouteDevice(fields)
|
||||
if dev == "" || dev == "lo" {
|
||||
continue
|
||||
}
|
||||
if vpnIface != "" && dev == vpnIface {
|
||||
continue
|
||||
}
|
||||
if isVPNLikeIface != nil && isVPNLikeIface(dev) {
|
||||
continue
|
||||
}
|
||||
|
||||
isScopeLink := strings.Contains(" "+line+" ", " scope link ")
|
||||
if isScopeLink || IsContainerIface(dev) || IsAutoBypassDestination(dst) {
|
||||
add(dst, dev)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(routes, func(i, j int) bool {
|
||||
if routes[i].Dev == routes[j].Dev {
|
||||
return routes[i].Dst < routes[j].Dst
|
||||
}
|
||||
return routes[i].Dev < routes[j].Dev
|
||||
})
|
||||
return routes
|
||||
}
|
||||
|
||||
func ParseRouteDevice(fields []string) string {
|
||||
for i := 0; i+1 < len(fields); i++ {
|
||||
if fields[i] == "dev" {
|
||||
return strings.TrimSpace(fields[i+1])
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func IsContainerIface(iface string) bool {
|
||||
l := strings.ToLower(strings.TrimSpace(iface))
|
||||
return strings.HasPrefix(l, "docker") ||
|
||||
strings.HasPrefix(l, "br-") ||
|
||||
strings.HasPrefix(l, "veth") ||
|
||||
strings.HasPrefix(l, "svh") ||
|
||||
strings.HasPrefix(l, "svn") ||
|
||||
strings.HasPrefix(l, "cni")
|
||||
}
|
||||
|
||||
func RouteLineIsLinkDown(line string) bool {
|
||||
l := " " + strings.ToLower(strings.TrimSpace(line)) + " "
|
||||
return strings.Contains(l, " linkdown ")
|
||||
}
|
||||
|
||||
func IsPrivateLikeAddr(a netip.Addr) bool {
|
||||
if !a.Is4() {
|
||||
return false
|
||||
}
|
||||
if a.IsPrivate() || a.IsLoopback() || a.IsLinkLocalUnicast() {
|
||||
return true
|
||||
}
|
||||
return cgnatPrefix.Contains(a)
|
||||
}
|
||||
|
||||
func IsAutoBypassDestination(dst string) bool {
|
||||
dst = strings.TrimSpace(dst)
|
||||
if dst == "" || dst == "default" {
|
||||
return false
|
||||
}
|
||||
if strings.Contains(dst, "/") {
|
||||
pfx, err := netip.ParsePrefix(dst)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return IsPrivateLikeAddr(pfx.Addr())
|
||||
}
|
||||
addr, err := netip.ParseAddr(dst)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return IsPrivateLikeAddr(addr)
|
||||
}
|
||||
Reference in New Issue
Block a user