Files

129 lines
2.7 KiB
Go

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