129 lines
2.7 KiB
Go
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)
|
|
}
|