Compare commits

..

41 Commits

Author SHA1 Message Date
世界
658d4a180b Use stdlib ech 2024-12-11 19:33:53 +08:00
世界
93966bdd36 documentation: Bump version 2024-12-10 21:38:13 +08:00
世界
14a4b0f922 Fix socks5 UDP implementation 2024-12-10 21:38:04 +08:00
世界
919b08e64c clash-api: Fix missing endpoints 2024-12-10 21:37:36 +08:00
世界
bdd2472065 hysteria2: Add more masquerade options 2024-12-10 21:37:36 +08:00
世界
705c23866a Improve timeouts 2024-12-10 21:37:36 +08:00
世界
ec310170cc Add UDP timeout route option 2024-12-10 21:37:35 +08:00
世界
7405b22dc8 Make GSO adaptive 2024-12-10 21:37:35 +08:00
世界
cd2cf2450e Fix tests 2024-12-10 21:37:35 +08:00
世界
a525f139dc Fix lint 2024-12-10 21:37:34 +08:00
世界
cc8ba050dd refactor: WireGuard endpoint 2024-12-10 21:37:34 +08:00
世界
987556fd3d refactor: connection manager 2024-12-10 21:37:33 +08:00
世界
e6dd7d279d documentation: Fix typo 2024-12-10 21:37:32 +08:00
世界
b1cbf141c7 Add override destination to route options 2024-12-10 21:37:32 +08:00
世界
ab616d8510 Add dns.cache_capacity 2024-12-10 21:37:32 +08:00
世界
097818dfef Refactor multi networks strategy 2024-12-10 21:37:31 +08:00
世界
47af425e45 documentation: Remove unused titles 2024-12-10 21:37:31 +08:00
世界
5d49685683 Add multi network dialing 2024-12-10 21:37:30 +08:00
世界
15624a648c documentation: Merge route options to route actions 2024-12-10 21:37:30 +08:00
世界
1bfedf5332 Add network_[type/is_expensive/is_constrained] rule items 2024-12-10 21:37:30 +08:00
世界
10d15259b6 Merge route options to route actions 2024-12-10 21:37:29 +08:00
世界
ee11ca4935 refactor: Platform Interfaces 2024-12-10 21:37:28 +08:00
世界
e83331c2d9 refactor: Extract services form router 2024-12-10 21:37:28 +08:00
世界
b74df53a9c refactor: Modular network manager 2024-12-10 21:37:27 +08:00
世界
f64107f040 refactor: Modular inbound/outbound manager 2024-12-10 21:37:26 +08:00
世界
7db0e712b6 documentation: Add rule action 2024-12-10 21:37:26 +08:00
世界
edfdf1d4f3 documentation: Update the scheduled removal time of deprecated features 2024-12-10 21:37:25 +08:00
世界
eb3023d66c documentation: Remove outdated icons 2024-12-10 21:37:25 +08:00
世界
2c39c4d19c Migrate bad options to library 2024-12-10 21:37:25 +08:00
世界
aa3fcefb72 Implement udp connect 2024-12-10 21:37:25 +08:00
世界
ce503fd682 Implement new deprecated warnings 2024-12-10 21:37:24 +08:00
世界
017fa5e298 Improve rule actions 2024-12-10 21:37:23 +08:00
世界
94d187397d Remove unused reject methods 2024-12-10 21:37:23 +08:00
世界
1d497b08d7 refactor: Modular inbounds/outbounds 2024-12-10 21:37:23 +08:00
世界
e52ab781bb Implement dns-hijack 2024-12-10 21:37:23 +08:00
世界
2c1e398c78 Implement resolve(server) 2024-12-10 21:37:22 +08:00
世界
d4ad7ff638 Implement TCP and ICMP rejects 2024-12-10 21:37:22 +08:00
世界
ee0f3ba739 Crazy sekai overturns the small pond 2024-12-10 21:37:01 +08:00
世界
b365b1369d Fix play release 2024-12-10 20:44:08 +08:00
世界
69f8d7fa72 Add workaround for bulkBarrierPreWrite: unaligned arguments panic 2024-12-10 20:36:32 +08:00
世界
746f63a9ac Update debug iOS library build 2024-12-10 20:36:32 +08:00
54 changed files with 490 additions and 1072 deletions

View File

@@ -7,6 +7,11 @@ on:
description: "Version name"
required: true
type: string
prerelease:
description: "Is prerelease"
required: true
type: boolean
default: true
build:
description: "Build type"
required: true
@@ -23,6 +28,10 @@ on:
- tvOS
- macOS-standalone
- publish-android
macos_project_version:
description: "macOS project version"
required: false
type: string
push:
branches:
- main-next
@@ -38,6 +47,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.outputs.outputs.version }}
prerelease: ${{ steps.outputs.outputs.prerelease }}
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
@@ -51,7 +61,9 @@ jobs:
if: github.event_name == 'workflow_dispatch'
run: |-
echo "version=${{ inputs.version }}"
echo "prerelease=${{ inputs.prerelease }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
echo "prerelease=${{ inputs.prerelease }}" >> "$GITHUB_ENV"
- name: Calculate version
if: github.event_name != 'workflow_dispatch'
run: |-
@@ -60,6 +72,7 @@ jobs:
id: outputs
run: |-
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
build:
name: Build binary
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
@@ -240,12 +253,12 @@ jobs:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- name: Checkout main branch
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
if: needs.calculate_version.outputs.prerelease == 'false'
run: |-
cd clients/android
git checkout main
- name: Checkout dev branch
if: github.ref == 'refs/heads/dev-next'
if: needs.calculate_version.outputs.prerelease == 'true'
run: |-
cd clients/android
git checkout dev
@@ -314,12 +327,12 @@ jobs:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- name: Checkout main branch
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
if: needs.calculate_version.outputs.prerelease == 'false'
run: |-
cd clients/android
git checkout main
- name: Checkout dev branch
if: github.ref == 'refs/heads/dev-next'
if: needs.calculate_version.outputs.prerelease == 'true'
run: |-
cd clients/android
git checkout dev
@@ -341,38 +354,67 @@ jobs:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
build_apple_library:
name: Build Apple library
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store' || inputs.build == 'iOS' || inputs.build == 'macOS' || inputs.build == 'tvOS' || inputs.build == 'macOS-standalone'
runs-on: macos-15
needs:
- calculate_version
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
submodules: 'recursive'
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Setup Xcode
run: |-
sudo xcode-select -s /Applications/Xcode_16.2_beta_3.app
- name: Set tag
run: |-
git tag v${{ needs.calculate_version.outputs.version }}
- name: Build library
run: |-
make lib_install
export PATH="$PATH:$(go env GOPATH)/bin"
make lib_ios
- name: Upload library
uses: actions/upload-artifact@v4
with:
name: library-apple
path: 'Libbox.xcframework'
build_apple:
name: Build Apple clients
runs-on: macos-15
needs:
- calculate_version
- build_apple_library
strategy:
matrix:
include:
- name: iOS
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }}
platform: ios
scheme: SFI
destination: 'generic/platform=iOS'
archive: build/SFI.xcarchive
upload: SFI/Upload.plist
- name: macOS
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }}
platform: macos
scheme: SFM
destination: 'generic/platform=macOS'
archive: build/SFM.xcarchive
upload: SFI/Upload.plist
- name: tvOS
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }}
platform: tvos
scheme: SFT
destination: 'generic/platform=tvOS'
archive: build/SFT.xcarchive
upload: SFI/Upload.plist
- name: macOS-standalone
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }}
platform: macos
scheme: SFM.System
destination: 'generic/platform=macOS'
archive: build/SFM.System.xcarchive
@@ -390,26 +432,22 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next'
- name: Setup Xcode
if: matrix.if
run: |-
sudo xcode-select -s /Applications/Xcode_16.1.app
- name: Setup Xcode beta
if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.2.app
sudo xcode-select -s /Applications/Xcode_16.2_beta_3.app
- name: Set tag
if: matrix.if
run: |-
git tag v${{ needs.calculate_version.outputs.version }}
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Checkout main branch
if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
if: matrix.if && needs.calculate_version.outputs.prerelease == 'false'
run: |-
cd clients/apple
git checkout main
- name: Checkout dev branch
if: matrix.if && github.ref == 'refs/heads/dev-next'
if: matrix.if && needs.calculate_version.outputs.prerelease == 'true'
run: |-
cd clients/apple
git checkout dev
@@ -440,10 +478,6 @@ jobs:
--key $ASC_KEY_PATH \
--key-id $ASC_KEY_ID \
--issuer $ASC_KEY_ISSUER_ID
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
env:
CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
@@ -452,19 +486,12 @@ jobs:
ASC_KEY: ${{ secrets.ASC_KEY }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
- name: Build library
- name: Download library
if: matrix.if
run: |-
make lib_install
export PATH="$PATH:$(go env GOPATH)/bin"
go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }}
mv Libbox.xcframework clients/apple
- name: Update macOS version
if: matrix.if && matrix.name == 'macOS' && github.event_name == 'workflow_dispatch'
run: |-
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
uses: actions/download-artifact@v4
with:
name: library-apple
path: clients/apple/Libbox.xcframework
- name: Build
if: matrix.if
run: |-
@@ -476,25 +503,27 @@ jobs:
-destination "${{ matrix.destination }}" \
-archivePath "${{ matrix.archive }}" \
-allowProvisioningUpdates \
-authenticationKeyPath $ASC_KEY_PATH \
-authenticationKeyPath $RUNNER_TEMP/Key.p12 \
-authenticationKeyID $ASC_KEY_ID \
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
env:
MACOS_PROJECT_VERSION: ${{ inputs.macos_project_version }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
- name: Upload to App Store Connect
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }}
cd clients/apple
xcodebuild -exportArchive \
-archivePath "${{ matrix.archive }}" \
-exportOptionsPlist ${{ matrix.upload }} \
-allowProvisioningUpdates \
-authenticationKeyPath $ASC_KEY_PATH \
-authenticationKeyPath $RUNNER_TEMP/Key.p12 \
-authenticationKeyID $ASC_KEY_ID \
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
- name: Publish to TestFlight
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
- name: Build image
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
run: |-
@@ -528,7 +557,7 @@ jobs:
path: 'dist'
upload:
name: Upload builds
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
if: always() && github.event_name == 'workflow_dispatch' && inputs.build != 'publish-android'
runs-on: ubuntu-latest
needs:
- calculate_version

