Skip to content

libcore: port to sing-box 1.13 (adapter.PlatformInterface + sing v0.8)#63

Merged
hawkff merged 7 commits into
mainfrom
feat/port-1.13
Jun 24, 2026
Merged

libcore: port to sing-box 1.13 (adapter.PlatformInterface + sing v0.8)#63
hawkff merged 7 commits into
mainfrom
feat/port-1.13

Conversation

@hawkff

@hawkff hawkff commented Jun 23, 2026

Copy link
Copy Markdown
Owner

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

  • libcore implements 1.13's adapter.PlatformInterface (was experimental/libbox/platform.Interface); migrated for sing v0.7->v0.8 API breaks.
  • Kotlin side unchanged (NativeInterface binds libcore's own interfaces, not sing-box's).
  • Dep set: sing v0.8.10, quic-go v0.59-sing-box-mod.5, sing-tun v0.8.10, sing-quic -> hawkff feat/gecko-on-v0.6.1.

Companion branches (hawkff forks)

  • sing-box feat/port-1.13: 1.13.13 base + AmneziaWG, snell, shadowsocksr, boxapi, nekoutils, conntrack, plain-wireguard outbound + DoNotSelectInterface gate, VLESS encryption + xhttp ports.
  • sing-quic feat/gecko-on-v0.6.1: v0.6.1 + hysteria2 gecko obfs.

Tested

  • libcore go build + gomobile aar (all ABIs) clean.
  • sing-box full build + vet clean (android tags).
  • VLESS-encryption and xhttp are build/vet-verified only (no live server).

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.Interface with adapter.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.

  • libcore Go side: Per-instance boxPlatformInterfaceWrapper is created per BoxInstance (fixing the previously flagged singleton data race); DoNotSelectInterface migrated to sync/atomic.Store; Reset() no-op added to platformLocalDNSTransport; MyInterface() renamed to MyInterfaces() in the stub.
  • Kotlin/Java side: Tun inbound migrates from separate inet4_address/inet6_address to unified address; sniff/sniff_override_destination/domain_strategy removed from inbounds and re-expressed as first-position action=\"sniff\" and action=\"resolve\" route rules; SingBoxOptions.java gains the matching new fields.
  • juicity outbound: Two mechanical API fixes for sing v0.8 — tls.NewSTDClient gains a logger parameter; tlsConfig.Config() renamed to STDConfig(); module redirected via replace to exclavenetwork/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

