package app import ( "fmt" "strings" "time" ) func restoreAppMarksFromState() error { appMarksMu.Lock() defer appMarksMu.Unlock() if err := ensureAppMarksNft(); err != nil { return err } st := loadAppMarksState() now := time.Now().UTC() changed := pruneExpiredAppMarksLocked(&st, now) clearManagedAppMarkRules(appMarksChain) clearManagedAppMarkRules(appMarksGuardChain) traffic := loadTrafficModeState() vpnIface, _ := resolveTrafficIface(traffic.PreferredIface) vpnIface = strings.TrimSpace(vpnIface) kept := make([]appMarkItem, 0, len(st.Items)) for _, it := range st.Items { target := strings.ToLower(strings.TrimSpace(it.Target)) if target != "vpn" && target != "direct" { changed = true continue } rel := normalizeCgroupRelOnly(it.CgroupRel) if rel == "" { rel = normalizeCgroupRelOnly(it.Cgroup) } if rel == "" { changed = true continue } id := it.ID if id == 0 { inode, err := cgroupDirInode(rel) if err != nil { changed = true continue } id = inode it.ID = inode changed = true } level := it.Level if level <= 0 { level = strings.Count(strings.Trim(rel, "/"), "/") + 1 it.Level = level changed = true } abs := "/" + strings.TrimPrefix(rel, "/") it.CgroupRel = rel it.Cgroup = abs if _, err := cgroupDirInode(rel); err != nil { changed = true continue } iface := "" if target == "vpn" { if vpnIface == "" { // Keep state for later retry when VPN interface appears. kept = append(kept, it) continue } iface = vpnIface } if err := nftInsertAppMarkRule(target, rel, level, id, iface); err != nil { appendTraceLine("traffic", fmt.Sprintf("appmarks restore failed target=%s id=%d err=%v", target, id, err)) kept = append(kept, it) continue } if !nftHasAppMarkRule(target, id) { appendTraceLine("traffic", fmt.Sprintf("appmarks restore post-check failed target=%s id=%d", target, id)) kept = append(kept, it) continue } kept = append(kept, it) } st.Items = kept if changed { return saveAppMarksState(st) } return nil }