platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
152
selective-vpn-api/app/traffic_mode_apply.go
Normal file
152
selective-vpn-api/app/traffic_mode_apply.go
Normal file
@@ -0,0 +1,152 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user