libcore: port to sing-box 1.13 (adapter.PlatformInterface + sing v0.8)#63
Conversation
Rewrite the platform integration for sing-box 1.13, where the platform contract moved from experimental/libbox/platform.Interface to adapter.PlatformInterface and the process-owner lookup changed. - platform_box.go: implement adapter.PlatformInterface (OpenInterface + UsePlatformInterface, NetworkInterfaces + gate, MyInterfaceAddress, NetworkExtensionIncludeAllNetworks, RequestPermissionForWIFIState, UsePlatformWIFIMonitor/Notification/ConnectionOwnerFinder gates); replace process.Searcher/FindProcessInfo with FindConnectionOwner(request) returning adapter.ConnectionOwner; validate parsed addrs in the procfs path (CR). - box.go: register via service.ContextWith[adapter.PlatformInterface]; dialer.DoNotSelectInterface.Store(true) (now atomic.Bool). - interface_monitor.go: MyInterface() -> MyInterfaces() []string (sing-tun v0.8). - dns_box.go: add DNSTransport.Reset() no-op (new in 1.13). - protocol/juicity: NewSTDClient logger arg + STDConfig() (1.13 tls API). - go.mod: sing v0.8.10, sing-tun v0.8.10, quic-go v0.59-mod.5, qpack v0.6.0, sing-quic v0.6.1 gecko branch; juicity -> exclavenetwork v0.1.4 via replace. The Kotlin NativeInterface (libcore.BoxPlatformInterface/NB4AInterface) is unchanged: libcore still owns that gomobile-bound surface and adapts it. Builds clean with android tags; gomobile aar builds for all ABIs.
📝 WalkthroughWalkthroughUpdates Changessing-box 1.13 platform adapter update
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
CI's get_source_env.sh pinned COMMIT_SING_BOX at the old 1.12.x AWG tip (32aea122, sing v0.7.18), but libcore on this branch is the 1.13 port (sing v0.8.10). CI built the new libcore against the old sing-box -> REALITY tls.Config STDConfig interface mismatch. Pin sing-box at the 1.13 port tip (1596dbcb) and bump libneko to its branch tip (47d1b8b).
47d1b8b only exists on hawkff/libneko; CI clones starifly/libneko which can't read that tree (fatal: unable to read tree). Revert to 1c47a3a, the known-good commit reachable on starifly/libneko.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@libcore/go.mod`:
- Around line 9-15: Reconcile the version mismatch for the
github.com/sagernet/sing-quic dependency in the go.mod file. The indirect
dependency currently declares v0.6.1 while the replace directive points to
v0.6.2-0.20260623171805-c5b53db7a343. Either update the indirect dependency
version to match the replaced version if the bump to v0.6.2 is intentional, or
update the replace directive to use v0.6.1 if the versions should remain
aligned. Ensure consistency between the indirect dependency declaration and its
corresponding replace directive.
In `@libcore/platform_box.go`:
- Around line 21-27: The issue is that boxPlatformInterfaceInstance is a
singleton wrapper that's reused across all box contexts, but it holds
per-instance state in myTunAddress. When multiple boxes open simultaneously,
they overwrite each other's myTunAddress, causing MyInterfaceAddress() to return
incorrect values. Instead of using the package-level singleton
boxPlatformInterfaceInstance for every new context, create a fresh
boxPlatformInterfaceWrapper instance for each new box context registration.
Locate where boxPlatformInterfaceInstance is registered into box contexts and
replace it with creating a new wrapper instance per context so that each box
maintains its own isolated myTunAddress state.
In `@libcore/protocol/juicity/outbound.go`:
- Around line 77-78: The call to STDConfig() on line 77 returns both a config
and an error, but the error is discarded using the blank identifier while the
config is immediately dereferenced on line 78. Instead of ignoring the error,
check the return error from STDConfig() and handle it appropriately before
attempting to use stdTLSConfig, such as by returning an error or logging it and
exiting early. This prevents potential panics if STDConfig() fails and respects
the API contract.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2e5970f6-b707-4731-9f92-4eacd33c6398
⛔ Files ignored due to path filters (1)
libcore/go.sumis excluded by!**/*.sum
📒 Files selected for processing (6)
libcore/box.golibcore/dns_box.golibcore/go.modlibcore/interface_monitor.golibcore/platform_box.golibcore/protocol/juicity/outbound.go
| github.com/miekg/dns v1.1.72 | ||
| github.com/oschwald/maxminddb-golang v1.13.1 | ||
| github.com/reF1nd/sing-snell v0.0.6 // indirect | ||
| github.com/sagernet/quic-go v0.52.0-sing-box-mod.3 | ||
| github.com/sagernet/sing v0.7.18 | ||
| github.com/sagernet/quic-go v0.59.0-sing-box-mod.5 | ||
| github.com/sagernet/sing v0.8.10 | ||
| github.com/sagernet/sing-box v1.0.0 // replaced | ||
| github.com/sagernet/sing-tun v0.7.10 | ||
| github.com/sagernet/sing-tun v0.8.10 |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify that all direct dependencies resolve correctly and that imports match API expectations
# Check that sing-box replace target is set to feat/port-1.13 or equivalent (lines 110-118)
sed -n '110,118p' libcore/go.mod | grep -A 5 'sing-box'
# Verify go.mod syntax is valid
cd libcore && go mod verifyRepository: hawkff/NekoBoxForAndroid
Length of output: 325
🏁 Script executed:
cat libcore/go.modRepository: hawkff/NekoBoxForAndroid
Length of output: 5715
Reconcile sing-quic version mismatch between indirect dependency and replace directive.
The indirect dependency declares github.com/sagernet/sing-quic v0.6.1, but the replace directive points to github.com/hawkff/sing-quic v0.6.2-0.20260623171805-c5b53db7a343. Clarify whether this version bump is intentional or if the indirect dependency should be updated to reflect the actual replaced version.
The github.com/sagernet/sing-box replace directive correctly uses a local path (../../sing-box). The sing-juicity replace to github.com/exclavenetwork/sing-juicity v0.1.4 is properly documented and necessary for the import path migration.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@libcore/go.mod` around lines 9 - 15, Reconcile the version mismatch for the
github.com/sagernet/sing-quic dependency in the go.mod file. The indirect
dependency currently declares v0.6.1 while the replace directive points to
v0.6.2-0.20260623171805-c5b53db7a343. Either update the indirect dependency
version to match the replaced version if the bump to v0.6.2 is intentional, or
update the replace directive to use v0.6.1 if the versions should remain
aligned. Ensure consistency between the indirect dependency declaration and its
corresponding replace directive.
| stdTLSConfig, _ := tlsConfig.STDConfig() | ||
| stdTLSConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Inspect STDConfig call sites and module pins without executing project code.
set -eu
echo "Configured sing-related module versions:"
fd '^go\.mod$' . --exec sh -c 'printf "\n== %s ==\n" "$1"; sed -n "/github.com\/sagernet\/sing/p;/github.com\/sagernet\/sing-box/p" "$1"' sh {}
echo
echo "STDConfig call sites in this repository:"
rg -n -C3 '\.STDConfig\(\)' .Repository: hawkff/NekoBoxForAndroid
Length of output: 2008
🏁 Script executed:
# Inspect the outbound.go file around lines 77-78 to understand the type
sed -n '1,100p' ./libcore/protocol/juicity/outbound.go | head -100
# Check imports to find where tlsConfig comes from
grep -n 'import\|tlsConfig' ./libcore/protocol/juicity/outbound.go | head -20Repository: hawkff/NekoBoxForAndroid
Length of output: 3202
🏁 Script executed:
# Check if sing-box directory exists locally
ls -la ../../sing-box 2>/dev/null | head -20 || echo "sing-box not found at ../../sing-box from libcore"
# Try searching for STDConfig definition in the codebase
find . -name "*.go" -type f | xargs grep -l "func.*STDConfig" 2>/dev/null | head -10
# Search for STDConfig method definition
rg "func.*STDConfig\(\)" --type go -A 2Repository: hawkff/NekoBoxForAndroid
Length of output: 162
🌐 Web query:
sing-box STDConfig() method signature error return
💡 Result:
In the sing-box codebase, the STDConfig method signature consistently returns (*tls.Config, error) [1][2][3]. Specifically, this method is defined for both STDServerConfig and STDClientConfig types within the common/tls package [1][2][4]. In all identified implementations, the method returns the underlying *tls.Config instance alongside a nil error [1][2][3]. The standard implementation across these structures is: func (c *STDServerConfig) STDConfig (*tls.Config, error) { return c.config, nil } func (c *STDClientConfig) STDConfig (*tls.Config, error) { return c.config, nil } If you are encountering an error related to this method, it may be due to a version mismatch or an incorrect type assertion in your Go implementation, as the expected return signature is firmly established as (*tls.Config, error).
Citations:
- 1: https://github.com/SagerNet/sing-box/blob/8a42af32/common/tls/std_server.go
- 2: https://github.com/SagerNet/sing-box/blob/8a42af32/common/tls/std_client.go
- 3: https://github.com/SagerNet/sing-box/blob/bee2e3b6/common/tls/std_client.go
- 4: https://github.com/SagerNet/sing-box/blob/9da0746d/common/tls/std_client.go
Handle STDConfig() error return before using the config.
STDConfig() returns (*tls.Config, error) but line 77 discards the error with _, then immediately dereferences the config on line 78. While current implementations return nil, ignoring the error violates the API contract and leaves the code vulnerable to panics if the method fails.
Proposed fix
- stdTLSConfig, _ := tlsConfig.STDConfig()
+ stdTLSConfig, err := tlsConfig.STDConfig()
+ if err != nil {
+ return nil, E.Cause(err, "get std tls config")
+ }
stdTLSConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| stdTLSConfig, _ := tlsConfig.STDConfig() | |
| stdTLSConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { | |
| stdTLSConfig, err := tlsConfig.STDConfig() | |
| if err != nil { | |
| return nil, E.Cause(err, "get std tls config") | |
| } | |
| stdTLSConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@libcore/protocol/juicity/outbound.go` around lines 77 - 78, The call to
STDConfig() on line 77 returns both a config and an error, but the error is
discarded using the blank identifier while the config is immediately
dereferenced on line 78. Instead of ignoring the error, check the return error
from STDConfig() and handle it appropriately before attempting to use
stdTLSConfig, such as by returning an error or logging it and exiting early.
This prevents potential panics if STDConfig() fails and respects the API
contract.
E2E on the 1.13 core rejected the legacy config NekoBox emitted:
- TUN inbound: 1.13 removed inet4_address/inet6_address -> emit unified 'address'
list (v4 + v6 CIDRs). Add 'address' to Inbound_TunOptions.
- Sniffing/domain-strategy moved off inbounds onto route rule actions in 1.13.
Drop sniff/sniff_override_destination/domain_strategy from the TUN + mixed
inbounds; prepend route rules { action: sniff } and { action: resolve,
strategy } when sniffing is enabled. Add sniffer/timeout/strategy/server
fields to Rule_DefaultOptions.
Fixes 'legacy tun address fields ... removed in 1.12.0' and 'legacy inbound
fields ... removed in 1.13.0' create-service errors. Verified: sing-box starts,
TUN up, hy2-gecko tunnel carries traffic on device 1ad2ed680404.
Pin CI's sing-box to the port tip that carries the E2E config-decode fixes (gecko obfs option wiring, route.concurrent_dial). Sync libcore/go.sum.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt`:
- Around line 303-315: The sniff and resolve rules created with
Rule_DefaultOptions() are unscoped and execute for all inbounds, but should only
apply to the mixed inbound. Add an inbound scope to both rules by setting the
inbound field to TAG_MIXED when keepMixedInbound is true, to match the original
behavior. Additionally, the rule execution order is broken because DNS hijack
rules are being prepended with add(0, ...) after these sniff/resolve rules are
added, causing DNS hijacking to evaluate before sniffing completes. Either
prepend the sniff/resolve rules to position 0 instead of appending them (so they
execute first), or refactor the DNS hijack rule addition to append instead of
prepend, ensuring sniff and resolve rules execute before any protocol-dependent
rules like DNS hijacking.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 812cc6ba-2020-4986-9aa6-013f5c728fa7
⛔ Files ignored due to path filters (1)
libcore/go.sumis excluded by!**/*.sum
📒 Files selected for processing (3)
app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.ktapp/src/main/java/moe/matsuri/nb4a/SingBoxOptions.javabuildScript/lib/core/get_source_env.sh
✅ Files skipped from review due to trivial changes (1)
- buildScript/lib/core/get_source_env.sh
| // sing-box 1.13 moved sniffing + domain resolution off inbounds onto route | ||
| // rule actions. Emit them first so behaviour matches the old inbound fields. | ||
| if (needSniff) { | ||
| rules.add(Rule_DefaultOptions().apply { | ||
| action = "sniff" | ||
| }) | ||
| } | ||
| if (needSniffOverride) { | ||
| rules.add(Rule_DefaultOptions().apply { | ||
| action = "resolve" | ||
| strategy = genDomainStrategy(DataStore.resolveDestination) | ||
| }) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
# First, locate the ConfigBuilder.kt file
find . -name "ConfigBuilder.kt" -type fRepository: hawkff/NekoBoxForAndroid
Length of output: 227
🏁 Script executed:
# Read the target file around lines 303-315
cat -n app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt | sed -n '295,325p'Repository: hawkff/NekoBoxForAndroid
Length of output: 1540
🏁 Script executed:
# Search for add(0, ...) DNS hijack rules and related patterns
rg "add\(0," app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt -B 3 -A 3Repository: hawkff/NekoBoxForAndroid
Length of output: 1523
🏁 Script executed:
# Search for keepMixedInbound and TAG_MIXED usage
rg "keepMixedInbound|TAG_MIXED" app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt -B 2 -A 2Repository: hawkff/NekoBoxForAndroid
Length of output: 1435
🏁 Script executed:
# Search for legacy inbound field usage (sniffing, domain resolution fields)
rg "sniffing|domainStrategy|domainMatcher" app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt -B 2 -A 2Repository: hawkff/NekoBoxForAndroid
Length of output: 1019
🏁 Script executed:
# Search for all route.rules.add operations to understand the final order
rg "route\.rules\.add" app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt -B 2 -A 5Repository: hawkff/NekoBoxForAndroid
Length of output: 3205
🏁 Script executed:
# Check the complete context around the DNS hijack and sniff/resolve rules
cat -n app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt | sed -n '240,400p'Repository: hawkff/NekoBoxForAndroid
Length of output: 7771
🏁 Script executed:
# Search for where rules list is used/finalized
rg "rules\s*=" app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt -B 1 -A 3Repository: hawkff/NekoBoxForAndroid
Length of output: 617
🏁 Script executed:
# Get broader context: find where DNS hijack rules are added and understand final ordering
cat -n app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt | sed -n '450,550p'Repository: hawkff/NekoBoxForAndroid
Length of output: 5525
🏁 Script executed:
# Check if there are comments or docs about rule ordering
rg "protocol.*dns|hijack-dns" app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt -B 5 -A 5Repository: hawkff/NekoBoxForAndroid
Length of output: 762
🌐 Web query:
sing-box migration 1.13 inbound sniffing rule order scoped actions
💡 Result:
In sing-box 1.13.0, legacy inbound sniffing fields (such as sniff, sniff_timeout, and sniff_override_destination defined directly under an inbound) were removed [1][2]. To migrate these configurations, you must move the sniffing logic to the route.rules section using the sniff rule action [3][4]. When migrating, the sniffing process is now controlled by the order of your routing rules [3][4]. You should define a specific rule with the action: sniff to trigger protocol detection on desired traffic [3][5]. Because these are standard routing rules, they follow the standard top-to-bottom evaluation order [3][4]. Key points for migration and usage: - Deprecation: The old inbound.sniff options were effectively a pre-routing sniff operation [5][6]. The new sniff action in route.rules gives you granular control over exactly which traffic is sniffed and when [3][4]. - Rule Order: Since sniffing is now a rule action, you can place it before or after other rules [3][4]. For example, you can sniff only traffic from a specific inbound or specific port by adding inbound or port matchers to the same rule that contains the action: sniff [3][4]. - Scoped Actions: By combining action: sniff with other criteria (like inbound tags), you can scope sniffing to specific connections rather than enabling it globally for all inbound traffic [3][4]. - Pre-match & Performance: The sniff action can be used for pre-matching, and it is recommended to define it early if subsequent rules (like DNS hijacking or specific routing) depend on the sniffed protocol or domain [3][5][4]. For complete examples of how to rewrite your inbounds and route configuration to use the new action-based system, refer to the official sing-box migration guide [3][4].
Citations:
- 1: https://sing-box.sagernet.org/deprecated/
- 2: https://sing-box.sagernet.org/configuration/shared/listen/
- 3: https://sing-box.sagernet.org/migration/
- 4: https://github.com/SagerNet/sing-box/blob/9675b090/docs/migration.md
- 5: https://sing-box.sagernet.org/configuration/route/rule_action/
- 6: https://github.com/SagerNet/sing-box/blob/master/docs/configuration/route/rule_action.md
Scope sniff/resolve rules to the mixed inbound and fix rule execution order.
These rules are unscoped, so sniff/resolve run for all inbounds (TUN, mixed, and any direct mapping inbounds) even though the removed legacy fields were only on the mixed inbound. Additionally, the subsequent route.rules.add(0, ...) DNS hijack rules are prepended to position 0, breaking the "emit them first" comment—the final rule order becomes [port 53 hijack, dns hijack, ..., sniff, resolve], causing DNS hijacking to evaluate before sniffing completes.
Scope these actions to TAG_MIXED when keepMixedInbound is true, matching the original inbound scope. For rule order, either:
- Move sniff/resolve to
add(0, ...)prepending (after the DNS hijacks so they still evaluate first), or - Move the DNS hijack rules to append after sniff/resolve instead of prepending.
See sing-box's migration examples at sing-box.sagernet.org/migration/ which scope legacy inbound actions by inbound tag and place sniff before protocol-dependent rules like DNS hijacking.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt` around lines
303 - 315, The sniff and resolve rules created with Rule_DefaultOptions() are
unscoped and execute for all inbounds, but should only apply to the mixed
inbound. Add an inbound scope to both rules by setting the inbound field to
TAG_MIXED when keepMixedInbound is true, to match the original behavior.
Additionally, the rule execution order is broken because DNS hijack rules are
being prepended with add(0, ...) after these sniff/resolve rules are added,
causing DNS hijacking to evaluate before sniffing completes. Either prepend the
sniff/resolve rules to position 0 instead of appending them (so they execute
first), or refactor the DNS hijack rule addition to append instead of prepend,
ensuring sniff and resolve rules execute before any protocol-dependent rules
like DNS hijacking.
…/Greptile) Address P1 review findings on PR #63: - libcore: boxPlatformInterfaceInstance was a package-level singleton with a mutable myTunAddress field, registered into every BoxInstance. Concurrent starts/restarts raced on it (CodeRabbit + Greptile P1). Create a fresh boxPlatformInterfaceWrapper per NewSingBoxInstance (newBoxPlatformInterfaceWrapper) so per-open TUN address state is never shared. - ConfigBuilder: the migrated route 'resolve' action was gated on needSniffOverride (trafficSniffing == 2), but the old inbound domain_strategy ran whenever a non-empty strategy was configured - orthogonal to sniff-override. Gate the resolve rule on genDomainStrategy() being non-empty instead, so a non-default resolveDestination still applies with sniff-without-override (trafficSniffing == 1). Re-verified on device 1ad2ed680404: sing-box starts, route sniff action fires (sniffed protocol: tls), gecko tunnel + DNS work, no decode errors.
After migrating sniffing to a route 'sniff' action, needSniffOverride (trafficSniffing == 2) was left computed but unused. sing-box 1.13's sniff action has no override_destination flag (RouteActionSniff is sniffer+timeout only; the deprecated OverrideDestination is never set from config) - the sniff action overrides the destination by default, so the old override mode is preserved without a separate toggle. Remove the dead variable and document the behavior. Sniff/resolve rule emission unchanged from afdaa75 (device-verified). Compiles (app:assembleOssDebug). Device re-check pending (device offline at commit time); change is behavior-equivalent to the prior E2E-verified commit.
Migrate libcore to consume sing-box 1.13.x (hawkff/sing-box feat/port-1.13) + the matching sing-quic gecko-on-v0.6.1.
Summary
adapter.PlatformInterface(wasexperimental/libbox/platform.Interface); migrated for sing v0.7->v0.8 API breaks.Companion branches (hawkff forks)
feat/port-1.13: 1.13.13 base + AmneziaWG, snell, shadowsocksr, boxapi, nekoutils, conntrack, plain-wireguard outbound + DoNotSelectInterface gate, VLESS encryption + xhttp ports.feat/gecko-on-v0.6.1: v0.6.1 + hysteria2 gecko obfs.Tested
Status
On-device E2E in progress (hy2-gecko, naive, amneziawg, mieru, MasterDnsVPN protect-hook, DoH). Do not merge until E2E-proven.
Greptile Summary
This PR migrates libcore from sing-box 1.12 to 1.13, replacing
experimental/libbox/platform.Interfacewithadapter.PlatformInterface, updating the sing v0.7→v0.8 and sing-tun v0.7→v0.8 APIs, and adapting the Android config builder to sing-box 1.13's route-rule-action model for sniffing and domain resolution.boxPlatformInterfaceWrapperis created perBoxInstance(fixing the previously flagged singleton data race);DoNotSelectInterfacemigrated tosync/atomic.Store;Reset()no-op added toplatformLocalDNSTransport;MyInterface()renamed toMyInterfaces()in the stub.inet4_address/inet6_addressto unifiedaddress;sniff/sniff_override_destination/domain_strategyremoved from inbounds and re-expressed as first-positionaction=\"sniff\"andaction=\"resolve\"route rules;SingBoxOptions.javagains the matching new fields.tls.NewSTDClientgains aloggerparameter;tlsConfig.Config()renamed toSTDConfig(); module redirected viareplacetoexclavenetwork/sing-juicity v0.1.4.Confidence Score: 5/5
This is a methodical API migration that correctly addresses the two previously flagged issues (singleton data race and domain-strategy routing logic) while cleanly porting to sing-box 1.13 interfaces
The platform interface wrapper now creates a fresh instance per BoxInstance (eliminating the shared-state data race); the Kotlin config builder now correctly emits resolve route-rules when a domain strategy is configured regardless of sniffing mode (fixing the silently-dropped-strategy issue); all sing v0.8 API breaks are handled with targeted mechanical fixes (logger param, STDConfig rename, atomic Store, Reset no-op); the tun address unified-field migration preserves backward compatibility by keeping both field sets in the schema
No files require special attention
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant App as Android App participant NB as NewSingBoxInstance participant Ctx as Context Setup participant PI as PlatformInterface participant SB as sing-box Router participant Tun as Tun Device App->>NB: NewSingBoxInstance(config, dnsTransport) NB->>Ctx: Create context with registries NB->>PI: newBoxPlatformInterfaceWrapper() Note over PI: Fresh instance per BoxInstance<br/>(fixes data race) NB->>Ctx: service.ContextWith[adapter.PlatformInterface] NB->>SB: box.New(options, ctx, platformLogWriter) Note over App,Tun: On Box Start App->>SB: Start() SB->>PI: Initialize(networkManager) SB->>PI: OpenInterface(options, platformOptions) PI->>App: intfBox.OpenTun(jsonOptions) App-->>PI: tunFd PI->>PI: myTunAddress(options) Note over PI: Captures Inet4Address/<br/>Inet6Address for router PI->>Tun: tun.New(options) Tun-->>PI: Tun interface PI-->>SB: Tun interface Note over SB,Tun: During Routing SB->>PI: MyInterfaceAddress() PI-->>SB: cached myTunAddress SB->>PI: AutoDetectInterfaceControl(fd) PI->>App: VPNService protect App-->>PI: success%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant App as Android App participant NB as NewSingBoxInstance participant Ctx as Context Setup participant PI as PlatformInterface participant SB as sing-box Router participant Tun as Tun Device App->>NB: NewSingBoxInstance(config, dnsTransport) NB->>Ctx: Create context with registries NB->>PI: newBoxPlatformInterfaceWrapper() Note over PI: Fresh instance per BoxInstance<br/>(fixes data race) NB->>Ctx: service.ContextWith[adapter.PlatformInterface] NB->>SB: box.New(options, ctx, platformLogWriter) Note over App,Tun: On Box Start App->>SB: Start() SB->>PI: Initialize(networkManager) SB->>PI: OpenInterface(options, platformOptions) PI->>App: intfBox.OpenTun(jsonOptions) App-->>PI: tunFd PI->>PI: myTunAddress(options) Note over PI: Captures Inet4Address/<br/>Inet6Address for router PI->>Tun: tun.New(options) Tun-->>PI: Tun interface PI-->>SB: Tun interface Note over SB,Tun: During Routing SB->>PI: MyInterfaceAddress() PI-->>SB: cached myTunAddress SB->>PI: AutoDetectInterfaceControl(fd) PI->>App: VPNService protect App-->>PI: successComments Outside Diff (1)
app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt, line 175-176 (link)needSniffOverrideis now a dead variable — sniff destination override silently droppedneedSniffOverride(trafficSniffing == 2) is declared here but is never read in the new code. The oldsniff_override_destination = needSniffOverrideon both inbounds has been removed, and no equivalent is emitted in the route rules. In sing-box 1.13, theaction = "sniff"rule schema only exposessnifferandtimeout;override_destinationwas removed from that action. Any user who has "sniff + override destination" configured will silently lose that behavior after the upgrade, with routing now using raw IP addresses instead of sniffed hostnames — which breaks domain-based routing rules and SNI-based unlock scenarios. The setting should either be wired up to whatever 1.13 replacement exists, or thetrafficSniffing == 2option should be disabled/removed in the UI with a migration notice.Reviews (5): Last reviewed commit: "fix: drop dead needSniffOverride var (Gr..." | Re-trigger Greptile