Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ fun buildConfig(
val enableDnsRouting = DataStore.enableDnsRouting
val useFakeDns = DataStore.enableFakeDns && !forTest
val needSniff = DataStore.trafficSniffing > 0
val needSniffOverride = DataStore.trafficSniffing == 2
val externalIndexMap = ArrayList<IndexEntity>()
val ipv6Mode = if (forTest) IPv6Mode.ENABLE else DataStore.ipv6Mode

Expand Down Expand Up @@ -251,23 +250,22 @@ fun buildConfig(
}
endpoint_independent_nat = true
mtu = DataStore.mtu
domain_strategy = genDomainStrategy(DataStore.resolveDestination)
auto_route = true
strict_route = DataStore.strictRoute
sniff = needSniff
sniff_override_destination = needSniffOverride
when (ipv6Mode) {
IPv6Mode.DISABLE -> {
inet4_address = listOf(VpnService.PRIVATE_VLAN4_CLIENT + "/28")
address = listOf(VpnService.PRIVATE_VLAN4_CLIENT + "/28")
}

IPv6Mode.ONLY -> {
inet6_address = listOf(VpnService.PRIVATE_VLAN6_CLIENT + "/126")
address = listOf(VpnService.PRIVATE_VLAN6_CLIENT + "/126")
}

else -> {
inet4_address = listOf(VpnService.PRIVATE_VLAN4_CLIENT + "/28")
inet6_address = listOf(VpnService.PRIVATE_VLAN6_CLIENT + "/126")
address = listOf(
VpnService.PRIVATE_VLAN4_CLIENT + "/28",
VpnService.PRIVATE_VLAN6_CLIENT + "/126",
)
}
}
})
Expand All @@ -280,9 +278,6 @@ fun buildConfig(
tag = TAG_MIXED
listen = bind
listen_port = DataStore.mixedPort
domain_strategy = genDomainStrategy(DataStore.resolveDestination)
sniff = needSniff
sniff_override_destination = needSniffOverride
if (DataStore.mixedInboundNeedsAuth) {
users = listOf(User().also { u ->
u.username = Key.MIXED_USERNAME
Expand All @@ -303,6 +298,28 @@ fun buildConfig(

// add concurrent dial setting
concurrent_dial = DataStore.concurrentDial

// sing-box 1.13 moved sniffing + domain resolution off inbounds onto route
// rule actions. Emit them first so behaviour matches the old inbound fields.
// sniff: previously inbound.sniff (trafficSniffing > 0). The old
// sniff_override_destination (trafficSniffing == 2) toggle has no 1.13
// equivalent - the sniff action overrides the destination by default, so the
// override mode is preserved without a separate flag.
// resolve: previously inbound.domain_strategy, which ran whenever a non-empty
// strategy was configured - independent of sniff-override. Gate on the
// strategy string being non-empty (orthogonal to sniffing).
if (needSniff) {
rules.add(Rule_DefaultOptions().apply {
action = "sniff"
})
}
val resolveStrategy = genDomainStrategy(DataStore.resolveDestination)
if (resolveStrategy.isNotEmpty()) {
rules.add(Rule_DefaultOptions().apply {
action = "resolve"
strategy = resolveStrategy
})
}
Comment on lines +302 to +322

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.

}

// returns outbound tag
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -2896,6 +2896,9 @@ public static class Inbound_TunOptions extends Inbound {

public Integer mtu;

// Generate note: Listable
public List<String> address;

// Generate note: Listable
public List<String> inet4_address;

Expand Down Expand Up @@ -4699,6 +4702,17 @@ public static class Rule_DefaultOptions extends Rule {

public String outbound;

// action = "sniff"
// Generate note: Listable
public List<String> sniffer;

public String timeout;

// action = "resolve"
public String strategy;

public String server;

}

public static class DNSRule_DefaultOptions extends DNSRule {
Expand Down
2 changes: 1 addition & 1 deletion buildScript/lib/core/get_source_env.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export COMMIT_SING_BOX="32aea122752fdac1201d7d5ea3a6042ff90a030d"
export COMMIT_SING_BOX="8966dca291128e4801b1492b0ae6e68cc7bea214"
export COMMIT_LIBNEKO="1c47a3af71990a7b2192e03292b4d246c308ef0b"
5 changes: 2 additions & 3 deletions libcore/box.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"github.com/matsuridayo/libneko/speedtest"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/boxapi"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/protocol/group"

box "github.com/sagernet/sing-box"
Expand All @@ -29,7 +28,7 @@ import (
)

func init() {
dialer.DoNotSelectInterface = true
dialer.DoNotSelectInterface.Store(true)
}

var mainInstance *BoxInstance
Expand Down Expand Up @@ -89,7 +88,7 @@ func NewSingBoxInstance(config string, localTransport LocalDNSTransport) (b *Box
nekoboxAndroidDNSTransportRegistry(localTransport), nekoboxAndroidServiceRegistry(),
)
ctx = service.ContextWithDefaultRegistry(ctx)
service.MustRegister[platform.Interface](ctx, boxPlatformInterfaceInstance)
ctx = service.ContextWith[adapter.PlatformInterface](ctx, newBoxPlatformInterfaceWrapper())

// parse options
var options option.Options
Expand Down
5 changes: 5 additions & 0 deletions libcore/dns_box.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ func (p *platformLocalDNSTransport) Close() error {
return nil
}

// Reset implements adapter.DNSTransport (added in sing-box 1.13). The platform
// transport holds no persistent connection of its own, so this is a no-op.
func (p *platformLocalDNSTransport) Reset() {
}

func (p *platformLocalDNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
if p.raw && rawQueryFunc != nil {
// Raw - only available on Android 10 and above
Expand Down
77 changes: 43 additions & 34 deletions libcore/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ require (
github.com/dyhkwong/sing-juicity v0.0.3
github.com/gofrs/uuid/v5 v5.4.0
github.com/matsuridayo/libneko v1.0.0 // replaced
github.com/miekg/dns v1.1.67
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
Comment on lines +9 to +15

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.

github.com/ulikunitz/xz v0.5.15
golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b
golang.org/x/sys v0.41.0
Expand All @@ -23,71 +23,76 @@ require (
github.com/amnezia-vpn/amneziawg-go v0.2.19 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/anytls/sing-anytls v0.0.11 // indirect
github.com/caddyserver/certmagic v0.23.0 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/caddyserver/certmagic v0.25.2 // indirect
github.com/caddyserver/zerossl v0.1.5 // indirect
github.com/cretz/bine v0.2.0 // indirect
github.com/database64128/netx-go v0.1.1 // indirect
github.com/database64128/tfo-go/v2 v2.3.2 // indirect
github.com/florianl/go-nfqueue/v2 v2.0.2 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-chi/chi/v5 v5.2.3 // indirect
github.com/go-chi/chi/v5 v5.2.5 // indirect
github.com/go-chi/render v1.0.3 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/godbus/dbus/v5 v5.2.2 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/libdns/alidns v1.0.5-libdns.v1.beta1 // indirect
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 // indirect
github.com/libdns/libdns v1.1.0 // indirect
github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/libdns/acmedns v0.5.0 // indirect
github.com/libdns/alidns v1.0.6 // indirect
github.com/libdns/cloudflare v0.2.2 // indirect
github.com/libdns/libdns v1.1.1 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
github.com/mdlayher/netlink v1.9.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/metacubex/chacha v0.1.5 // indirect
github.com/metacubex/mihomo v1.19.13 // indirect
github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/sing v0.5.5 // indirect
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 // indirect
github.com/metacubex/utls v1.8.4 // indirect
github.com/mholt/acmez/v3 v3.1.2 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/mholt/acmez/v3 v3.1.6 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
github.com/sagernet/cors v1.2.1 // indirect
github.com/sagernet/fswatch v0.1.1 // indirect
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb // indirect
github.com/sagernet/fswatch v0.1.2 // indirect
github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/nftables v0.3.0-mod.2 // indirect
github.com/sagernet/sing-mux v0.3.4 // indirect
github.com/sagernet/sing-quic v0.5.2 // indirect
github.com/sagernet/sing-quic v0.6.1 // indirect
github.com/sagernet/sing-shadowsocks v0.2.8 // indirect
github.com/sagernet/sing-shadowsocks2 v0.2.1 // indirect
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect
github.com/sagernet/sing-vmess v0.2.7 // indirect
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 // indirect
github.com/sagernet/smux v1.5.50-sing-box-mod.1 // indirect
github.com/sagernet/wireguard-go v0.0.1-beta.7 // indirect
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c // indirect
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tevino/abool v1.2.0 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.41.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.42.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.79.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gvisor.dev/gvisor v0.0.0-20250606233247-e3c4c4cad86f // indirect
lukechampine.com/blake3 v1.3.0 // indirect
)
Expand All @@ -98,6 +103,10 @@ replace github.com/sagernet/sing-box => ../../sing-box

replace github.com/sagernet/sing-vmess => github.com/starifly/sing-vmess v0.2.8-mod.1

// sing-juicity was renamed dyhkwong -> exclavenetwork at v0.1.x; the import path
// in libcore stays dyhkwong. v0.1.4 targets quic-go v0.59 / sing v0.8 / sing-quic v0.6.1.
replace github.com/dyhkwong/sing-juicity => github.com/exclavenetwork/sing-juicity v0.1.4

// replace github.com/sagernet/sing-quic => github.com/matsuridayo/sing-quic v0.0.0-20241009042333-b49ce60d9b36
// replace github.com/sagernet/sing-quic => ../../sing-quic

Expand All @@ -107,4 +116,4 @@ replace github.com/sagernet/sing-vmess => github.com/starifly/sing-vmess v0.2.8-

// replace berty.tech/go-libtor => github.com/berty/go-libtor v0.0.0-20220627102132-9189eb6e3982

replace github.com/sagernet/sing-quic => github.com/hawkff/sing-quic v0.5.3-0.20260618035506-f2f558c5aaba
replace github.com/sagernet/sing-quic => github.com/hawkff/sing-quic v0.6.2-0.20260623171805-c5b53db7a343
Loading
Loading