@@ -9,11 +9,9 @@ import (
99 "io/fs"
1010 "log/slog"
1111 "net"
12- "net/netip"
1312 "os"
1413 "path/filepath"
1514 "slices"
16- "strconv"
1715 "strings"
1816 "time"
1917
@@ -29,12 +27,9 @@ import (
2927 C "github.com/sagernet/sing-box/constant"
3028 O "github.com/sagernet/sing-box/option"
3129 "github.com/sagernet/sing/common/json"
32- "github.com/sagernet/sing/common/json/badoption"
3330
34- "github.com/getlantern/radiance/bypass"
3531 "github.com/getlantern/radiance/common"
3632 "github.com/getlantern/radiance/common/atomicfile"
37- "github.com/getlantern/radiance/common/env"
3833 "github.com/getlantern/radiance/common/fileperm"
3934 "github.com/getlantern/radiance/internal"
4035 "github.com/getlantern/radiance/log"
@@ -50,11 +45,6 @@ const (
5045 cacheID = "lantern"
5146 cacheFileName = "lantern.cache"
5247 cacheClearMarkerName = "lantern.cache.clear"
53- // minAndroidSystemStackKernel is the minimum Linux kernel version (major.minor) required
54- // for the system TUN stack to work reliably on Android only. Devices running a
55- // kernel below this version fall back to gvisor. This constant has no effect on
56- // other platforms.
57- minAndroidSystemStackKernel = "5.10"
5848)
5949
6050var reservedTags = []string {AutoSelectTag , ManualSelectTag , "direct" , "block" }
@@ -169,23 +159,10 @@ func hasGlobalIPv6Using(getSnapshots func() ([]ifaceSnapshot, error)) bool {
169159// baseOpts returns the minimum sing-box options required for the tunnel to
170160// function. Do not modify without understanding the downstream effects.
171161func baseOpts (basePath string ) O.Options {
172- // ensure split tunnel file exists
173- splitTunnelPath := newSplitTunnel (basePath , slog .Default ()).ruleFile
174-
175162 cacheFile := cacheFilePath (basePath )
176- loopbackAddr := badoption . Addr ( netip . MustParseAddr ( "127.0.0.1" ) )
163+ inbounds := baseInbounds ( )
177164
178- // v6 ULA conditional on system v6 — see hasGlobalIPv6.
179- tunAddress := []netip.Prefix {
180- netip .MustParsePrefix ("10.10.1.1/30" ),
181- }
182- if hasGlobalIPv6 () {
183- tunAddress = append (tunAddress , netip .MustParsePrefix ("fdfe:dcba:9876::1/126" ))
184- slog .Info ("vpn: TUN with IPv6 ULA (system has global v6)" )
185- } else {
186- slog .Info ("vpn: TUN IPv4-only (no global v6 detected)" )
187- }
188- return O.Options {
165+ opts := O.Options {
189166 Log : & O.LogOptions {
190167 Level : "debug" ,
191168 Output : "lantern-box.log" ,
@@ -200,30 +177,7 @@ func baseOpts(basePath string) O.Options {
200177 Final : "dns_local" ,
201178 },
202179 },
203- Inbounds : []O.Inbound {
204- {
205- Type : "tun" ,
206- Tag : "tun-in" ,
207- Options : & O.TunInboundOptions {
208- InterfaceName : "utun225" ,
209- Address : tunAddress ,
210- AutoRoute : true ,
211- StrictRoute : true ,
212- EndpointIndependentNat : true , // needed for QUIC migration and hole-punching
213- Stack : "system" , // fallback to gvisor on older Android kernels in buildOptions
214- },
215- },
216- {
217- Type : C .TypeMixed ,
218- Tag : bypass .BypassInboundTag ,
219- Options : & O.HTTPMixedInboundOptions {
220- ListenOptions : O.ListenOptions {
221- Listen : & loopbackAddr ,
222- ListenPort : bypass .ProxyPort ,
223- },
224- },
225- },
226- },
180+ Inbounds : inbounds ,
227181 Outbounds : []O.Outbound {
228182 {
229183 Type : C .TypeDirect ,
@@ -239,16 +193,7 @@ func baseOpts(basePath string) O.Options {
239193 Route : & O.RouteOptions {
240194 AutoDetectInterface : true ,
241195 Rules : baseRoutingRules (),
242- RuleSet : []O.RuleSet {
243- {
244- Type : C .RuleSetTypeLocal ,
245- Tag : splitTunnelTag ,
246- LocalOptions : O.LocalRuleSet {
247- Path : splitTunnelPath ,
248- },
249- Format : C .RuleSetFormatSource ,
250- },
251- },
196+ RuleSet : splitTunnelRuleSet (basePath ),
252197 DefaultDomainResolver : & O.DomainResolveOptions {
253198 Server : "dns_local" ,
254199 },
@@ -268,6 +213,22 @@ func baseOpts(basePath string) O.Options {
268213 },
269214 },
270215 }
216+
217+ // OverrideAndroidVPN is meaningless without a TUN inbound, so gate on its
218+ // presence — the novpn build has none.
219+ if common .Platform == "android" && hasTunInbound (inbounds ) {
220+ opts .Route .OverrideAndroidVPN = true
221+ }
222+ return opts
223+ }
224+
225+ func hasTunInbound (inbounds []O.Inbound ) bool {
226+ for _ , in := range inbounds {
227+ if in .Type == "tun" {
228+ return true
229+ }
230+ }
231+ return false
271232}
272233
273234func baseRoutingRules () []O.Rule {
@@ -323,49 +284,23 @@ func baseRoutingRules() []O.Rule {
323284 },
324285 },
325286 },
326- { // Route bypass proxy traffic directly (for kindling connections)
327- Type : C .RuleTypeDefault ,
328- DefaultOptions : O.DefaultRule {
329- RawDefaultRule : O.RawDefaultRule {
330- Inbound : []string {bypass .BypassInboundTag },
331- },
332- RuleAction : O.RuleAction {
333- Action : C .RuleActionTypeRoute ,
334- RouteOptions : O.RouteActionOptions {
335- Outbound : "direct" ,
336- },
337- },
338- },
339- },
340- {
341- Type : C .RuleTypeDefault ,
342- DefaultOptions : O.DefaultRule {
343- RawDefaultRule : O.RawDefaultRule {
344- IPIsPrivate : true ,
345- },
346- RuleAction : O.RuleAction {
347- Action : C .RuleActionTypeRoute ,
348- RouteOptions : O.RouteActionOptions {
349- Outbound : "direct" ,
350- },
351- },
287+ }
288+ rules = append (rules , bypassRoutingRules ()... )
289+ rules = append (rules , O.Rule {
290+ Type : C .RuleTypeDefault ,
291+ DefaultOptions : O.DefaultRule {
292+ RawDefaultRule : O.RawDefaultRule {
293+ IPIsPrivate : true ,
352294 },
353- },
354- { // split tunnel rule
355- Type : C .RuleTypeDefault ,
356- DefaultOptions : O.DefaultRule {
357- RawDefaultRule : O.RawDefaultRule {
358- RuleSet : []string {splitTunnelTag },
359- },
360- RuleAction : O.RuleAction {
361- Action : C .RuleActionTypeRoute ,
362- RouteOptions : O.RouteActionOptions {
363- Outbound : "direct" ,
364- },
295+ RuleAction : O.RuleAction {
296+ Action : C .RuleActionTypeRoute ,
297+ RouteOptions : O.RouteActionOptions {
298+ Outbound : "direct" ,
365299 },
366300 },
367301 },
368- }
302+ })
303+ rules = append (rules , splitTunnelRoutingRules ()... )
369304 return rules
370305}
371306
@@ -489,47 +424,6 @@ func buildOptions(bOptions BoxOptions) (O.Options, error) {
489424 opts := baseOpts (bOptions .BasePath )
490425 slog .Debug ("Base options initialized" )
491426
492- if env .GetBool (env .UseSocks ) {
493- socksAddr , _ := env .Get (env .SocksAddress )
494- slog .Info ("Using SOCKS proxy for inbound as per environment variable" , "socksAddr" , socksAddr )
495- addrPort , err := netip .ParseAddrPort (socksAddr )
496- if err != nil {
497- return O.Options {}, fmt .Errorf ("invalid SOCKS address: %w" , err )
498- }
499- addr := badoption .Addr (addrPort .Addr ())
500- socksIn := O.Inbound {
501- Type : C .TypeMixed ,
502- Tag : "http-socks-in" ,
503- Options : & O.HTTPMixedInboundOptions {
504- ListenOptions : O.ListenOptions {
505- Listen : & addr ,
506- ListenPort : addrPort .Port (),
507- },
508- },
509- }
510- opts .Inbounds = []O.Inbound {socksIn }
511- } else {
512- switch common .Platform {
513- case "android" :
514- opts .Route .OverrideAndroidVPN = true
515- kv := kernelVersion ()
516- slog .Debug ("detected kernel version" , "kernel" , kv )
517- if kv == "" {
518- slog .Warn ("kernel version unknown, keeping default TUN stack" )
519- } else if kernelBelow (kv , minAndroidSystemStackKernel ) {
520- opts .Inbounds [0 ].Options .(* O.TunInboundOptions ).Stack = "gvisor"
521- slog .Info ("kernel below 5.10, using gvisor TUN stack" , "kernel" , kv )
522- }
523- slog .Debug ("Android platform detected, OverrideAndroidVPN set to true" )
524- case "ios" :
525- opts .Inbounds [0 ].Options .(* O.TunInboundOptions ).Stack = ""
526- slog .Debug ("iOS platform detected, using default TUN stack with no override" )
527- case "linux" :
528- opts .Inbounds [0 ].Options .(* O.TunInboundOptions ).AutoRedirect = true
529- slog .Debug ("Linux platform detected, AutoRedirect set to true" )
530- }
531- }
532-
533427 // add smart routing and ad block rules
534428 smartRoutingRules := normalizeSmartRoutingRules (bOptions .SmartRouting )
535429 if len (smartRoutingRules ) > 0 {
@@ -731,47 +625,6 @@ func selectModeRule(mode string) O.Rule {
731625 }
732626}
733627
734- // kernelBelow reports whether the kernel version string v is below min.
735- // Only the first two components (major.minor) are compared, e.g. "5.10" or "4.19.0-android13".
736- // Returns false if either version string cannot be parsed.
737- func kernelBelow (v , min string ) bool {
738- parseKernelMajorMinor := func (s string ) (int , int , bool ) {
739- p := strings .SplitN (s , "." , 3 )
740- if len (p ) < 2 {
741- return 0 , 0 , false
742- }
743- // Strip non-numeric suffixes (e.g. "19" from "19-android13")
744- numericPrefix := func (part string ) string {
745- for i , r := range part {
746- if r < '0' || r > '9' {
747- return part [:i ]
748- }
749- }
750- return part
751- }
752- majorStr := numericPrefix (p [0 ])
753- minorStr := numericPrefix (p [1 ])
754- if majorStr == "" || minorStr == "" {
755- return 0 , 0 , false
756- }
757- major , err := strconv .Atoi (majorStr )
758- if err != nil {
759- return 0 , 0 , false
760- }
761- minor , err := strconv .Atoi (minorStr )
762- if err != nil {
763- return 0 , 0 , false
764- }
765- return major , minor , true
766- }
767- vMaj , vMin , vok := parseKernelMajorMinor (v )
768- mMaj , mMin , mok := parseKernelMajorMinor (min )
769- if ! vok || ! mok {
770- return false
771- }
772- return vMaj < mMaj || (vMaj == mMaj && vMin < mMin )
773- }
774-
775628func catchAllBlockerRule () O.Rule {
776629 return O.Rule {
777630 Type : C .RuleTypeDefault ,
0 commit comments