feat: Add per-app proxy (Split Tunneling) for desktop platforms
Some checks failed
CI / run (push) Has been cancelled
Some checks failed
CI / run (push) Has been cancelled
- Add PerAppProxyOptions struct with Mode, IncludedApplications, ExcludedApplications - Implement routing rules for include/exclude modes - Include mode: selected apps use VPN, others go direct - Exclude mode: selected apps bypass VPN, others use VPN - Only active on non-Android platforms (Windows, Linux, macOS) - Logging added for debugging per-app routing decisions Part of v1.7.6 Split Tunneling feature
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
dns "github.com/sagernet/sing-dns"
|
dns "github.com/sagernet/sing-dns"
|
||||||
)
|
)
|
||||||
@@ -494,6 +495,53 @@ func setRoutingOptions(options *option.Options, opt *HiddifyOptions) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per-App Proxy for Desktop platforms (Windows, Linux, macOS)
|
||||||
|
if runtime.GOOS != "android" {
|
||||||
|
if opt.PerAppProxyOptions.Mode == "include" && len(opt.PerAppProxyOptions.IncludedApplications) > 0 {
|
||||||
|
// Mode: Only selected apps use VPN
|
||||||
|
log.Info("[Per-App] Mode: include - ", len(opt.PerAppProxyOptions.IncludedApplications), " apps will use VPN")
|
||||||
|
|
||||||
|
// Rule 1: Selected apps → VPN
|
||||||
|
routeRules = append(
|
||||||
|
routeRules,
|
||||||
|
option.Rule{
|
||||||
|
Type: C.RuleTypeDefault,
|
||||||
|
DefaultOptions: option.DefaultRule{
|
||||||
|
ProcessName: opt.PerAppProxyOptions.IncludedApplications,
|
||||||
|
Outbound: OutboundSelectTag,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rule 2: All other apps → Direct
|
||||||
|
routeRules = append(
|
||||||
|
routeRules,
|
||||||
|
option.Rule{
|
||||||
|
Type: C.RuleTypeDefault,
|
||||||
|
DefaultOptions: option.DefaultRule{
|
||||||
|
Outbound: OutboundDirectTag,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else if opt.PerAppProxyOptions.Mode == "exclude" && len(opt.PerAppProxyOptions.ExcludedApplications) > 0 {
|
||||||
|
// Mode: Excluded apps DON'T use VPN
|
||||||
|
log.Info("[Per-App] Mode: exclude - ", len(opt.PerAppProxyOptions.ExcludedApplications), " apps will bypass VPN")
|
||||||
|
|
||||||
|
// Rule: Excluded apps → Direct (rest goes through VPN by default)
|
||||||
|
routeRules = append(
|
||||||
|
routeRules,
|
||||||
|
option.Rule{
|
||||||
|
Type: C.RuleTypeDefault,
|
||||||
|
DefaultOptions: option.DefaultRule{
|
||||||
|
ProcessName: opt.PerAppProxyOptions.ExcludedApplications,
|
||||||
|
Outbound: OutboundDirectTag,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
routeRules = append(routeRules, option.Rule{
|
routeRules = append(routeRules, option.Rule{
|
||||||
Type: C.RuleTypeDefault,
|
Type: C.RuleTypeDefault,
|
||||||
DefaultOptions: option.DefaultRule{
|
DefaultOptions: option.DefaultRule{
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ type HiddifyOptions struct {
|
|||||||
InboundOptions
|
InboundOptions
|
||||||
URLTestOptions
|
URLTestOptions
|
||||||
RouteOptions
|
RouteOptions
|
||||||
|
PerAppProxyOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
type PerAppProxyOptions struct {
|
||||||
|
Mode string `json:"per-app-proxy-mode"` // "off", "include", "exclude"
|
||||||
|
IncludedApplications []string `json:"included-applications"` // ["chrome.exe", "firefox.exe"]
|
||||||
|
ExcludedApplications []string `json:"excluded-applications"` // ["steam.exe", "uTorrent.exe"]
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSOptions struct {
|
type DNSOptions struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user