package app import ( "encoding/json" "fmt" "os" "strings" "syscall" "time" ) // --------------------------------------------------------------------- // EN: `routesClear` contains core logic for routes clear. // RU: `routesClear` - содержит основную логику для routes clear. // --------------------------------------------------------------------- func routesClear() cmdResult { return withRoutesOpLock("routes clear", routesClearUnlocked) } func routesClearUnlocked() cmdResult { cacheMeta, cacheErr := saveRoutesClearCache() stdout, stderr, _, err := runCommand("ip", "rule", "show") if err == nil && stdout != "" { removeTrafficRulesForTable() } _, _, _, _ = runCommand("ip", "route", "flush", "table", routesTableName()) _, _, _, _ = runCommand("nft", "flush", "set", "inet", "agvpn", "agvpn4") _, _, _, _ = runCommand("nft", "flush", "set", "inet", "agvpn", "agvpn_dyn4") iface := strings.TrimSpace(cacheMeta.Iface) if iface == "" { iface, _ = resolveTrafficIface(loadTrafficModeState().PreferredIface) } _ = writeStatusSnapshot(0, iface) res := cmdResult{ OK: true, Message: "routes cleared", ExitCode: 0, Stdout: stdout, Stderr: stderr, } if cacheErr != nil { res.Message = fmt.Sprintf("%s (cache warning: %v)", res.Message, cacheErr) } else { res.Message = fmt.Sprintf( "%s (cache saved: agvpn4=%d agvpn_dyn4=%d routes=%d iface=%s at=%s)", res.Message, cacheMeta.IPCount, cacheMeta.DynIPCount, cacheMeta.RouteCount, ifaceOrDash(cacheMeta.Iface), cacheMeta.CreatedAt, ) } return res } func withRoutesOpLock(opName string, fn func() cmdResult) cmdResult { lock, err := os.OpenFile(lockFile, os.O_CREATE|os.O_RDWR, 0o644) if err != nil { return cmdResult{ OK: false, Message: fmt.Sprintf("%s lock open error: %v", opName, err), } } defer lock.Close() if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil { return cmdResult{ OK: false, Message: fmt.Sprintf("%s skipped: routes operation already running", opName), } } defer syscall.Flock(int(lock.Fd()), syscall.LOCK_UN) return fn() } func writeStatusSnapshot(ipCount int, iface string) error { if ipCount < 0 { ipCount = 0 } iface = strings.TrimSpace(iface) if iface == "" { iface = "-" } st := Status{ Timestamp: time.Now().UTC().Format(time.RFC3339), IPCount: ipCount, DomainCount: countDomainsFromMap(lastIPsMapPath), Iface: iface, Table: routesTableName(), Mark: MARK, } data, err := json.MarshalIndent(st, "", " ") if err != nil { return err } return os.WriteFile(statusFilePath, data, 0o644) }