Compare commits

..

6 Commits

Author SHA1 Message Date
世界
804606042f Bump version 2026-02-15 21:13:55 +08:00
世界
53f2db3f97 platform: Add windows build 2026-02-15 21:10:44 +08:00
世界
1f2fdec89d release: Fix update_apple_version command 2026-02-15 21:09:14 +08:00
世界
8714c157c9 Fix matching multi predefined 2026-02-15 21:09:06 +08:00
世界
657fba4ca5 Fix matching rule-set invert 2026-02-15 21:08:33 +08:00
世界
0a69621207 wireguard: Fix missing fallback for gso 2026-02-15 21:08:26 +08:00
13 changed files with 289 additions and 111 deletions

3
.gitignore vendored
View File

@@ -12,6 +12,9 @@
/*.jar
/*.aar
/*.xcframework/
/experimental/libbox/*.aar
/experimental/libbox/*.xcframework/
/experimental/libbox/*.nupkg
.DS_Store
/config.d/
/venv/

View File

@@ -10,6 +10,8 @@ PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Versio
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH)
SING_FFI ?= sing-ffi
LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json
.PHONY: test release docs build
@@ -237,11 +239,14 @@ lib_android:
lib_apple:
go run ./cmd/internal/build_libbox -target apple
lib_windows:
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type csharp
lib_android_new:
go run ./cmd/internal/build_libbox_newffi -target android
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type android
lib_apple_new:
go run ./cmd/internal/build_libbox_newffi -target apple
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type apple
lib_install:
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.11

View File

@@ -1,93 +0,0 @@
package main
import (
"flag"
"os"
"os/exec"
"path/filepath"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/rw"
)
var target string
func init() {
flag.StringVar(&target, "target", "android", "target platform (android or apple)")
}
func main() {
flag.Parse()
args := []string{
"generate",
"-v",
"--config", "experimental/libbox/ffi.json",
"--platform-type", target,
}
command := exec.Command("sing-ffi", args...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err := command.Run()
if err != nil {
log.Fatal(err)
}
copyArtifacts(target)
}
func copyArtifacts(target string) {
switch target {
case "android":
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
if rw.IsDir(copyPath) {
copyPath, _ = filepath.Abs(copyPath)
for _, name := range []string{"libbox.aar", "libbox-legacy.aar"} {
artifactPath, found := findArtifactPath(name)
if !found {
continue
}
targetPath := filepath.Join(target, artifactPath)
os.RemoveAll(targetPath)
err := os.Rename(artifactPath, targetPath)
if err != nil {
log.Fatal(err)
}
log.Info("copied ", name, " to ", copyPath)
}
}
case "apple":
copyPath := filepath.Join("..", "sing-box-for-apple")
if rw.IsDir(copyPath) {
sourceDir, found := findArtifactPath("Libbox.xcframework")
if !found {
log.Fatal("Libbox.xcframework not found in current directory or experimental/libbox")
}
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
targetDir, _ = filepath.Abs(targetDir)
err := os.RemoveAll(targetDir)
if err != nil {
log.Fatal(err)
}
err = os.Rename(sourceDir, targetDir)
if err != nil {
log.Fatal(err)
}
log.Info("copied ", sourceDir, " to ", targetDir)
}
}
}
func findArtifactPath(name string) (string, bool) {
candidates := []string{
name,
filepath.Join("experimental", "libbox", name),
}
for _, candidate := range candidates {
if rw.IsFile(candidate) || rw.IsDir(candidate) {
return candidate, true
}
}
return "", false
}

View File

@@ -71,12 +71,12 @@ func findAndReplace(objectsMap map[string]any, projectContent string, bundleIDLi
indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}")
versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "MARKETING_VERSION = ") + 20
versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";")
version := projectContent[versionStart:versionEnd]
version := strings.Trim(projectContent[versionStart:versionEnd], "\"")
if version == newVersion {
continue
}
updated = true
projectContent = projectContent[:versionStart] + newVersion + projectContent[versionEnd:]
projectContent = projectContent[:versionStart] + "\"" + newVersion + "\"" + projectContent[versionEnd:]
}
return projectContent, updated
}

View File

@@ -377,9 +377,11 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
case *R.RuleActionReject:
return nil, &R.RejectedError{Cause: action.Error(ctx)}
case *R.RuleActionPredefined:
responseAddrs = nil
if action.Rcode != mDNS.RcodeSuccess {
err = RcodeError(action.Rcode)
} else {
err = nil
for _, answer := range action.Answer {
switch record := answer.(type) {
case *mDNS.A:

View File

@@ -2,7 +2,7 @@
icon: material/alert-decagram
---
#### 1.13.0-rc.3
#### 1.13.0-rc.4
* Fixes and improvements
@@ -169,6 +169,14 @@ Also, documentation has been updated with a warning about uTLS fingerprinting vu
uTLS is not recommended for censorship circumvention due to fundamental architectural limitations;
use NaiveProxy instead for TLS fingerprint resistance.
#### 1.12.22
* Fixes and improvements
#### 1.13.0-rc.3
* Fixes and improvements
#### 1.12.21
* Fixes and improvements

View File

@@ -119,7 +119,11 @@ func dialTarget() (string, func(context.Context, string) (net.Conn, error)) {
}
}
if sCommandServerListenPort == 0 {
return "unix://" + filepath.Join(sBasePath, "command.sock"), nil
socketPath := filepath.Join(sBasePath, "command.sock")
return "passthrough:///command-socket", func(ctx context.Context, _ string) (net.Conn, error) {
var networkDialer net.Dialer
return networkDialer.DialContext(ctx, "unix", socketPath)
}
}
return net.JoinHostPort("127.0.0.1", strconv.Itoa(int(sCommandServerListenPort))), nil
}

View File

@@ -1,10 +1,19 @@
{
"version": 1,
"variables": {
"VERSION": "$(go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)",
"WORKSPACE_ROOT": "../../..",
"DEPLOY_ANDROID": "${WORKSPACE_ROOT}/sing-box-for-android/app/libs",
"DEPLOY_APPLE": "${WORKSPACE_ROOT}/sing-box-for-apple",
"DEPLOY_WINDOWS": "${WORKSPACE_ROOT}/sing-box-for-windows/local-packages"
},
"packages": [
{
"id": "libbox",
"path": ".",
"java_package": "io.nekohasekai.libbox",
"csharp_namespace": "SagerNet",
"csharp_entrypoint": "Libbox",
"apple_prefix": "Libbox"
}
],
@@ -36,7 +45,7 @@
"ts_omit_synology",
"ts_omit_bird"
],
"ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0",
"ldflags": "-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0",
"trimpath": true
}
},
@@ -66,7 +75,7 @@
"ts_omit_synology",
"ts_omit_bird"
],
"ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0",
"ldflags": "-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0",
"trimpath": true
}
},
@@ -99,7 +108,7 @@
"ts_omit_synology",
"ts_omit_bird"
],
"ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0",
"ldflags": "-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0",
"trimpath": true
},
"overrides": [
@@ -112,6 +121,38 @@
"tags_append": ["with_low_memory"]
}
]
},
{
"id": "windows",
"packages": ["libbox"],
"default": {
"tags": [
"with_gvisor",
"with_quic",
"with_wireguard",
"with_utls",
"with_naive_outbound",
"with_purego",
"with_clash_api",
"with_conntrack",
"badlinkname",
"tfogo_checklinkname0",
"with_tailscale",
"ts_omit_logtail",
"ts_omit_ssh",
"ts_omit_drive",
"ts_omit_taildrop",
"ts_omit_webclient",
"ts_omit_doctor",
"ts_omit_capture",
"ts_omit_kube",
"ts_omit_aws",
"ts_omit_synology",
"ts_omit_bird"
],
"ldflags": "-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0",
"trimpath": true
}
}
],
"platforms": [
@@ -119,12 +160,19 @@
"type": "android",
"build": "android-main",
"min_sdk": 23,
"ndk_version": "28.0.13004108",
"lib_name": "box",
"languages": [{ "type": "java" }],
"artifacts": [
{
"type": "aar",
"output_path": "libbox.aar"
"output_path": "libbox.aar",
"execute_after": [
"if [ -d \"${DEPLOY_ANDROID}\" ]; then",
" rm -f \"${DEPLOY_ANDROID}/$$(basename \"${OUTPUT_PATH}\")\"",
" mv \"${OUTPUT_PATH}\" \"${DEPLOY_ANDROID}/\"",
"fi"
]
}
]
},
@@ -132,12 +180,19 @@
"type": "android",
"build": "android-legacy",
"min_sdk": 21,
"ndk_version": "28.0.13004108",
"lib_name": "box",
"languages": [{ "type": "java" }],
"artifacts": [
{
"type": "aar",
"output_path": "libbox-legacy.aar"
"output_path": "libbox-legacy.aar",
"execute_after": [
"if [ -d \"${DEPLOY_ANDROID}\" ]; then",
" rm -f \"${DEPLOY_ANDROID}/$$(basename \"${OUTPUT_PATH}\")\"",
" mv \"${OUTPUT_PATH}\" \"${DEPLOY_ANDROID}/\"",
"fi"
]
}
]
},
@@ -159,7 +214,46 @@
{
"type": "xcframework",
"module_name": "Libbox",
"output_path": "Libbox.xcframework"
"execute_after": [
"if [ -d \"${DEPLOY_APPLE}\" ]; then",
" rm -rf \"${DEPLOY_APPLE}/${MODULE_NAME}.xcframework\"",
" mv \"${OUTPUT_PATH}\" \"${DEPLOY_APPLE}/\"",
"fi"
]
}
]
},
{
"type": "csharp",
"build": "windows",
"targets": [
"windows/amd64"
],
"languages": [{ "type": "csharp" }],
"artifacts": [
{
"type": "nuget",
"package_id": "SagerNet.Libbox",
"package_version": "0.0.0-local",
"execute_after": {
"windows": [
"$$deployPath = '${DEPLOY_WINDOWS}'",
"if (Test-Path $$deployPath) {",
" Remove-Item \"$$deployPath\\${PACKAGE_ID}.*.nupkg\" -ErrorAction SilentlyContinue",
" Move-Item -Force '${OUTPUT_PATH}' \"$$deployPath\\\"",
" $$cachePath = if ($$env:NUGET_PACKAGES) { $$env:NUGET_PACKAGES } else { \"$$env:USERPROFILE\\.nuget\\packages\" }",
" Remove-Item -Recurse -Force \"$$cachePath\\sagernet.libbox\\${PACKAGE_VERSION}\" -ErrorAction SilentlyContinue",
"}"
],
"default": [
"if [ -d \"${DEPLOY_WINDOWS}\" ]; then",
" rm -f \"${DEPLOY_WINDOWS}/${PACKAGE_ID}.*.nupkg\"",
" mv \"${OUTPUT_PATH}\" \"${DEPLOY_WINDOWS}/\"",
" cache_path=\"$${NUGET_PACKAGES:-$${HOME}/.nuget/packages}\"",
" rm -rf \"$${cache_path}/sagernet.libbox/${PACKAGE_VERSION}\"",
"fi"
]
}
}
]
}

View File

@@ -107,9 +107,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
}
for _, item := range r.items {
if _, isRuleSet := item.(*RuleSetItem); !isRuleSet {
metadata.DidMatch = true
}
metadata.DidMatch = true
if !item.Match(metadata) {
return r.invert
}

View File

@@ -0,0 +1,157 @@
package rule
import (
"context"
"testing"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common/x/list"
"github.com/stretchr/testify/require"
"go4.org/netipx"
)
type fakeRuleSet struct {
matched bool
}
func (f *fakeRuleSet) Name() string {
return "fake-rule-set"
}
func (f *fakeRuleSet) StartContext(context.Context, *adapter.HTTPStartContext) error {
return nil
}
func (f *fakeRuleSet) PostStart() error {
return nil
}
func (f *fakeRuleSet) Metadata() adapter.RuleSetMetadata {
return adapter.RuleSetMetadata{}
}
func (f *fakeRuleSet) ExtractIPSet() []*netipx.IPSet {
return nil
}
func (f *fakeRuleSet) IncRef() {}
func (f *fakeRuleSet) DecRef() {}
func (f *fakeRuleSet) Cleanup() {}
func (f *fakeRuleSet) RegisterCallback(adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
return nil
}
func (f *fakeRuleSet) UnregisterCallback(*list.Element[adapter.RuleSetUpdateCallback]) {}
func (f *fakeRuleSet) Close() error {
return nil
}
func (f *fakeRuleSet) Match(*adapter.InboundContext) bool {
return f.matched
}
func (f *fakeRuleSet) String() string {
return "fake-rule-set"
}
type fakeRuleItem struct {
matched bool
}
func (f *fakeRuleItem) Match(*adapter.InboundContext) bool {
return f.matched
}
func (f *fakeRuleItem) String() string {
return "fake-rule-item"
}
func newRuleSetOnlyRule(ruleSetMatched bool, invert bool) *DefaultRule {
ruleSetItem := &RuleSetItem{
setList: []adapter.RuleSet{&fakeRuleSet{matched: ruleSetMatched}},
}
return &DefaultRule{
abstractDefaultRule: abstractDefaultRule{
items: []RuleItem{ruleSetItem},
allItems: []RuleItem{ruleSetItem},
invert: invert,
},
}
}
func newSingleItemRule(matched bool) *DefaultRule {
item := &fakeRuleItem{matched: matched}
return &DefaultRule{
abstractDefaultRule: abstractDefaultRule{
items: []RuleItem{item},
allItems: []RuleItem{item},
},
}
}
func TestAbstractDefaultRule_RuleSetOnly_InvertFalse(t *testing.T) {
t.Parallel()
require.True(t, newRuleSetOnlyRule(true, false).Match(&adapter.InboundContext{}))
require.False(t, newRuleSetOnlyRule(false, false).Match(&adapter.InboundContext{}))
}
func TestAbstractDefaultRule_RuleSetOnly_InvertTrue(t *testing.T) {
t.Parallel()
require.False(t, newRuleSetOnlyRule(true, true).Match(&adapter.InboundContext{}))
require.True(t, newRuleSetOnlyRule(false, true).Match(&adapter.InboundContext{}))
}
func TestAbstractLogicalRule_And_WithRuleSetInvert(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
aMatched bool
ruleSetBMatch bool
expected bool
}{
{
name: "A true B true",
aMatched: true,
ruleSetBMatch: true,
expected: false,
},
{
name: "A true B false",
aMatched: true,
ruleSetBMatch: false,
expected: true,
},
{
name: "A false B true",
aMatched: false,
ruleSetBMatch: true,
expected: false,
},
{
name: "A false B false",
aMatched: false,
ruleSetBMatch: false,
expected: false,
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
logicalRule := &abstractLogicalRule{
mode: C.LogicalTypeAnd,
rules: []adapter.HeadlessRule{
newSingleItemRule(testCase.aMatched),
newRuleSetOnlyRule(testCase.ruleSetBMatch, true),
},
}
require.Equal(t, testCase.expected, logicalRule.Match(&adapter.InboundContext{}))
})
}
}

View File

@@ -116,7 +116,7 @@ func (w *systemDevice) Start() error {
w.options.Logger.Info("started at ", w.options.Name)
w.device = tunInterface
batchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN)
if isBatchTUN {
if isBatchTUN && batchTUN.BatchSize() > 1 {
w.batchDevice = batchTUN
}
w.events <- wgTun.EventUp