153 lines
5.0 KiB
Go
153 lines
5.0 KiB
Go
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
|
|
}
|