View File

@@ -182,22 +182,10 @@ release_tvos: build_tvos upload_tvos_app_store
update_apple_version:
go run ./cmd/internal/update_apple_version
update_macos_version:
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
release_apple_beta: update_apple_version release_ios release_macos release_tvos
publish_testflight:
go run -v ./cmd/internal/app_store_connect publish_testflight
prepare_app_store:
go run -v ./cmd/internal/app_store_connect prepare_app_store
publish_app_store:
go run -v ./cmd/internal/app_store_connect publish_app_store
test:
@go test -v ./... && \
cd test && \
@@ -216,11 +204,11 @@ lib_android:
lib_android_debug:
go run ./cmd/internal/build_libbox -target android -debug
lib_apple:
go run ./cmd/internal/build_libbox -target apple
lib_ios:
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
go run ./cmd/internal/build_libbox -target ios
lib_ios_debug:
go run ./cmd/internal/build_libbox -target ios -debug
lib:
go run ./cmd/internal/build_libbox -target android

View File

@@ -72,7 +72,7 @@ type InboundContext struct {
UDPConnect bool
UDPTimeout time.Duration
NetworkStrategy *C.NetworkStrategy
NetworkStrategy C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration

View File

@@ -28,7 +28,7 @@ type NetworkManager interface {
}
type NetworkOptions struct {
NetworkStrategy *C.NetworkStrategy
NetworkStrategy C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration

16
box.go
View File

@@ -14,7 +14,6 @@ import (
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/taskmonitor"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/experimental/cachefile"
@@ -150,14 +149,6 @@ func New(options Options) (*Box, error) {
if err != nil {
return nil, E.Cause(err, "initialize router")
}
ntpOptions := common.PtrValueOrDefault(options.NTP)
var timeService *tls.TimeServiceWrapper
if ntpOptions.Enabled {
timeService = new(tls.TimeServiceWrapper)
service.MustRegister[ntp.TimeService](ctx, timeService)
}
for i, endpointOptions := range options.Endpoints {
var tag string
if endpointOptions.Tag != "" {
@@ -263,12 +254,13 @@ func New(options Options) (*Box, error) {
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
}
}
ntpOptions := common.PtrValueOrDefault(options.NTP)
if ntpOptions.Enabled {
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions)
if err != nil {
return nil, E.Cause(err, "create NTP service")
}
ntpService := ntp.NewService(ntp.Options{
timeService := ntp.NewService(ntp.Options{
Context: ctx,
Dialer: ntpDialer,
Logger: logFactory.NewLogger("ntp"),
@@ -276,8 +268,8 @@ func New(options Options) (*Box, error) {
Interval: time.Duration(ntpOptions.Interval),
WriteToSystem: ntpOptions.WriteToSystem,
})
timeService.TimeService = ntpService
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
service.MustRegister[ntp.TimeService](ctx, timeService)
services = append(services, adapter.NewLifecycleService(timeService, "ntp service"))
}
return &Box{
network: networkManager,

View File

@@ -1,30 +0,0 @@
package main
import (
"context"
"fmt"
_ "unsafe"
"github.com/cidertool/asc-go/asc"
)
type Client struct {
*asc.Client
}
func (c *Client) UpdateBuildForAppStoreVersion(ctx context.Context, id string, buildID *string) (*asc.Response, error) {
linkage := newRelationshipDeclaration(buildID, "builds")
url := fmt.Sprintf("appStoreVersions/%s/relationships/build", id)
return c.patch(ctx, url, newRequestBody(linkage), nil)
}
func newRelationshipDeclaration(id *string, relationshipType string) *asc.RelationshipData {
if id == nil {
return nil
}
return &asc.RelationshipData{
ID: *id,
Type: relationshipType,
}
}

View File

@@ -1,140 +0,0 @@
package main
import (
"context"
"net/http"
"net/url"
"reflect"
_ "unsafe"
"github.com/cidertool/asc-go/asc"
"github.com/google/go-querystring/query"
)
func (c *Client) newRequest(ctx context.Context, method string, path string, body *requestBody, options ...requestOption) (*http.Request, error) {
return clientNewRequest(c.Client, ctx, method, path, body, options...)
}
//go:linkname clientNewRequest github.com/cidertool/asc-go/asc.(*Client).newRequest
func clientNewRequest(c *asc.Client, ctx context.Context, method string, path string, body *requestBody, options ...requestOption) (*http.Request, error)
func (c *Client) do(ctx context.Context, req *http.Request, v interface{}) (*asc.Response, error) {
return clientDo(c.Client, ctx, req, v)
}
//go:linkname clientDo github.com/cidertool/asc-go/asc.(*Client).do
func clientDo(c *asc.Client, ctx context.Context, req *http.Request, v interface{}) (*asc.Response, error)
// get sends a GET request to the API as configured.
func (c *Client) get(ctx context.Context, url string, query interface{}, v interface{}, options ...requestOption) (*asc.Response, error) {
var err error
if query != nil {
url, err = appendingQueryOptions(url, query)
if err != nil {
return nil, err
}
}
req, err := c.newRequest(ctx, "GET", url, nil, options...)
if err != nil {
return nil, err
}
resp, err := c.do(ctx, req, v)
if err != nil {
return resp, err
}
return resp, err
}
// post sends a POST request to the API as configured.
func (c *Client) post(ctx context.Context, url string, body *requestBody, v interface{}) (*asc.Response, error) {
req, err := c.newRequest(ctx, "POST", url, body, withContentType("application/json"))
if err != nil {
return nil, err
}
resp, err := c.do(ctx, req, v)
if err != nil {
return resp, err
}
return resp, err
}
// patch sends a PATCH request to the API as configured.
func (c *Client) patch(ctx context.Context, url string, body *requestBody, v interface{}) (*asc.Response, error) {
req, err := c.newRequest(ctx, "PATCH", url, body, withContentType("application/json"))
if err != nil {
return nil, err
}
resp, err := c.do(ctx, req, v)
if err != nil {
return resp, err
}
return resp, err
}
// delete sends a DELETE request to the API as configured.
func (c *Client) delete(ctx context.Context, url string, body *requestBody) (*asc.Response, error) {
req, err := c.newRequest(ctx, "DELETE", url, body, withContentType("application/json"))
if err != nil {
return nil, err
}
return c.do(ctx, req, nil)
}
// request is a common structure for a request body sent to the API.
type requestBody struct {
Data interface{} `json:"data"`
Included interface{} `json:"included,omitempty"`
}
func newRequestBody(data interface{}) *requestBody {
return newRequestBodyWithIncluded(data, nil)
}
func newRequestBodyWithIncluded(data interface{}, included interface{}) *requestBody {
return &requestBody{Data: data, Included: included}
}
type requestOption func(*http.Request)
func withAccept(typ string) requestOption {
return func(req *http.Request) {
req.Header.Set("Accept", typ)
}
}
func withContentType(typ string) requestOption {
return func(req *http.Request) {
req.Header.Set("Content-Type", typ)
}
}
// AddOptions adds the parameters in opt as URL query parameters to s. opt
// must be a struct whose fields may contain "url" tags.
func appendingQueryOptions(s string, opt interface{}) (string, error) {
v := reflect.ValueOf(opt)
if v.Kind() == reflect.Ptr && v.IsNil() {
return s, nil
}
u, err := url.Parse(s)
if err != nil {
return s, err
}
qs, err := query.Values(opt)
if err != nil {
return s, err
}
u.RawQuery = qs.Encode()
return u.String(), nil
}

View File

@@ -1,430 +0,0 @@
package main
import (
"context"
"net/http"
"os"
"strconv"
"time"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/cidertool/asc-go/asc"
)
func main() {
ctx := context.Background()
switch os.Args[1] {
case "next_macos_project_version":
err := fetchMacOSVersion(ctx)
if err != nil {
log.Fatal(err)
}
case "publish_testflight":
err := publishTestflight(ctx)
if err != nil {
log.Fatal(err)
}
case "cancel_app_store":
err := cancelAppStore(ctx, os.Args[2])
if err != nil {
log.Fatal(err)
}
case "prepare_app_store":
err := prepareAppStore(ctx)
if err != nil {
log.Fatal(err)
}
case "publish_app_store":
err := publishAppStore(ctx)
if err != nil {
log.Fatal(err)
}
default:
log.Fatal("unknown action: ", os.Args[1])
}
}
const (
appID = "6673731168"
groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda"
)
func createClient(expireDuration time.Duration) *Client {
privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH"))
if err != nil {
log.Fatal(err)
}
tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), expireDuration, privateKey)
if err != nil {
log.Fatal(err)
}
return &Client{asc.NewClient(tokenConfig.Client())}
}
func fetchMacOSVersion(ctx context.Context) error {
client := createClient(time.Minute)
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{"MAC_OS"},
})
if err != nil {
return err
}
var versionID string
findVersion:
for _, version := range versions.Data {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStateReadyForSale,
asc.AppStoreVersionStatePendingDeveloperRelease:
versionID = version.ID
break findVersion
}
}
if versionID == "" {
return E.New("no version found")
}
latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{})
if err != nil {
return err
}
versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version)
if err != nil {
return E.Cause(err, "parse version code")
}
os.Stdout.WriteString(F.ToString(versionInt+1, "\n"))
return nil
}
func publishTestflight(ctx context.Context) error {
tagVersion, err := build_shared.ReadTagVersion()
if err != nil {
return err
}
tag := tagVersion.VersionString()
client := createClient(10 * time.Minute)
log.Info(tag, " list build IDs")
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
if err != nil {
return err
}
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
return it.ID
})
var platforms []asc.Platform
if len(os.Args) == 3 {
switch os.Args[2] {
case "ios":
platforms = []asc.Platform{asc.PlatformIOS}
case "macos":
platforms = []asc.Platform{asc.PlatformMACOS}
case "tvos":
platforms = []asc.Platform{asc.PlatformTVOS}
default:
return E.New("unknown platform: ", os.Args[2])
}
} else {
platforms = []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
}
}
for _, platform := range platforms {
log.Info(string(platform), " list builds")
for {
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
FilterApp: []string{appID},
FilterPreReleaseVersionPlatform: []string{string(platform)},
})
if err != nil {
return err
}
build := builds.Data[0]
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
log.Info(string(platform), " ", tag, " waiting for process")
time.Sleep(15 * time.Second)
continue
}
if *build.Attributes.ProcessingState != "VALID" {
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
time.Sleep(15 * time.Second)
continue
}
log.Info(string(platform), " ", tag, " list localizations")
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
if err != nil {
return err
}
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
return *it.Attributes.Locale == "en-US"
})
if localization.ID == "" {
log.Fatal(string(platform), " ", tag, " no en-US localization found")
}
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
log.Info(string(platform), " ", tag, " update localization")
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(
F.ToString("sing-box ", tag),
))
if err != nil {
return err
}
}
log.Info(string(platform), " ", tag, " publish")
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
log.Info("waiting for process")
time.Sleep(15 * time.Second)
continue
} else if err != nil {
return err
}
log.Info(string(platform), " ", tag, " list submissions")
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
FilterBuild: []string{build.ID},
})
if err != nil {
return err
}
if len(betaSubmissions.Data) == 0 {
log.Info(string(platform), " ", tag, " create submission")
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
if err != nil {
return err
}
}
break
}
}
return nil
}
func cancelAppStore(ctx context.Context, platform string) error {
switch platform {
case "ios":
platform = string(asc.PlatformIOS)
case "macos":
platform = string(asc.PlatformMACOS)
case "tvos":
platform = string(asc.PlatformTVOS)
}
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
log.Info(platform, " list versions")
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
if version.ID == "" {
return nil
}
log.Info(string(platform), " ", tag, " get submission")
submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
if response != nil && response.StatusCode == http.StatusNotFound {
return nil
}
if err != nil {
return err
}
log.Info(platform, " ", tag, " delete submission")
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
if err != nil {
return err
}
return nil
}
func prepareAppStore(ctx context.Context) error {
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for _, platform := range []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
} {
log.Info(string(platform), " list versions")
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
log.Info(string(platform), " ", tag, " list builds")
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
FilterApp: []string{appID},
FilterPreReleaseVersionPlatform: []string{string(platform)},
})
if err != nil {
return err
}
if len(builds.Data) == 0 {
log.Fatal(platform, " ", tag, " no build found")
}
buildID := common.Ptr(builds.Data[0].ID)
if version.ID == "" {
log.Info(string(platform), " ", tag, " create version")
newVersion, _, err := client.Apps.CreateAppStoreVersion(ctx, asc.AppStoreVersionCreateRequestAttributes{
Platform: platform,
VersionString: tag,
}, appID, buildID)
if err != nil {
return err
}
version = newVersion.Data
} else {
log.Info(string(platform), " ", tag, " check build")
currentBuild, response, err := client.Apps.GetBuildIDForAppStoreVersion(ctx, version.ID)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK || currentBuild.Data.ID != *buildID {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission,
asc.AppStoreVersionStateRejected,
asc.AppStoreVersionStateDeveloperRejected:
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview,
asc.AppStoreVersionStatePendingDeveloperRelease:
submission, _, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
if err != nil {
return err
}
if submission != nil {
log.Info(string(platform), " ", tag, " delete submission")
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
if err != nil {
return err
}
time.Sleep(5 * time.Second)
}
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
log.Info(string(platform), " ", tag, " update build")
response, err = client.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID)
if err != nil {
return err
}
if response.StatusCode != http.StatusNoContent {
response.Write(os.Stderr)
log.Fatal(string(platform), " ", tag, " unexpected response: ", response.Status)
}
} else {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission,
asc.AppStoreVersionStateRejected,
asc.AppStoreVersionStateDeveloperRejected:
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview,
asc.AppStoreVersionStatePendingDeveloperRelease:
continue
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
}
}
log.Info(string(platform), " ", tag, " list localization")
localizations, _, err := client.Apps.ListLocalizationsForAppStoreVersion(ctx, version.ID, nil)
if err != nil {
return err
}
localization := common.Find(localizations.Data, func(it asc.AppStoreVersionLocalization) bool {
return *it.Attributes.Locale == "en-US"
})
if localization.ID == "" {
log.Info(string(platform), " ", tag, " no en-US localization found")
}
if localization.Attributes.WhatsNew == nil && *localization.Attributes.WhatsNew == "" {
log.Info(string(platform), " ", tag, " update localization")
_, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{
PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."),
WhatsNew: common.Ptr(F.ToString("sing-box ", tag, ": Fixes and improvements.")),
})
if err != nil {
return err
}
}
log.Info(string(platform), " ", tag, " create submission")
fixSubmit:
for {
_, response, err := client.Submission.CreateSubmission(ctx, version.ID)
if err != nil {
switch response.StatusCode {
case http.StatusInternalServerError:
continue
default:
response.Write(os.Stderr)
log.Info(string(platform), " ", tag, " unexpected response: ", response.Status)
}
}
switch response.StatusCode {
case http.StatusCreated:
break fixSubmit
default:
response.Write(os.Stderr)
log.Info(string(platform), " ", tag, " unexpected response: ", response.Status)
}
}
}
return nil
}
func publishAppStore(ctx context.Context) error {
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for _, platform := range []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
} {
log.Info(string(platform), " list versions")
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission, asc.AppStoreVersionStateDeveloperRejected:
log.Fatal(string(platform), " ", tag, " not submitted")
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview:
log.Warn(string(platform), " ", tag, " waiting for review")
continue
case asc.AppStoreVersionStatePendingDeveloperRelease:
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
_, _, err = client.Publishing.CreatePhasedRelease(ctx, common.Ptr(asc.PhasedReleaseStateComplete), version.ID)
if err != nil {
return err
}
}
return nil
}

View File

@@ -18,13 +18,11 @@ import (
var (
debugEnabled bool
target string
platform string
)
func init() {
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
flag.StringVar(&target, "target", "android", "target platform")
flag.StringVar(&platform, "platform", "", "specify platform")
}
func main() {
@@ -35,8 +33,8 @@ func main() {
switch target {
case "android":
buildAndroid()
case "apple":
buildApple()
case "ios":
buildiOS()
}
}
@@ -83,9 +81,7 @@ func buildAndroid() {
}
var bindTarget string
if platform != "" {
bindTarget = platform
} else if debugEnabled {
if debugEnabled {
bindTarget = "android/arm64"
} else {
bindTarget = "android"
@@ -133,14 +129,12 @@ func buildAndroid() {
}
}
func buildApple() {
func buildiOS() {
var bindTarget string
if platform != "" {
bindTarget = platform
} else if debugEnabled {
if debugEnabled {
bindTarget = "ios"
} else {
bindTarget = "ios,tvos,macos"
bindTarget = "ios,iossimulator,tvos,tvossimulator,macos"
}
args := []string{

View File

@@ -36,3 +36,11 @@ func ReadTagVersion() (badversion.Version, error) {
}
return version, nil
}
func IsDevBranch() bool {
branch, err := shell.Exec("git", "branch", "--show-current").ReadOutput()
if err != nil {
return false
}
return branch == "dev-next"
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
F "github.com/sagernet/sing/common/format"
)
var nightly bool
@@ -21,14 +22,25 @@ func main() {
if err != nil {
log.Fatal(err)
}
var versionStr string
var (
versionStr string
isPrerelease bool
)
if version.PreReleaseIdentifier != "" {
isPrerelease = true
versionStr = version.VersionString() + "-nightly"
} else {
version.Patch++
versionStr = version.VersionString() + "-nightly"
}
err = setGitHubEnv("version", versionStr)
if build_shared.IsDevBranch() {
isPrerelease = true
}
err = setGitHubOutput("version", versionStr)
if err != nil {
log.Fatal(err)
}
err = setGitHubOutput("prerelease", F.ToString(isPrerelease))
if err != nil {
log.Fatal(err)
}
@@ -43,7 +55,7 @@ func main() {
}
}
func setGitHubEnv(name string, value string) error {
func setGitHubOutput(name string, value string) error {
outputFile, err := os.OpenFile(os.Getenv("GITHUB_ENV"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
return err

View File

@@ -9,7 +9,6 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/conntrack"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
@@ -17,7 +16,6 @@ import (
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
var (
@@ -35,22 +33,19 @@ type DefaultDialer struct {
udpAddr6 string
isWireGuardListener bool
networkManager adapter.NetworkManager
networkStrategy *C.NetworkStrategy
networkStrategy C.NetworkStrategy
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
networkLastFallback atomic.TypedValue[time.Time]
}
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
networkManager := service.FromContext[adapter.NetworkManager](ctx)
platformInterface := service.FromContext[platform.Interface](ctx)
func NewDefault(networkManager adapter.NetworkManager, options option.DialerOptions) (*DefaultDialer, error) {
var (
dialer net.Dialer
listener net.ListenConfig
interfaceFinder control.InterfaceFinder
networkStrategy *C.NetworkStrategy
networkStrategy C.NetworkStrategy
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
@@ -79,42 +74,39 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
}
}
if options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil || options.TCPFastOpen {
if options.NetworkStrategy != nil || len(options.NetworkType) > 0 && options.FallbackNetworkType == nil && options.FallbackDelay == 0 {
return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address`, `inet6_bind_address` and `tcp_fast_open`")
if C.NetworkStrategy(options.NetworkStrategy) != C.NetworkStrategyDefault {
if options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil {
return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`")
}
networkStrategy = C.NetworkStrategy(options.NetworkStrategy)
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
networkFallbackDelay = time.Duration(options.NetworkFallbackDelay)
if networkManager == nil || !networkManager.AutoDetectInterface() {
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
}
}
if networkManager != nil {
if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil {
defaultOptions := networkManager.DefaultOptions()
if defaultOptions.BindInterface != "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if networkManager.AutoDetectInterface() {
if platformInterface != nil {
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
if networkStrategy == nil {
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
}
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
if options.BindInterface == "" {
if defaultOptions.BindInterface != "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if networkManager.AutoDetectInterface() {
if defaultOptions.NetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault {
networkStrategy = defaultOptions.NetworkStrategy
networkType = defaultOptions.NetworkType
fallbackNetworkType = defaultOptions.FallbackNetworkType
}
networkFallbackDelay = time.Duration(options.FallbackDelay)
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
networkFallbackDelay = defaultOptions.FallbackDelay
bindFunc := networkManager.ProtectFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else {
bindFunc := networkManager.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
bindFunc := networkManager.ProtectFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else {
bindFunc := networkManager.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
}
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
@@ -180,6 +172,9 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
listener.Control = control.Append(listener.Control, controlFn)
}
}
if networkStrategy != C.NetworkStrategyDefault && options.TCPFastOpen {
return nil, E.New("`tcp_fast_open` is conflict with `network_strategy` or `route.default_network_strategy`")
}
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
if err != nil {
return nil, err
@@ -209,7 +204,7 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
if !address.IsValid() {
return nil, E.New("invalid address")
}
if d.networkStrategy == nil {
if d.networkStrategy == C.NetworkStrategyDefault {
switch N.NetworkName(network) {
case N.NetworkUDP:
if !address.IsIPv6() {
@@ -228,21 +223,12 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
}
}
func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if strategy == nil {
strategy = d.networkStrategy
}
if strategy == nil {
func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if strategy == C.NetworkStrategyDefault {
return d.DialContext(ctx, network, address)
}
if len(interfaceType) == 0 {
interfaceType = d.networkType
}
if len(fallbackInterfaceType) == 0 {
fallbackInterfaceType = d.fallbackNetworkType
}
if fallbackDelay == 0 {
fallbackDelay = d.networkFallbackDelay
if !d.networkManager.AutoDetectInterface() {
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
}
var dialer net.Dialer
if N.NetworkName(network) == N.NetworkTCP {
@@ -257,9 +243,9 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
err error
)
if !fastFallback {
conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
} else {
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
}
if err != nil {
return nil, err
@@ -271,7 +257,7 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
}
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if d.networkStrategy == nil {
if d.networkStrategy == C.NetworkStrategyDefault {
if destination.IsIPv6() {
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
@@ -284,27 +270,18 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
}
}
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
if strategy == nil {
strategy = d.networkStrategy
}
if strategy == nil {
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
if strategy == C.NetworkStrategyDefault {
return d.ListenPacket(ctx, destination)
}
if len(interfaceType) == 0 {
interfaceType = d.networkType
}
if len(fallbackInterfaceType) == 0 {
fallbackInterfaceType = d.fallbackNetworkType
}
if fallbackDelay == 0 {
fallbackDelay = d.networkFallbackDelay
if !d.networkManager.AutoDetectInterface() {
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
}
network := N.NetworkUDP
if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
network += "4"
}
return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", *strategy, interfaceType, fallbackInterfaceType, fallbackDelay))
return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", strategy, interfaceType, fallbackInterfaceType, fallbackDelay))
}
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {

View File

@@ -40,7 +40,7 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
}
} else {
select {
case results <- dialResult{Conn: conn, primary: primary}:
case results <- dialResult{Conn: conn}:
case <-returned:
conn.Close()
}
@@ -112,7 +112,7 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
}
} else {
select {
case results <- dialResult{Conn: conn, primary: primary}:
case results <- dialResult{Conn: conn}:
case <-returned:
if primary && time.Since(startAt) <= fallbackDelay {
resetFastFallback(time.Time{})
@@ -180,19 +180,21 @@ func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkS
for _, iif := range interfaces {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
} else {
fallbackInterfaces = append(fallbackInterfaces, iif)
}
}
} else {
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return common.Contains(interfaceType, it.Type)
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
})
}
case C.NetworkStrategyHybrid:
if len(interfaceType) == 0 {
primaryInterfaces = interfaces
} else {
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return common.Contains(interfaceType, it.Type)
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
})
}
case C.NetworkStrategyFallback:
@@ -201,25 +203,18 @@ func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkS
for _, iif := range interfaces {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
break
} else {
fallbackInterfaces = append(fallbackInterfaces, iif)
}
}
} else {
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return common.Contains(interfaceType, it.Type)
})
}
if len(fallbackInterfaceType) == 0 {
fallbackInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return !common.Any(primaryInterfaces, func(iif adapter.NetworkInterface) bool {
return it.Index == iif.Index
})
})
} else {
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(fallbackInterfaceType, iif.Type)
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
})
}
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(fallbackInterfaceType, iif.Type)
})
}
return primaryInterfaces, fallbackInterfaces
}

View File

@@ -13,13 +13,7 @@ import (
N "github.com/sagernet/sing/common/network"
)
func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if len(destinationAddresses) == 0 {
if !destination.IsIP() {
panic("invalid usage")
}
destinationAddresses = []netip.Addr{destination.Addr}
}
func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}
@@ -44,14 +38,7 @@ func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, des
return nil, E.Errors(errors...)
}
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if len(destinationAddresses) == 0 {
if !destination.IsIP() {
panic("invalid usage")
}
destinationAddresses = []netip.Addr{destination.Addr}
}
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
@@ -129,13 +116,7 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne
}
}
func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
if len(destinationAddresses) == 0 {
if !destination.IsIP() {
panic("invalid usage")
}
destinationAddresses = []netip.Addr{destination.Addr}
}
func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}

View File

@@ -17,15 +17,16 @@ import (
)
func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
networkManager := service.FromContext[adapter.NetworkManager](ctx)
if options.IsWireGuardListener {
return NewDefault(ctx, options)
return NewDefault(networkManager, options)
}
var (
dialer N.Dialer
err error
)
if options.Detour == "" {
dialer, err = NewDefault(ctx, options)
dialer, err = NewDefault(networkManager, options)
if err != nil {
return nil, err
}
@@ -36,6 +37,9 @@ func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
}
dialer = NewDetour(outboundManager, options.Detour)
}
if networkManager == nil {
return NewDefault(networkManager, options)
}
if options.Detour == "" {
router := service.FromContext[adapter.Router](ctx)
if router != nil {
@@ -54,10 +58,11 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter
if options.Detour != "" {
return nil, E.New("`detour` is not supported in direct context")
}
networkManager := service.FromContext[adapter.NetworkManager](ctx)
if options.IsWireGuardListener {
return NewDefault(ctx, options)
return NewDefault(networkManager, options)
}
dialer, err := NewDefault(ctx, options)
dialer, err := NewDefault(networkManager, options)
if err != nil {
return nil, err
}
@@ -72,11 +77,11 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter
type ParallelInterfaceDialer interface {
N.Dialer
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error)
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error)
}
type ParallelNetworkDialer interface {
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
}

View File

@@ -106,7 +106,7 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
}
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if !destination.IsFqdn() {
return d.dialer.DialContext(ctx, network, destination)
}
@@ -134,7 +134,7 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
}
}
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
if !destination.IsFqdn() {
return d.dialer.ListenPacket(ctx, destination)
}

View File

@@ -30,14 +30,15 @@ func NewClient(ctx context.Context, serverAddress string, options option.Outboun
return nil, nil
}
if options.ECH != nil && options.ECH.Enabled {
return NewECHClient(ctx, serverAddress, options)
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
return NewECHClient(ctx, serverAddress, options)
}
} else if options.Reality != nil && options.Reality.Enabled {
return NewRealityClient(ctx, serverAddress, options)
} else if options.UTLS != nil && options.UTLS.Enabled {
return NewUTLSClient(ctx, serverAddress, options)
} else {
return NewSTDClient(ctx, serverAddress, options)
}
return NewSTDClient(ctx, serverAddress, options)
}
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {

View File

@@ -7,7 +7,6 @@ import (
"encoding/binary"
"encoding/pem"
cftls "github.com/sagernet/cloudflare-tls"
E "github.com/sagernet/sing/common/exceptions"
"github.com/cloudflare/circl/hpke"
@@ -59,7 +58,6 @@ func ECHKeygenDefault(serverName string, pqSignatureSchemesEnabled bool) (config
type echKeyConfigPair struct {
id uint8
key cftls.EXP_ECHKey
rawKey []byte
conf myECHKeyConfig
rawConf []byte
@@ -153,14 +151,13 @@ func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite [
sk = be.AppendUint16(sk, uint16(len(b)))
sk = append(sk, b...)
cfECHKeys, err := cftls.EXP_UnmarshalECHKeys(sk)
cfECHKeys, err := UnmarshalECHKeys(sk)
if err != nil {
return nil, E.Cause(err, "bug: can't parse generated ECH server key")
}
if len(cfECHKeys) != 1 {
return nil, E.New("bug: unexpected server key count")
}
pair.key = cfECHKeys[0]
pair.rawKey = sk
pairs = append(pairs, pair)

View File

@@ -17,12 +17,13 @@ func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLS
return nil, nil
}
if options.ECH != nil && options.ECH.Enabled {
return NewECHServer(ctx, logger, options)
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
return NewECHServer(ctx, logger, options)
}
} else if options.Reality != nil && options.Reality.Enabled {
return NewRealityServer(ctx, logger, options)
} else {
return NewSTDServer(ctx, logger, options)
}
return NewSTDServer(ctx, logger, options)
}
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {

View File

@@ -4,16 +4,25 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"net"
"net/netip"
"os"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
aTLS "github.com/sagernet/sing/common/tls"
"github.com/sagernet/sing/service"
mDNS "github.com/miekg/dns"
)
var _ ConfigCompat = (*STDClientConfig)(nil)
type STDClientConfig struct {
config *tls.Config
}
@@ -46,6 +55,63 @@ func (s *STDClientConfig) Clone() Config {
return &STDClientConfig{s.config.Clone()}
}
type STDECHClientConfig struct {
STDClientConfig
}
func (s *STDClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
if len(s.config.EncryptedClientHelloConfigList) == 0 {
message := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
RecursionDesired: true,
},
Question: []mDNS.Question{
{
Name: mDNS.Fqdn(s.config.ServerName),
Qtype: mDNS.TypeHTTPS,
Qclass: mDNS.ClassINET,
},
},
}
dnsRouter := service.FromContext[adapter.Router](ctx)
response, err := dnsRouter.Exchange(ctx, message)
if err != nil {
return nil, E.Cause(err, "fetch ECH config list")
}
if response.Rcode != mDNS.RcodeSuccess {
return nil, E.Cause(dns.RCodeError(response.Rcode), "fetch ECH config list")
}
for _, rr := range response.Answer {
switch resource := rr.(type) {
case *mDNS.HTTPS:
for _, value := range resource.Value {
if value.Key().String() == "ech" {
echConfigList, err := base64.StdEncoding.DecodeString(value.String())
if err != nil {
return nil, E.Cause(err, "decode ECH config")
}
s.config.EncryptedClientHelloConfigList = echConfigList
}
}
}
}
return nil, E.New("no ECH config found in DNS records")
}
tlsConn, err := s.Client(conn)
if err != nil {
return nil, err
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
return tlsConn, nil
}
func (s *STDECHClientConfig) Clone() Config {
return &STDECHClientConfig{STDClientConfig{s.config.Clone()}}
}
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string
if options.ServerName != "" {
@@ -128,5 +194,21 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
}
tlsConfig.RootCAs = certPool
}
if options.ECH != nil && options.ECH.Enabled {
var echConfig []byte
if len(options.ECH.Config) > 0 {
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
} else if options.ECH.ConfigPath != "" {
content, err := os.ReadFile(options.ECH.ConfigPath)
if err != nil {
return nil, E.Cause(err, "read ECH config")
}
echConfig = content
}
if echConfig != nil {
tlsConfig.EncryptedClientHelloConfigList = echConfig
}
return &STDECHClientConfig{STDClientConfig{&tlsConfig}}, nil
}
return &STDClientConfig{&tlsConfig}, nil
}

View File

@@ -3,6 +3,7 @@ package tls
import (
"context"
"crypto/tls"
"encoding/pem"
"net"
"os"
"strings"
@@ -14,6 +15,8 @@ import (
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
"golang.org/x/crypto/cryptobyte"
)
var errInsecureUnused = E.New("tls: insecure unused")
@@ -238,6 +241,31 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
tlsConfig.Certificates = []tls.Certificate{keyPair}
}
}
if options.ECH != nil && options.ECH.Enabled {
var echKey []byte
if len(options.ECH.Key) > 0 {
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
} else if options.ECH.KeyPath != "" {
content, err := os.ReadFile(options.ECH.KeyPath)
if err != nil {
return nil, E.Cause(err, "read ECH key")
}
echKey = content
} else {
return nil, E.New("missing ECH key")
}
block, rest := pem.Decode(echKey)
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
return nil, E.New("invalid ECH keys pem")
}
echKeys, err := UnmarshalECHKeys(block.Bytes)
if err != nil {
return nil, E.Cause(err, "parse ECH keys")
}
tlsConfig.EncryptedClientHelloKeys = echKeys
}
return &STDServerConfig{
config: tlsConfig,
logger: logger,
@@ -248,3 +276,22 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
keyPath: options.KeyPath,
}, nil
}
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
var keys []tls.EncryptedClientHelloKey
rawString := cryptobyte.String(raw)
for !rawString.Empty() {
var key tls.EncryptedClientHelloKey
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.PrivateKey)) {
return nil, E.New("error parsing private key")
}
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.Config)) {
return nil, E.New("error parsing config")
}
keys = append(keys, key)
}
if len(keys) == 0 {
return nil, E.New("empty ECH keys")
}
return keys, nil
}

View File

@@ -1,22 +0,0 @@
package tls
import (
"time"
"github.com/sagernet/sing/common/ntp"
)
type TimeServiceWrapper struct {
ntp.TimeService
}
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
if w.TimeService == nil {
return nil
}
return w.TimeService.TimeFunc()
}
func (w *TimeServiceWrapper) Upstream() any {
return w.TimeService
}

View File

@@ -2,11 +2,7 @@
icon: material/alert-decagram
---
#### 1.11.0-beta.11
* Fixes and improvements
### 1.10.4
#### 1.11.0-beta.9
* Fixes and improvements
@@ -19,6 +15,10 @@ icon: material/alert-decagram
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
### 1.10.3
* Fixes and improvements
#### 1.11.0-alpha.25
* Update quic-go to v0.48.2

View File

@@ -88,13 +88,13 @@ icon: material/alert-decagram
0
],
"include_uid_range": [
"1000:99999"
"1000-99999"
],
"exclude_uid": [
1000
],
"exclude_uid_range": [
"1000:99999"
"1000-99999"
],
"include_android_user": [
0,

View File

@@ -88,13 +88,13 @@ icon: material/alert-decagram
0
],
"include_uid_range": [
"1000:99999"
"1000-99999"
],
"exclude_uid": [
1000
],
"exclude_uid_range": [
"1000:99999"
"1000-99999"
],
"include_android_user": [
0,

View File

@@ -128,8 +128,11 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
if options.ExternalUI != "" {
s.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
chiRouter.Group(func(r chi.Router) {
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusMovedPermanently).ServeHTTP)
r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(http.Dir(s.externalUI))))
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(s.externalUI)))
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
})
})
}
return s, nil

View File

@@ -1,11 +1,8 @@
package deprecated
import (
"fmt"
"github.com/sagernet/sing-box/common/badversion"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/locale"
F "github.com/sagernet/sing/common/format"
"golang.org/x/mod/semver"
@@ -37,9 +34,15 @@ func (n Note) Impending() bool {
func (n Note) Message() string {
if n.MigrationLink != "" {
return fmt.Sprintf(locale.Current().DeprecatedMessage, n.Description, n.DeprecatedVersion, n.ScheduledVersion)
return F.ToString(
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
" and will be removed in sing-box ", n.ScheduledVersion, ", please checkout documentation for migration.",
)
} else {
return fmt.Sprintf(locale.Current().DeprecatedMessageNoLink, n.Description, n.DeprecatedVersion, n.ScheduledVersion)
return F.ToString(
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
" and will be removed in sing-box ", n.ScheduledVersion, ".",
)
}
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/sagernet/sing-box/common/humanize"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/locale"
"github.com/sagernet/sing-box/log"
)
@@ -54,10 +53,6 @@ func SetupWithUsername(basePath string, workingPath string, tempPath string, use
return nil
}
func SetLocale(localeId string) {
locale.Set(localeId)
}
func Version() string {
return C.Version
}

View File

@@ -13,7 +13,7 @@ import (
type TunOptions interface {
GetInet4Address() RoutePrefixIterator
GetInet6Address() RoutePrefixIterator
GetDNSServerAddress() (*StringBox, error)
GetDNSServerAddress() (string, error)
GetMTU() int32
GetAutoRoute() bool
GetStrictRoute() bool
@@ -89,11 +89,11 @@ func (o *tunOptions) GetInet6Address() RoutePrefixIterator {
return mapRoutePrefix(o.Inet6Address)
}
func (o *tunOptions) GetDNSServerAddress() (*StringBox, error) {
func (o *tunOptions) GetDNSServerAddress() (string, error) {
if len(o.Inet4Address) == 0 || o.Inet4Address[0].Bits() == 32 {
return nil, E.New("need one more IPv4 address for DNS hijacking")
return "", E.New("need one more IPv4 address for DNS hijacking")
}
return wrapString(o.Inet4Address[0].Addr().Next().String()), nil
return o.Inet4Address[0].Addr().Next().String(), nil
}
func (o *tunOptions) GetMTU() int32 {

View File

@@ -1,30 +0,0 @@
package locale
var (
localeRegistry = make(map[string]*Locale)
current = defaultLocal
)
type Locale struct {
// deprecated messages for graphical clients
DeprecatedMessage string
DeprecatedMessageNoLink string
}
var defaultLocal = &Locale{
DeprecatedMessage: "%s is deprecated in sing-box %s and will be removed in sing-box %s please checkout documentation for migration.",
DeprecatedMessageNoLink: "%s is deprecated in sing-box %s and will be removed in sing-box %s.",
}
func Current() *Locale {
return current
}
func Set(localeId string) bool {
locale, loaded := localeRegistry[localeId]
if !loaded {
return false
}
current = locale
return true
}

View File

@@ -1,10 +0,0 @@
package locale
var warningMessageForEndUsers = "\n\n如果您不明白此消息意味着什么您的配置文件已过时且将很快不可用。请联系您的配置提供者以更新配置。"
func init() {
localeRegistry["zh_CN"] = &Locale{
DeprecatedMessage: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除,请参阅迁移指南。" + warningMessageForEndUsers,
DeprecatedMessageNoLink: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除。" + warningMessageForEndUsers,
}
}

8
go.mod
View File

@@ -4,13 +4,11 @@ go 1.20
require (
github.com/caddyserver/certmagic v0.20.0
github.com/cidertool/asc-go v0.5.1
github.com/cloudflare/circl v1.3.7
github.com/cretz/bine v0.2.0
github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/render v1.0.3
github.com/gofrs/uuid/v5 v5.3.0
github.com/google/go-querystring v1.0.0
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
github.com/libdns/alidns v1.0.3
github.com/libdns/cloudflare v0.1.1
@@ -27,14 +25,14 @@ require (
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
github.com/sagernet/quic-go v0.48.2-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.6.0-beta.8
github.com/sagernet/sing v0.6.0-beta.6
github.com/sagernet/sing-dns v0.4.0-beta.1
github.com/sagernet/sing-mux v0.3.0-alpha.1
github.com/sagernet/sing-quic v0.4.0-alpha.4
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2
github.com/sagernet/sing-tun v0.6.0-beta.6
github.com/sagernet/sing-tun v0.6.0-beta.2
github.com/sagernet/sing-vmess v0.2.0-beta.1
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/utls v1.6.7
@@ -60,9 +58,7 @@ require (
require (
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect

18
go.sum
View File

@@ -4,10 +4,6 @@ github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sx
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cidertool/asc-go v0.5.1 h1:KYki2Y8IXJMOkOXy9y1sdr8tz6IdW2ti770K4bk7WY0=
github.com/cidertool/asc-go v0.5.1/go.mod h1:LyrZWU7DeCh8cWrFwXcpl93ixRUUL2aEZV7/0h07FxA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -16,8 +12,6 @@ github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbe
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
@@ -40,8 +34,6 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
@@ -118,8 +110,8 @@ github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.6.0-beta.8 h1:PoxDdN7y8D4oImT3cQ05Sq1ZYnYsJberkUkIEHIGwWE=
github.com/sagernet/sing v0.6.0-beta.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.6.0-beta.6 h1:IFnTCG06Z5rLMZJqw1ZmDncDl2N9gsVw0MGvgakrpg8=
github.com/sagernet/sing v0.6.0-beta.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.0-beta.1 h1:W1XkdhigwxDOMgMDVB+9kdomCpb7ExsZfB4acPcTZFY=
github.com/sagernet/sing-dns v0.4.0-beta.1/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
@@ -132,8 +124,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA=
github.com/sagernet/sing-tun v0.6.0-beta.6 h1:xaIHoH78MqTSvZqQ4SQto8pC1A+X4qXReDRNaC8DQeI=
github.com/sagernet/sing-tun v0.6.0-beta.6/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-tun v0.6.0-beta.2 h1:GK7r2jWKm7RhlJGTq4QadgFcebQia1c3BO3OlYMcQJ0=
github.com/sagernet/sing-tun v0.6.0-beta.2/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-vmess v0.2.0-beta.1 h1:5sXQ23uwNlZuDvygzi0dFtnG0Csm/SNqTjAHXJkpuj4=
github.com/sagernet/sing-vmess v0.2.0-beta.1/go.mod h1:fLyE1emIcvQ5DV8reFWnufquZ7MkCSYM5ThodsR9NrQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
@@ -203,8 +195,6 @@ golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=

View File

@@ -65,24 +65,25 @@ type DialerOptionsWrapper interface {
}
type DialerOptions struct {
Detour string `json:"detour,omitempty"`
BindInterface string `json:"bind_interface,omitempty"`
Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"`
Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"`
ProtectPath string `json:"protect_path,omitempty"`
RoutingMark FwMark `json:"routing_mark,omitempty"`
ReuseAddr bool `json:"reuse_addr,omitempty"`
ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
UDPFragment *bool `json:"udp_fragment,omitempty"`
UDPFragmentDefault bool `json:"-"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"`
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"`
FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"`
IsWireGuardListener bool `json:"-"`
Detour string `json:"detour,omitempty"`
BindInterface string `json:"bind_interface,omitempty"`
Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"`
Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"`
ProtectPath string `json:"protect_path,omitempty"`
RoutingMark FwMark `json:"routing_mark,omitempty"`
ReuseAddr bool `json:"reuse_addr,omitempty"`
ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
UDPFragment *bool `json:"udp_fragment,omitempty"`
UDPFragmentDefault bool `json:"-"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"`
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"`
FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"`
NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"`
IsWireGuardListener bool `json:"-"`
}
func (o *DialerOptions) TakeDialerOptions() DialerOptions {

View File

@@ -13,7 +13,7 @@ type RouteOptions struct {
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
DefaultInterface string `json:"default_interface,omitempty"`
DefaultMark FwMark `json:"default_mark,omitempty"`
DefaultNetworkStrategy *NetworkStrategy `json:"default_network_strategy,omitempty"`
DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"`
DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"`
DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"`
DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,omitempty"`

View File

@@ -145,8 +145,8 @@ type RawRouteOptionsActionOptions struct {
OverrideAddress string `json:"override_address,omitempty"`
OverridePort uint16 `json:"override_port,omitempty"`
NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"`
FallbackDelay uint32 `json:"fallback_delay,omitempty"`
NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"`
FallbackDelay uint32 `json:"fallback_delay,omitempty"`
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
UDPConnect bool `json:"udp_connect,omitempty"`

View File

@@ -32,12 +32,16 @@ var (
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
dialer dialer.ParallelInterfaceDialer
domainStrategy dns.DomainStrategy
fallbackDelay time.Duration
overrideOption int
overrideDestination M.Socksaddr
logger logger.ContextLogger
dialer dialer.ParallelInterfaceDialer
domainStrategy dns.DomainStrategy
fallbackDelay time.Duration
networkStrategy C.NetworkStrategy
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
overrideOption int
overrideDestination M.Socksaddr
// loopBack *loopBackDetector
}
@@ -48,11 +52,15 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return nil, err
}
outbound := &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
logger: logger,
domainStrategy: dns.DomainStrategy(options.DomainStrategy),
fallbackDelay: time.Duration(options.FallbackDelay),
dialer: outboundDialer,
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
logger: logger,
domainStrategy: dns.DomainStrategy(options.DomainStrategy),
fallbackDelay: time.Duration(options.FallbackDelay),
networkStrategy: C.NetworkStrategy(options.NetworkStrategy),
networkType: common.Map(options.NetworkType, option.InterfaceType.Build),
fallbackNetworkType: common.Map(options.FallbackNetworkType, option.InterfaceType.Build),
networkFallbackDelay: time.Duration(options.NetworkFallbackDelay),
dialer: outboundDialer,
// loopBack: newLoopBackDetector(router),
}
//nolint:staticcheck
@@ -170,10 +178,10 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination
return nil, E.New("no IPv6 address available for ", destination)
}
}
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, nil, nil, nil, h.fallbackDelay)
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.networkStrategy, h.networkType, h.fallbackNetworkType, h.fallbackDelay)
}
func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag()
metadata.Destination = destination
@@ -213,7 +221,7 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, networkStrategy, networkType, fallbackNetworkType, fallbackDelay)
}
func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag()
metadata.Destination = destination

View File

@@ -82,16 +82,16 @@ func (h *Inbound) Close() error {
}
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
var err error
if h.tlsConfig != nil {
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake"))
return
}
conn = tlsConn
}
err := http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose)
err = http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))

View File

@@ -110,19 +110,11 @@ func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketC
metadata.InboundType = h.Type()
user, loaded := auth.UserFromContext[string](ctx)
if !loaded {
if !metadata.Destination.IsValid() {
h.logger.InfoContext(ctx, "inbound packet connection")
} else {
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
}
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
return
}
metadata.User = user
if !metadata.Destination.IsValid() {
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection")
} else {
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
}
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}

View File

@@ -92,19 +92,11 @@ func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketC
metadata.InboundType = h.Type()
user, loaded := auth.UserFromContext[string](ctx)
if !loaded {
if !metadata.Destination.IsValid() {
h.logger.InfoContext(ctx, "inbound packet connection")
} else {
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
}
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
return
}
metadata.User = user
if !metadata.Destination.IsValid() {
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection")
} else {
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
}
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}

View File

@@ -159,16 +159,16 @@ func (h *Inbound) Close() error {
}
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
var err error
if h.tlsConfig != nil && h.transport == nil {
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake"))
return
}
conn = tlsConn
}
err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
err = h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))

View File

@@ -139,16 +139,16 @@ func (h *Inbound) Close() error {
}
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
var err error
if h.tlsConfig != nil && h.transport == nil {
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake"))
return
}
conn = tlsConn
}
err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
err = h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))

View File

@@ -153,16 +153,16 @@ func (h *Inbound) Close() error {
}
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
var err error
if h.tlsConfig != nil && h.transport == nil {
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake"))
return
}
conn = tlsConn
}
err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
err = h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))

View File

@@ -20,6 +20,7 @@ import (
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
func RegisterEndpoint(registry *endpoint.Registry) {
@@ -69,7 +70,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
UDPTimeout: udpTimeout,
Dialer: outboundDialer,
CreateDialer: func(interfaceName string) N.Dialer {
return common.Must1(dialer.NewDefault(ctx, option.DialerOptions{
return common.Must1(dialer.NewDefault(service.FromContext[adapter.NetworkManager](ctx), option.DialerOptions{
BindInterface: interfaceName,
}))
},

View File

@@ -19,6 +19,7 @@ import (
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
func RegisterOutbound(registry *outbound.Registry) {
@@ -85,7 +86,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
System: options.SystemInterface,
Dialer: outboundDialer,
CreateDialer: func(interfaceName string) N.Dialer {
return common.Must1(dialer.NewDefault(ctx, option.DialerOptions{
return common.Must1(dialer.NewDefault(service.FromContext[adapter.NetworkManager](ctx), option.DialerOptions{
BindInterface: interfaceName,
}))
},

View File

@@ -56,7 +56,7 @@ func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, co
remoteConn net.Conn
err error
)
if len(metadata.DestinationAddresses) > 0 || metadata.Destination.IsIP() {
if len(metadata.DestinationAddresses) > 0 {
remoteConn, err = dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} else {
remoteConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
@@ -97,7 +97,7 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial
err error
)
if metadata.UDPConnect {
if len(metadata.DestinationAddresses) > 0 || metadata.Destination.IsIP() {
if len(metadata.DestinationAddresses) > 0 {
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer {
remoteConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} else {

View File

@@ -62,7 +62,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp
defaultOptions: adapter.NetworkOptions{
BindInterface: routeOptions.DefaultInterface,
RoutingMark: uint32(routeOptions.DefaultMark),
NetworkStrategy: (*C.NetworkStrategy)(routeOptions.DefaultNetworkStrategy),
NetworkStrategy: C.NetworkStrategy(routeOptions.DefaultNetworkStrategy),
NetworkType: common.Map(routeOptions.DefaultNetworkType, option.InterfaceType.Build),
FallbackNetworkType: common.Map(routeOptions.DefaultFallbackNetworkType, option.InterfaceType.Build),
FallbackDelay: time.Duration(routeOptions.DefaultFallbackDelay),
@@ -73,7 +73,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp
inbound: service.FromContext[adapter.InboundManager](ctx),
outbound: service.FromContext[adapter.OutboundManager](ctx),
}
if routeOptions.DefaultNetworkStrategy != nil {
if C.NetworkStrategy(routeOptions.DefaultNetworkStrategy) != C.NetworkStrategyDefault {
if routeOptions.DefaultInterface != "" {
return nil, E.New("`default_network_strategy` is conflict with `default_interface`")
}

View File

@@ -415,18 +415,8 @@ match:
Fqdn: metadata.Destination.Fqdn,
}
}
if routeOptions.NetworkStrategy != nil {
metadata.NetworkStrategy = routeOptions.NetworkStrategy
}
if len(routeOptions.NetworkType) > 0 {
metadata.NetworkType = routeOptions.NetworkType
}
if len(routeOptions.FallbackNetworkType) > 0 {
metadata.FallbackNetworkType = routeOptions.FallbackNetworkType
}
if routeOptions.FallbackDelay != 0 {
metadata.FallbackDelay = routeOptions.FallbackDelay
}
metadata.NetworkStrategy = routeOptions.NetworkStrategy
metadata.FallbackDelay = routeOptions.FallbackDelay
if routeOptions.UDPDisableDomainUnmapping {
metadata.UDPDisableDomainUnmapping = true
}

View File

@@ -45,70 +45,69 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
panic("no context")
}
var options dns.QueryOptions
var (
currentRuleIndex int
currentRule adapter.DNSRule
)
if ruleIndex != -1 {
currentRuleIndex = ruleIndex + 1
}
for currentRuleIndex, currentRule = range r.dnsRules[currentRuleIndex:] {
if currentRule.WithAddressLimit() && !isAddressQuery {
continue
if ruleIndex < len(r.dnsRules) {
dnsRules := r.dnsRules
if ruleIndex != -1 {
dnsRules = dnsRules[ruleIndex+1:]
}
metadata.ResetRuleCache()
if currentRule.Match(metadata) {
displayRuleIndex := currentRuleIndex
if ruleIndex != -1 {
displayRuleIndex += ruleIndex + 1
for currentRuleIndex, currentRule := range dnsRules {
if currentRule.WithAddressLimit() && !isAddressQuery {
continue
}
ruleDescription := currentRule.String()
if ruleDescription != "" {
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] ", currentRule, " => ", currentRule.Action())
} else {
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
}
switch action := currentRule.Action().(type) {
case *R.RuleActionDNSRoute:
transport, loaded := r.transportMap[action.Server]
if !loaded {
r.dnsLogger.ErrorContext(ctx, "transport not found: ", action.Server)
continue
metadata.ResetRuleCache()
if currentRule.Match(metadata) {
displayRuleIndex := currentRuleIndex
if displayRuleIndex != -1 {
displayRuleIndex += displayRuleIndex + 1
}
_, isFakeIP := transport.(adapter.FakeIPTransport)
if isFakeIP && !allowFakeIP {
continue
}
if isFakeIP || action.DisableCache {
options.DisableCache = true
}
if action.RewriteTTL != nil {
options.RewriteTTL = action.RewriteTTL
}
if action.ClientSubnet.IsValid() {
options.ClientSubnet = action.ClientSubnet
}
if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded {
options.Strategy = domainStrategy
ruleDescription := currentRule.String()
if ruleDescription != "" {
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] ", currentRule, " => ", currentRule.Action())
} else {
options.Strategy = r.defaultDomainStrategy
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
}
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
return transport, options, currentRule, currentRuleIndex
case *R.RuleActionDNSRouteOptions:
if action.DisableCache {
options.DisableCache = true
switch action := currentRule.Action().(type) {
case *R.RuleActionDNSRoute:
transport, loaded := r.transportMap[action.Server]
if !loaded {
r.dnsLogger.ErrorContext(ctx, "transport not found: ", action.Server)
continue
}
_, isFakeIP := transport.(adapter.FakeIPTransport)
if isFakeIP && !allowFakeIP {
continue
}
if isFakeIP || action.DisableCache {
options.DisableCache = true
}
if action.RewriteTTL != nil {
options.RewriteTTL = action.RewriteTTL
}
if action.ClientSubnet.IsValid() {
options.ClientSubnet = action.ClientSubnet
}
if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded {
options.Strategy = domainStrategy
} else {
options.Strategy = r.defaultDomainStrategy
}
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
return transport, options, currentRule, currentRuleIndex
case *R.RuleActionDNSRouteOptions:
if action.DisableCache {
options.DisableCache = true
}
if action.RewriteTTL != nil {
options.RewriteTTL = action.RewriteTTL
}
if action.ClientSubnet.IsValid() {
options.ClientSubnet = action.ClientSubnet
}
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
case *R.RuleActionReject:
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
return nil, options, currentRule, currentRuleIndex
}
if action.RewriteTTL != nil {
options.RewriteTTL = action.RewriteTTL
}
if action.ClientSubnet.IsValid() {
options.ClientSubnet = action.ClientSubnet
}
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
case *R.RuleActionReject:
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
return nil, options, currentRule, currentRuleIndex
}
}
}

View File

@@ -262,7 +262,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route
Context: ctx,
Name: "local",
Address: "local",
Dialer: common.Must1(dialer.NewDefault(ctx, option.DialerOptions{})),
Dialer: common.Must1(dialer.NewDefault(router.network, option.DialerOptions{})),
})))
}
defaultTransport = transports[0]

View File

@@ -33,7 +33,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
RuleActionRouteOptions: RuleActionRouteOptions{
OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptions.OverrideAddress, 0),
OverridePort: action.RouteOptions.OverridePort,
NetworkStrategy: (*C.NetworkStrategy)(action.RouteOptions.NetworkStrategy),
NetworkStrategy: C.NetworkStrategy(action.RouteOptions.NetworkStrategy),
FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay),
UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping,
UDPConnect: action.RouteOptions.UDPConnect,
@@ -43,7 +43,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
return &RuleActionRouteOptions{
OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptionsOptions.OverrideAddress, 0),
OverridePort: action.RouteOptionsOptions.OverridePort,
NetworkStrategy: (*C.NetworkStrategy)(action.RouteOptionsOptions.NetworkStrategy),
NetworkStrategy: C.NetworkStrategy(action.RouteOptionsOptions.NetworkStrategy),
FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay),
UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping,
UDPConnect: action.RouteOptionsOptions.UDPConnect,
@@ -147,7 +147,7 @@ func (r *RuleActionRoute) String() string {
type RuleActionRouteOptions struct {
OverrideAddress M.Socksaddr
OverridePort uint16
NetworkStrategy *C.NetworkStrategy
NetworkStrategy C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration

View File

@@ -253,7 +253,7 @@ func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []neti
return it.String()
}), ","), "]")
}
serverDialer := common.Must1(dialer.NewDefault(t.options.Context, option.DialerOptions{
serverDialer := common.Must1(dialer.NewDefault(t.networkManager, option.DialerOptions{
BindInterface: iface.Name,
UDPFragmentDefault: true,
}))