Files
elmprodvpn/selective-vpn-api/app/routes_cache_restore.go

122 lines
3.6 KiB
Go

package app
import (
"context"
"fmt"
"strings"
"time"
)
func restoreRoutesFromCache() cmdResult {
return withRoutesOpLock("routes restore", restoreRoutesFromCacheUnlocked)
}
func restoreRoutesFromCacheUnlocked() cmdResult {
meta, err := loadRoutesClearCacheMeta()
if err != nil {
return cmdResult{
OK: false,
Message: fmt.Sprintf("routes cache missing: %v", err),
}
}
ips := readNonEmptyLines(routesCacheIPs)
dynIPs := readNonEmptyLines(routesCacheDyn)
routeLines, _ := readLinesFile(routesCacheRT)
ensureRoutesTableEntry()
removeTrafficRulesForTable()
_, _, _, _ = runCommandTimeout(5*time.Second, "ip", "route", "flush", "table", routesTableName())
ignoredRoutes := 0
for _, ln := range routeLines {
if err := restoreRouteLine(ln); err != nil {
if shouldIgnoreRestoreRouteError(ln, err) {
ignoredRoutes++
appendTraceLine("routes", fmt.Sprintf("restore route skipped (%q): %v", ln, err))
continue
}
return cmdResult{
OK: false,
Message: fmt.Sprintf("restore route failed (%q): %v", ln, err),
}
}
}
if ignoredRoutes > 0 {
appendTraceLine("routes", fmt.Sprintf("restore route: skipped non-critical routes=%d", ignoredRoutes))
}
if len(routeLines) == 0 && strings.TrimSpace(meta.Iface) != "" {
_, _, _, _ = runCommandTimeout(
5*time.Second,
"ip", "-4", "route", "replace",
"default", "dev", meta.Iface,
"table", routesTableName(),
"mtu", policyRouteMTU,
)
}
_, _, _, _ = runCommandTimeout(5*time.Second, "nft", "add", "table", "inet", "agvpn")
_, _, _, _ = runCommandTimeout(5*time.Second, "nft", "add", "set", "inet", "agvpn", "agvpn4", "{", "type", "ipv4_addr", ";", "flags", "interval", ";", "}")
_, _, _, _ = runCommandTimeout(5*time.Second, "nft", "add", "set", "inet", "agvpn", "agvpn_dyn4", "{", "type", "ipv4_addr", ";", "flags", "interval", ";", "}")
_, _, _, _ = runCommandTimeout(5*time.Second, "nft", "flush", "set", "inet", "agvpn", "agvpn4")
_, _, _, _ = runCommandTimeout(5*time.Second, "nft", "flush", "set", "inet", "agvpn", "agvpn_dyn4")
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()
if len(ips) > 0 {
if err := nftUpdateSetIPsSmart(ctx, "agvpn4", ips, nil); err != nil {
return cmdResult{
OK: false,
Message: fmt.Sprintf("restore nft cache failed for agvpn4: %v", err),
}
}
}
if len(dynIPs) > 0 {
if err := nftUpdateSetIPsSmart(ctx, "agvpn_dyn4", dynIPs, nil); err != nil {
return cmdResult{
OK: false,
Message: fmt.Sprintf("restore nft cache failed for agvpn_dyn4: %v", err),
}
}
}
traffic := loadTrafficModeState()
iface := strings.TrimSpace(meta.Iface)
if iface == "" {
iface = detectIfaceFromRoutes(routeLines)
}
if iface == "" {
iface, _ = resolveTrafficIface(traffic.PreferredIface)
}
if iface != "" {
if err := applyTrafficMode(traffic, iface); err != nil {
return cmdResult{
OK: false,
Message: fmt.Sprintf("cache restored, but traffic mode apply failed: %v", err),
}
}
}
_ = cacheCopyOrEmpty(routesCacheIPs, stateDir+"/last-ips.txt")
if fileExists(routesCacheMap) {
_ = cacheCopyOrEmpty(routesCacheMap, stateDir+"/last-ips-map.txt")
}
if fileExists(routesCacheMapD) {
_ = cacheCopyOrEmpty(routesCacheMapD, lastIPsMapDirect)
}
if fileExists(routesCacheMapW) {
_ = cacheCopyOrEmpty(routesCacheMapW, lastIPsMapDyn)
}
_ = writeStatusSnapshot(len(ips)+len(dynIPs), iface)
return cmdResult{
OK: true,
Message: fmt.Sprintf(
"routes restored from cache: agvpn4=%d agvpn_dyn4=%d routes=%d iface=%s",
len(ips), len(dynIPs), len(routeLines), ifaceOrDash(iface),
),
}
}