package app import ( "fmt" trafficmodepkg "selective-vpn-api/app/trafficmode" "strings" ) type effectiveTrafficOverrides struct { VPNSubnets []string VPNUIDs []string DirectSubnets []string DirectUIDs []string CgroupResolvedUIDs int CgroupWarning string } func buildEffectiveOverrides(st TrafficModeState) effectiveTrafficOverrides { st = normalizeTrafficModeState(st) e := effectiveTrafficOverrides{ VPNSubnets: append([]string(nil), st.ForceVPNSubnets...), VPNUIDs: append([]string(nil), st.ForceVPNUIDs...), DirectSubnets: append([]string(nil), st.ForceDirectSubnets...), DirectUIDs: append([]string(nil), st.ForceDirectUIDs...), } vpnUIDsFromCG, warnVPN := trafficmodepkg.ResolveCgroupUIDRanges(st.ForceVPNCGroups, cgroupRootPath) directUIDsFromCG, warnDirect := trafficmodepkg.ResolveCgroupUIDRanges(st.ForceDirectCGroups, cgroupRootPath) e.CgroupResolvedUIDs = len(vpnUIDsFromCG) + len(directUIDsFromCG) e.VPNUIDs = normalizeUIDList(append(e.VPNUIDs, vpnUIDsFromCG...)) e.DirectUIDs = normalizeUIDList(append(e.DirectUIDs, directUIDsFromCG...)) warns := make([]string, 0, 2) if strings.TrimSpace(warnVPN) != "" { warns = append(warns, strings.TrimSpace(warnVPN)) } if strings.TrimSpace(warnDirect) != "" { warns = append(warns, strings.TrimSpace(warnDirect)) } e.CgroupWarning = strings.Join(warns, "; ") return e } func applyRule(pref int, args ...string) error { return trafficmodepkg.ApplyRule(pref, runCommand, args...) } func applyTrafficOverrides(e effectiveTrafficOverrides) (int, error) { return trafficmodepkg.ApplyOverrides( trafficModeOverrideConfig(), trafficmodepkg.EffectiveOverrides{ VPNSubnets: append([]string(nil), e.VPNSubnets...), VPNUIDs: append([]string(nil), e.VPNUIDs...), DirectSubnets: append([]string(nil), e.DirectSubnets...), DirectUIDs: append([]string(nil), e.DirectUIDs...), }, applyRule, ) } func ensureTrafficRouteBase(iface string, autoLocalBypass bool) error { iface = strings.TrimSpace(iface) if iface == "" { return fmt.Errorf("empty interface") } if !ifaceExists(iface) { return fmt.Errorf("interface not found: %s", iface) } ensureRoutesTableEntry() if _, _, code, err := runCommand("ip", "-4", "route", "replace", "default", "dev", iface, "table", routesTableName(), "mtu", policyRouteMTU); err != nil || code != 0 { if err == nil { err = fmt.Errorf("ip route replace default exited with %d", code) } return err } if autoLocalBypass { applyAutoLocalBypass(iface) } return nil } func applyTrafficMode(st TrafficModeState, iface string) error { st = normalizeTrafficModeState(st) eff := buildEffectiveOverrides(st) advancedActive := st.Mode == TrafficModeFullTunnel autoLocalActive := advancedActive && st.AutoLocalBypass ingressReplyActive := advancedActive && st.IngressReplyBypass removeTrafficRulesForTable() // EN: Ensure the policy table name exists even in direct mode so mark-based rules can be installed. // RU: Гарантируем наличие имени policy-table даже в direct режиме, чтобы можно было ставить mark-правила. ensureRoutesTableEntry() if err := disableIngressReplyBypass(); err != nil { return err } needVPNTable := st.Mode != TrafficModeDirect || len(eff.VPNSubnets) > 0 || len(eff.VPNUIDs) > 0 if needVPNTable { if err := ensureTrafficRouteBase(iface, autoLocalActive); err != nil { return err } } if _, err := applyTrafficOverrides(eff); err != nil { return err } // EN: Mark-based per-app routing support (cgroup-based marking in nftables). // EN: These rules are safe even when no packets are marked with MARK_APP/MARK_DIRECT. // RU: Поддержка per-app маршрутизации по mark (cgroup-based marking в nftables). // RU: Эти правила безопасны, если пакеты не помечаются MARK_APP/MARK_DIRECT. if err := applyRule(trafficRulePrefMarkDirect, "fwmark", MARK_DIRECT, "lookup", "main"); err != nil { return err } if ingressReplyActive { if err := applyRule(trafficRulePrefMarkIngressReply, "fwmark", MARK_INGRESS, "lookup", "main"); err != nil { return err } } if err := applyRule(trafficRulePrefMarkAppVPN, "fwmark", MARK_APP, "lookup", routesTableName()); err != nil { return err } switch st.Mode { case TrafficModeFullTunnel: if err := applyRule(trafficRulePrefFull, "lookup", routesTableName()); err != nil { return err } case TrafficModeSelective: if err := applyRule(trafficRulePrefSelective, "fwmark", MARK, "lookup", routesTableName()); err != nil { return err } case TrafficModeDirect: // direct mode relies only on optional direct/vpn overrides. default: return fmt.Errorf("unknown traffic mode: %s", st.Mode) } if ingressReplyActive { if err := enableIngressReplyBypass(iface); err != nil { return err } } if err := restoreAppMarksFromState(); err != nil { appendTraceLine("traffic", fmt.Sprintf("appmarks restore warning: %v", err)) } return nil }