Filename Overview
libcore/platform_box.go Implements adapter.PlatformInterface; moves from a shared singleton to per-instance wrappers (fixing the previously flagged data race); adds all required new methods for the 1.13 interface including MyInterfaceAddress, UsePlatformInterface, FindConnectionOwner, WiFi state, and notification stubs.
libcore/box.go Platform interface registration migrated from MustRegister to service.ContextWith per BoxInstance; DoNotSelectInterface updated to atomic Store call; no functional regressions observed.
app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt Tun inbound migrated to unified address field; sniff/resolve moved to route-rule-actions; genDomainStrategy gating is now correctly orthogonal to sniffing (resolveDestination strategy emitted when non-empty regardless of sniff mode).
libcore/protocol/juicity/outbound.go Two targeted API fixes for sing v0.8: logger added to tls.NewSTDClient signature; tlsConfig.Config() renamed to STDConfig(). Module path redirected via go.mod replace to exclavenetwork/sing-juicity v0.1.4.
libcore/go.mod Dependency bump: sing v0.7→v0.8, quic-go v0.52→v0.59, sing-tun v0.7→v0.8, sing-quic redirected to hawkff fork v0.6.2; dyhkwong/sing-juicity redirected to exclavenetwork v0.1.4 with explanatory comment.
libcore/dns_box.go Adds no-op Reset() to implement adapter.DNSTransport interface added in sing-box 1.13; correct since the platform transport holds no persistent connection.
libcore/interface_monitor.go MyInterface() stub renamed to MyInterfaces() returning []string instead of string, matching the updated DefaultInterfaceMonitor interface in sing-tun v0.8.

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
Loading
%%{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: success
Loading

Comments Outside Diff (1)

  1. app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt, line 175-176 (link)

    P1 needSniffOverride is now a dead variable — sniff destination override silently dropped

    needSniffOverride (trafficSniffing == 2) is declared here but is never read in the new code. The old sniff_override_destination = needSniffOverride on both inbounds has been removed, and no equivalent is emitted in the route rules. In sing-box 1.13, the action = "sniff" rule schema only exposes sniffer and timeout; override_destination was 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 the trafficSniffing == 2 option 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

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.
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Updates libcore and Android config generation to align with sing-box 1.13's adapter.PlatformInterface API. The platform wrapper is redesigned (OpenInterface, FindConnectionOwner, capability stubs), box.go wiring is updated, adapter stubs are fixed, the juicity TLS path is corrected, dependencies are bumped, and the Android config builder moves inbound-level sniffing and domain resolution to route rules with updated TUN address configuration.

Changes

sing-box 1.13 platform adapter update

Layer / File(s) Summary
Dependency bumps and build pins
libcore/go.mod, buildScript/lib/core/get_source_env.sh
Direct dependencies (dns, quic-go, sing, sing-box, sing-tun) are bumped, the full indirect graph is refreshed, a replace directive is added for the sing-juicity import-path rename, the sing-quic fork pin is upgraded, and the build script pins sing-box to a specific commit.
Platform interface wrapper redesign and box wiring
libcore/platform_box.go, libcore/box.go
boxPlatformInterfaceWrapper is rewritten for the new adapter.PlatformInterface shape: myTunAddress state is added and captured from tun options, OpenTun is replaced with OpenInterface, unsupported Android capabilities are stubbed, and FindConnectionOwner is implemented via procfs or intfBox. In box.go, platform registration switches to service.ContextWith[adapter.PlatformInterface], the libbox/platform import is dropped, and DoNotSelectInterface uses Store(true).
Adapter stub fixes
libcore/dns_box.go, libcore/interface_monitor.go
A no-op Reset() method is added to platformLocalDNSTransport to satisfy the new adapter.DNSTransport interface. The interfaceMonitorStub single-string MyInterface() is replaced with plural MyInterfaces() []string returning nil.
Juicity TLS logger and STDConfig fix
libcore/protocol/juicity/outbound.go
NewOutbound now passes logger to tls.NewSTDClient and calls tlsConfig.STDConfig() instead of tlsConfig.Config() to obtain the config for VerifyPeerCertificate/InsecureSkipVerify modification.
Android config builder updates for sing-box 1.13
app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt, app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java
TUN inbound address configuration is refactored from inet4_address/inet6_address fields to a single address list selected by IPv6Mode. Sniffing and domain-resolution behavior is moved from inbound-level domain_strategy/sniff/sniff_override_destination fields to explicit route.rules entries with action="sniff" and action="resolve". SingBoxOptions adds address field support to Inbound_TunOptions and introduces new Rule_DefaultOptions fields (sniffer, timeout, strategy, server) for serializing the route-level rules.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • hawkff/NekoBoxForAndroid#26: Both PRs modify libcore/go.mod to replace github.com/sagernet/sing-quic with the hawkff/sing-quic fork for native Gecko Hysteria2 obfs support.
  • hawkff/NekoBoxForAndroid#44: Both PRs modify app/.../fmt/ConfigBuilder.kt's Sing-box config generation around TUN/local mixed inbound behavior and routing-rule wiring.

Poem

🐇 Hop hop, the platform's new,
OpenInterface freshly brewed,
Route rules for sniff and domain strategy,
TUN addresses unified with glee.
Dependencies bumped with a leap,
This rabbit's code update's complete! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: migrating libcore to sing-box 1.13 and updating the adapter interface from experimental to stable.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description comprehensively describes the migration to sing-box 1.13, detailing API changes, dependency updates, implementation details, and testing status.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

hawkff added 2 commits June 23, 2026 16:47
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 23c07e7 and 7a8512b.

⛔ Files ignored due to path filters (1)
  • libcore/go.sum is excluded by !**/*.sum
📒 Files selected for processing (6)
  • libcore/box.go
  • libcore/dns_box.go
  • libcore/go.mod
  • libcore/interface_monitor.go
  • libcore/platform_box.go
  • libcore/protocol/juicity/outbound.go

Comment thread libcore/go.mod
Comment on lines +9 to +15
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 verify

Repository: hawkff/NekoBoxForAndroid

Length of output: 325


🏁 Script executed:

cat libcore/go.mod

Repository: 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.

Comment thread libcore/platform_box.go Outdated
Comment on lines +77 to 78
stdTLSConfig, _ := tlsConfig.STDConfig()
stdTLSConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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 -20

Repository: 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 2

Repository: 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:


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.

Suggested change
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.

Comment thread libcore/platform_box.go
hawkff added 2 commits June 23, 2026 17:35
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.
Comment thread app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 43b6e17 and 2e6be45.

⛔ Files ignored due to path filters (1)
  • libcore/go.sum is excluded by !**/*.sum
📒 Files selected for processing (3)
  • app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
  • app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java
  • buildScript/lib/core/get_source_env.sh
✅ Files skipped from review due to trivial changes (1)
  • buildScript/lib/core/get_source_env.sh

Comment on lines +303 to +315
// 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)
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate the ConfigBuilder.kt file
find . -name "ConfigBuilder.kt" -type f

Repository: 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 3

Repository: 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 2

Repository: 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 2

Repository: 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 5

Repository: 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 3

Repository: 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 5

Repository: 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:


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:

  1. Move sniff/resolve to add(0, ...) prepending (after the DNS hijacks so they still evaluate first), or
  2. 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.

hawkff added 2 commits June 23, 2026 18:47
…/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.
@hawkff hawkff merged commit 4f49d95 into main Jun 24, 2026
5 checks passed
@hawkff hawkff deleted the feat/port-1.13 branch June 24, 2026 17:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant