mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-11-06 10:12:37 +00:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb6629dc5c | ||
|
|
87237191f3 | ||
|
|
fc2cf83ded | ||
|
|
e61c67cbc2 | ||
|
|
406e089d13 | ||
|
|
256adf4a94 | ||
|
|
a6cf3697c3 | ||
|
|
9a42943070 | ||
|
|
e812102bda | ||
|
|
8ab8734614 | ||
|
|
5b92b7183f | ||
|
|
9aff92b89a |
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get latest go version
|
- name: Get latest go version
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -199,7 +199,7 @@ jobs:
|
|||||||
TAGS: with_clash_api,with_quic
|
TAGS: with_clash_api,with_quic
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get latest go version
|
- name: Get latest go version
|
||||||
|
|||||||
@@ -9,20 +9,20 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@v3
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Setup QEMU for Docker Buildx
|
- name: Setup QEMU for Docker Buildx
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v2
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Docker metadata
|
- name: Docker metadata
|
||||||
id: metadata
|
id: metadata
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/sagernet/sing-box
|
images: ghcr.io/sagernet/sing-box
|
||||||
- name: Get tag to build
|
- name: Get tag to build
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
- name: Build and release Docker images
|
- name: Build and release Docker images
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
||||||
target: dist
|
target: dist
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get latest go version
|
- name: Get latest go version
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ RUN set -ex \
|
|||||||
&& apk add git build-base \
|
&& apk add git build-base \
|
||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& go build -v -trimpath -tags with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api,with_acme \
|
&& go build -v -trimpath -tags with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_clash_api,with_acme \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api
|
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api
|
||||||
TAGS_GO120 = with_quic
|
TAGS_GO120 = with_quic,with_ech
|
||||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
|
TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ update_android_version:
|
|||||||
go run ./cmd/internal/update_android_version
|
go run ./cmd/internal/update_android_version
|
||||||
|
|
||||||
build_android:
|
build_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:assembleRelease && ./gradlew --stop
|
cd ../sing-box-for-android && ./gradlew :app:assembleRelease
|
||||||
|
|
||||||
upload_android:
|
upload_android:
|
||||||
mkdir -p dist/release_android
|
mkdir -p dist/release_android
|
||||||
@@ -84,7 +84,7 @@ upload_android:
|
|||||||
release_android: lib_android update_android_version build_android upload_android
|
release_android: lib_android update_android_version build_android upload_android
|
||||||
|
|
||||||
publish_android:
|
publish_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadRelease && ./gradlew --stop
|
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadRelease
|
||||||
|
|
||||||
build_ios:
|
build_ios:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
@@ -137,6 +137,7 @@ release_macos_independent: build_macos_independent notarize_macos_independent wa
|
|||||||
build_tvos:
|
build_tvos:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFT.xcarchive && \
|
rm -rf build/SFT.xcarchive && \
|
||||||
|
export DEVELOPER_DIR=/Applications/Xcode-beta.app/Contents/Developer && \
|
||||||
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive
|
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive
|
||||||
|
|
||||||
upload_tvos_app_store:
|
upload_tvos_app_store:
|
||||||
@@ -178,8 +179,8 @@ lib:
|
|||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go get -v -d
|
go get -v -d
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230915142329-c6740b6d2950
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230728014906-3de089147f59
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230915142329-c6740b6d2950
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230728014906-3de089147f59
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist sing-box
|
rm -rf bin dist sing-box
|
||||||
|
|||||||
+3
-10
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
mdns "github.com/miekg/dns"
|
mdns "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -45,8 +46,6 @@ type Router interface {
|
|||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
|
||||||
TimeService
|
|
||||||
|
|
||||||
ClashServer() ClashServer
|
ClashServer() ClashServer
|
||||||
SetClashServer(server ClashServer)
|
SetClashServer(server ClashServer)
|
||||||
|
|
||||||
@@ -56,18 +55,12 @@ type Router interface {
|
|||||||
ResetNetwork() error
|
ResetNetwork() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type routerContextKey struct{}
|
|
||||||
|
|
||||||
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
||||||
return context.WithValue(ctx, (*routerContextKey)(nil), router)
|
return service.ContextWith(ctx, router)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RouterFromContext(ctx context.Context) Router {
|
func RouterFromContext(ctx context.Context) Router {
|
||||||
metadata := ctx.Value((*routerContextKey)(nil))
|
return service.FromContext[Router](ctx)
|
||||||
if metadata == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return metadata.(Router)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ func New(options Options) (*Box, error) {
|
|||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
}
|
}
|
||||||
|
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||||
ctx = pause.ContextWithDefaultManager(ctx)
|
ctx = pause.ContextWithDefaultManager(ctx)
|
||||||
createdAt := time.Now()
|
createdAt := time.Now()
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pqSignatureSchemesEnabled bool
|
||||||
|
|
||||||
|
var commandGenerateECHKeyPair = &cobra.Command{
|
||||||
|
Use: "ech-keypair <plain_server_name>",
|
||||||
|
Short: "Generate TLS ECH key pair",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := generateECHKeyPair(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGenerateECHKeyPair.Flags().BoolVar(&pqSignatureSchemesEnabled, "pq-signature-schemes-enabled", false, "Enable PQ signature schemes")
|
||||||
|
commandGenerate.AddCommand(commandGenerateECHKeyPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateECHKeyPair(serverName string) error {
|
||||||
|
configPem, keyPem, err := tls.ECHKeygenDefault(serverName, pqSignatureSchemesEnabled)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.Stdout.WriteString(configPem)
|
||||||
|
os.Stdout.WriteString(keyPem)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -143,16 +143,14 @@ func create() (*box.Box, context.CancelFunc, error) {
|
|||||||
signal.Stop(osSignals)
|
signal.Stop(osSignals)
|
||||||
close(osSignals)
|
close(osSignals)
|
||||||
}()
|
}()
|
||||||
startCtx, finishStart := context.WithCancel(context.Background())
|
|
||||||
go func() {
|
go func() {
|
||||||
_, loaded := <-osSignals
|
_, loaded := <-osSignals
|
||||||
if loaded {
|
if loaded {
|
||||||
cancel()
|
cancel()
|
||||||
closeMonitor(startCtx)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
err = instance.Start()
|
err = instance.Start()
|
||||||
finishStart()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
return nil, nil, E.Cause(err, "start service")
|
return nil, nil, E.Cause(err, "start service")
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package baderror
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Contains(err error, msgList ...string) bool {
|
||||||
|
for _, msg := range msgList {
|
||||||
|
if strings.Contains(err.Error(), msg) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapH2(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = E.Unwrap(err)
|
||||||
|
if err == io.ErrUnexpectedEOF {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
if Contains(err, "client disconnected", "body closed by handler", "response body closed", "; CANCEL") {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapGRPC(err error) error {
|
||||||
|
// grpc uses stupid internal error types
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if Contains(err, "EOF") {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
if Contains(err, "Canceled") {
|
||||||
|
return context.Canceled
|
||||||
|
}
|
||||||
|
if Contains(err,
|
||||||
|
"the client connection is closing",
|
||||||
|
"server closed the stream without sending trailers") {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapQUIC(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if Contains(err, "canceled by local with error code 0") {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ func NewConn(conn net.Conn) (net.Conn, error) {
|
|||||||
element := openConnection.PushBack(conn)
|
element := openConnection.PushBack(conn)
|
||||||
connAccess.Unlock()
|
connAccess.Unlock()
|
||||||
if KillerEnabled {
|
if KillerEnabled {
|
||||||
err := KillerCheck()
|
err := killerCheck()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
package conntrack
|
package conntrack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime"
|
||||||
runtimeDebug "runtime/debug"
|
runtimeDebug "runtime/debug"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/memory"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
KillerEnabled bool
|
KillerEnabled bool
|
||||||
MemoryLimit uint64
|
MemoryLimit int64
|
||||||
killerLastCheck time.Time
|
killerLastCheck time.Time
|
||||||
)
|
)
|
||||||
|
|
||||||
func KillerCheck() error {
|
func killerCheck() error {
|
||||||
if !KillerEnabled {
|
if !KillerEnabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,10 @@ func KillerCheck() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
killerLastCheck = nowTime
|
killerLastCheck = nowTime
|
||||||
if memory.Total() > MemoryLimit {
|
var memStats runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&memStats)
|
||||||
|
inuseMemory := int64(memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased)
|
||||||
|
if inuseMemory > MemoryLimit {
|
||||||
Close()
|
Close()
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
@@ -18,7 +18,7 @@ func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
|
|||||||
element := openConnection.PushBack(conn)
|
element := openConnection.PushBack(conn)
|
||||||
connAccess.Unlock()
|
connAccess.Unlock()
|
||||||
if KillerEnabled {
|
if KillerEnabled {
|
||||||
err := KillerCheck()
|
err := killerCheck()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
|
|||||||
+8
-20
@@ -7,7 +7,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
@@ -25,7 +24,6 @@ type slowOpenConn struct {
|
|||||||
destination M.Socksaddr
|
destination M.Socksaddr
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
create chan struct{}
|
create chan struct{}
|
||||||
access sync.Mutex
|
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,26 +60,16 @@ func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||||
if c.conn != nil {
|
if c.conn == nil {
|
||||||
return c.conn.Write(b)
|
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||||
}
|
if err != nil {
|
||||||
c.access.Lock()
|
c.conn = nil
|
||||||
defer c.access.Unlock()
|
c.err = E.Cause(err, "dial tcp fast open")
|
||||||
select {
|
|
||||||
case <-c.create:
|
|
||||||
if c.err != nil {
|
|
||||||
return 0, c.err
|
|
||||||
}
|
}
|
||||||
return c.conn.Write(b)
|
close(c.create)
|
||||||
default:
|
return
|
||||||
}
|
}
|
||||||
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
return c.conn.Write(b)
|
||||||
if err != nil {
|
|
||||||
c.conn = nil
|
|
||||||
c.err = E.Cause(err, "dial tcp fast open")
|
|
||||||
}
|
|
||||||
close(c.create)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Close() error {
|
func (c *slowOpenConn) Close() error {
|
||||||
|
|||||||
@@ -1,158 +0,0 @@
|
|||||||
package humanize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IEC Sizes.
|
|
||||||
// kibis of bits
|
|
||||||
const (
|
|
||||||
Byte = 1 << (iota * 10)
|
|
||||||
KiByte
|
|
||||||
MiByte
|
|
||||||
GiByte
|
|
||||||
TiByte
|
|
||||||
PiByte
|
|
||||||
EiByte
|
|
||||||
)
|
|
||||||
|
|
||||||
// SI Sizes.
|
|
||||||
const (
|
|
||||||
IByte = 1
|
|
||||||
KByte = IByte * 1000
|
|
||||||
MByte = KByte * 1000
|
|
||||||
GByte = MByte * 1000
|
|
||||||
TByte = GByte * 1000
|
|
||||||
PByte = TByte * 1000
|
|
||||||
EByte = PByte * 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultSizeTable = map[string]uint64{
|
|
||||||
"b": Byte,
|
|
||||||
"kib": KiByte,
|
|
||||||
"kb": KByte,
|
|
||||||
"mib": MiByte,
|
|
||||||
"mb": MByte,
|
|
||||||
"gib": GiByte,
|
|
||||||
"gb": GByte,
|
|
||||||
"tib": TiByte,
|
|
||||||
"tb": TByte,
|
|
||||||
"pib": PiByte,
|
|
||||||
"pb": PByte,
|
|
||||||
"eib": EiByte,
|
|
||||||
"eb": EByte,
|
|
||||||
// Without suffix
|
|
||||||
"": Byte,
|
|
||||||
"ki": KiByte,
|
|
||||||
"k": KByte,
|
|
||||||
"mi": MiByte,
|
|
||||||
"m": MByte,
|
|
||||||
"gi": GiByte,
|
|
||||||
"g": GByte,
|
|
||||||
"ti": TiByte,
|
|
||||||
"t": TByte,
|
|
||||||
"pi": PiByte,
|
|
||||||
"p": PByte,
|
|
||||||
"ei": EiByte,
|
|
||||||
"e": EByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
var memorysSizeTable = map[string]uint64{
|
|
||||||
"b": Byte,
|
|
||||||
"kb": KiByte,
|
|
||||||
"mb": MiByte,
|
|
||||||
"gb": GiByte,
|
|
||||||
"tb": TiByte,
|
|
||||||
"pb": PiByte,
|
|
||||||
"eb": EiByte,
|
|
||||||
"": Byte,
|
|
||||||
"k": KiByte,
|
|
||||||
"m": MiByte,
|
|
||||||
"g": GiByte,
|
|
||||||
"t": TiByte,
|
|
||||||
"p": PiByte,
|
|
||||||
"e": EiByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
|
||||||
iSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
|
||||||
)
|
|
||||||
|
|
||||||
func Bytes(s uint64) string {
|
|
||||||
return humanateBytes(s, 1000, defaultSizes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func MemoryBytes(s uint64) string {
|
|
||||||
return humanateBytes(s, 1024, defaultSizes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IBytes(s uint64) string {
|
|
||||||
return humanateBytes(s, 1024, iSizes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func logn(n, b float64) float64 {
|
|
||||||
return math.Log(n) / math.Log(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
|
||||||
if s < 10 {
|
|
||||||
return fmt.Sprintf("%d B", s)
|
|
||||||
}
|
|
||||||
e := math.Floor(logn(float64(s), base))
|
|
||||||
suffix := sizes[int(e)]
|
|
||||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
|
||||||
f := "%.0f %s"
|
|
||||||
if val < 10 {
|
|
||||||
f = "%.1f %s"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(f, val, suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseBytes(s string) (uint64, error) {
|
|
||||||
return parseBytes0(s, defaultSizeTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseMemoryBytes(s string) (uint64, error) {
|
|
||||||
return parseBytes0(s, memorysSizeTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) {
|
|
||||||
lastDigit := 0
|
|
||||||
hasComma := false
|
|
||||||
for _, r := range s {
|
|
||||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if r == ',' {
|
|
||||||
hasComma = true
|
|
||||||
}
|
|
||||||
lastDigit++
|
|
||||||
}
|
|
||||||
|
|
||||||
num := s[:lastDigit]
|
|
||||||
if hasComma {
|
|
||||||
num = strings.Replace(num, ",", "", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := strconv.ParseFloat(num, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
|
||||||
if m, ok := sizeTable[extra]; ok {
|
|
||||||
f *= float64(m)
|
|
||||||
if f >= math.MaxUint64 {
|
|
||||||
return 0, fmt.Errorf("too large: %v", s)
|
|
||||||
}
|
|
||||||
return uint64(f), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package qtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sagernet/quic-go"
|
||||||
|
"github.com/sagernet/quic-go/http3"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QUICConfig interface {
|
||||||
|
Dial(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.Connection, error)
|
||||||
|
DialEarly(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.EarlyConnection, error)
|
||||||
|
CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config, enableDatagrams bool) http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
type QUICServerConfig interface {
|
||||||
|
Listen(conn net.PacketConn, config *quic.Config) (QUICListener, error)
|
||||||
|
ListenEarly(conn net.PacketConn, config *quic.Config) (QUICEarlyListener, error)
|
||||||
|
ConfigureHTTP3()
|
||||||
|
}
|
||||||
|
|
||||||
|
type QUICListener interface {
|
||||||
|
Accept(ctx context.Context) (quic.Connection, error)
|
||||||
|
Close() error
|
||||||
|
Addr() net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
type QUICEarlyListener interface {
|
||||||
|
Accept(ctx context.Context) (quic.EarlyConnection, error)
|
||||||
|
Close() error
|
||||||
|
Addr() net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func Dial(ctx context.Context, conn net.PacketConn, addr net.Addr, config aTLS.Config, quicConfig *quic.Config) (quic.Connection, error) {
|
||||||
|
if quicTLSConfig, isQUICConfig := config.(QUICConfig); isQUICConfig {
|
||||||
|
return quicTLSConfig.Dial(ctx, conn, addr, quicConfig)
|
||||||
|
}
|
||||||
|
tlsConfig, err := config.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return quic.Dial(ctx, conn, addr, tlsConfig, quicConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DialEarly(ctx context.Context, conn net.PacketConn, addr net.Addr, config aTLS.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
if quicTLSConfig, isQUICConfig := config.(QUICConfig); isQUICConfig {
|
||||||
|
return quicTLSConfig.DialEarly(ctx, conn, addr, quicConfig)
|
||||||
|
}
|
||||||
|
tlsConfig, err := config.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return quic.DialEarly(ctx, conn, addr, tlsConfig, quicConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, config aTLS.Config, quicConfig *quic.Config, enableDatagrams bool) (http.RoundTripper, error) {
|
||||||
|
if quicTLSConfig, isQUICConfig := config.(QUICConfig); isQUICConfig {
|
||||||
|
return quicTLSConfig.CreateTransport(conn, quicConnPtr, serverAddr, quicConfig, enableDatagrams), nil
|
||||||
|
}
|
||||||
|
tlsConfig, err := config.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &http3.RoundTripper{
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
|
QuicConfig: quicConfig,
|
||||||
|
EnableDatagrams: enableDatagrams,
|
||||||
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
quicConn, err := quic.DialEarly(ctx, conn, serverAddr.UDPAddr(), tlsCfg, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
*quicConnPtr = quicConn
|
||||||
|
return quicConn, nil
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Listen(conn net.PacketConn, config aTLS.ServerConfig, quicConfig *quic.Config) (QUICListener, error) {
|
||||||
|
if quicTLSConfig, isQUICConfig := config.(QUICServerConfig); isQUICConfig {
|
||||||
|
return quicTLSConfig.Listen(conn, quicConfig)
|
||||||
|
}
|
||||||
|
tlsConfig, err := config.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return quic.Listen(conn, tlsConfig, quicConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListenEarly(conn net.PacketConn, config aTLS.ServerConfig, quicConfig *quic.Config) (QUICEarlyListener, error) {
|
||||||
|
if quicTLSConfig, isQUICConfig := config.(QUICServerConfig); isQUICConfig {
|
||||||
|
return quicTLSConfig.ListenEarly(conn, quicConfig)
|
||||||
|
}
|
||||||
|
tlsConfig, err := config.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return quic.ListenEarly(conn, tlsConfig, quicConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfigureHTTP3(config aTLS.ServerConfig) error {
|
||||||
|
if len(config.NextProtos()) == 0 {
|
||||||
|
config.SetNextProtos([]string{http3.NextProtoH3})
|
||||||
|
}
|
||||||
|
if quicTLSConfig, isQUICConfig := config.(QUICServerConfig); isQUICConfig {
|
||||||
|
quicTLSConfig.ConfigureHTTP3()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tlsConfig, err := config.Config()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
http3.ConfigureTLSConfig(tlsConfig)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -16,10 +16,12 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
hasGSettings bool
|
hasGSettings bool
|
||||||
|
isKDE5 bool
|
||||||
sudoUser string
|
sudoUser string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
isKDE5 = common.Error(exec.LookPath("kwriteconfig5")) == nil
|
||||||
hasGSettings = common.Error(exec.LookPath("gsettings")) == nil
|
hasGSettings = common.Error(exec.LookPath("gsettings")) == nil
|
||||||
if os.Getuid() == 0 {
|
if os.Getuid() == 0 {
|
||||||
sudoUser = os.Getenv("SUDO_USER")
|
sudoUser = os.Getenv("SUDO_USER")
|
||||||
@@ -37,32 +39,61 @@ func runAsUser(name string, args ...string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
|
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
|
||||||
if !hasGSettings {
|
if hasGSettings {
|
||||||
return nil, E.New("unsupported desktop environment")
|
err := runAsUser("gsettings", "set", "org.gnome.system.proxy.http", "enabled", "true")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isMixed {
|
||||||
|
err = setGnomeProxy(port, "ftp", "http", "https", "socks")
|
||||||
|
} else {
|
||||||
|
err = setGnomeProxy(port, "http", "https")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "use-same-proxy", F.ToString(isMixed))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "manual")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func() error {
|
||||||
|
return runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "none")
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
err := runAsUser("gsettings", "set", "org.gnome.system.proxy.http", "enabled", "true")
|
if isKDE5 {
|
||||||
if err != nil {
|
err := runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "ProxyType", "1")
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isMixed {
|
||||||
|
err = setKDEProxy(port, "ftp", "http", "https", "socks")
|
||||||
|
} else {
|
||||||
|
err = setKDEProxy(port, "http", "https")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "Authmode", "0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = runAsUser("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func() error {
|
||||||
|
err = runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "ProxyType", "0")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return runAsUser("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
if isMixed {
|
return nil, E.New("unsupported desktop environment")
|
||||||
err = setGnomeProxy(port, "ftp", "http", "https", "socks")
|
|
||||||
} else {
|
|
||||||
err = setGnomeProxy(port, "http", "https")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "use-same-proxy", F.ToString(isMixed))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "manual")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return func() error {
|
|
||||||
return runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "none")
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setGnomeProxy(port uint16, proxyTypes ...string) error {
|
func setGnomeProxy(port uint16, proxyTypes ...string) error {
|
||||||
@@ -78,3 +109,27 @@ func setGnomeProxy(port uint16, proxyTypes ...string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setKDEProxy(port uint16, proxyTypes ...string) error {
|
||||||
|
for _, proxyType := range proxyTypes {
|
||||||
|
var proxyUrl string
|
||||||
|
if proxyType == "socks" {
|
||||||
|
proxyUrl = "socks://127.0.0.1:" + F.ToString(port)
|
||||||
|
} else {
|
||||||
|
proxyUrl = "http://127.0.0.1:" + F.ToString(port)
|
||||||
|
}
|
||||||
|
err := runAsUser(
|
||||||
|
"kwriteconfig5",
|
||||||
|
"--file",
|
||||||
|
"kioslaverc",
|
||||||
|
"--group",
|
||||||
|
"'Proxy Settings'",
|
||||||
|
"--key", proxyType+"Proxy",
|
||||||
|
proxyUrl,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,29 +13,29 @@ import (
|
|||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
func NewDialerFromOptions(ctx context.Context, router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return dialer, nil
|
return dialer, nil
|
||||||
}
|
}
|
||||||
config, err := NewClient(router, serverAddress, options)
|
config, err := NewClient(ctx, serverAddress, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewDialer(dialer, config), nil
|
return NewDialer(dialer, config), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return NewECHClient(router, serverAddress, options)
|
return NewECHClient(ctx, serverAddress, options)
|
||||||
} else if options.Reality != nil && options.Reality.Enabled {
|
} else if options.Reality != nil && options.Reality.Enabled {
|
||||||
return NewRealityClient(router, serverAddress, options)
|
return NewRealityClient(ctx, serverAddress, options)
|
||||||
} else if options.UTLS != nil && options.UTLS.Enabled {
|
} else if options.UTLS != nil && options.UTLS.Enabled {
|
||||||
return NewUTLSClient(router, serverAddress, options)
|
return NewUTLSClient(ctx, serverAddress, options)
|
||||||
} else {
|
} else {
|
||||||
return NewSTDClient(router, serverAddress, options)
|
return NewSTDClient(ctx, serverAddress, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+46
-31
@@ -7,50 +7,53 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
cftls "github.com/sagernet/cloudflare-tls"
|
cftls "github.com/sagernet/cloudflare-tls"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ECHClientConfig struct {
|
type echClientConfig struct {
|
||||||
config *cftls.Config
|
config *cftls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) ServerName() string {
|
func (c *echClientConfig) ServerName() string {
|
||||||
return e.config.ServerName
|
return c.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) SetServerName(serverName string) {
|
func (c *echClientConfig) SetServerName(serverName string) {
|
||||||
e.config.ServerName = serverName
|
c.config.ServerName = serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) NextProtos() []string {
|
func (c *echClientConfig) NextProtos() []string {
|
||||||
return e.config.NextProtos
|
return c.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) SetNextProtos(nextProto []string) {
|
func (c *echClientConfig) SetNextProtos(nextProto []string) {
|
||||||
e.config.NextProtos = nextProto
|
c.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Config() (*STDConfig, error) {
|
func (c *echClientConfig) Config() (*STDConfig, error) {
|
||||||
return nil, E.New("unsupported usage for ECH")
|
return nil, E.New("unsupported usage for ECH")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (c *echClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
|
return &echConnWrapper{cftls.Client(conn, c.config)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Clone() Config {
|
func (c *echClientConfig) Clone() Config {
|
||||||
return &ECHClientConfig{
|
return &echClientConfig{
|
||||||
config: e.config.Clone(),
|
config: c.config.Clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +83,7 @@ func (c *echConnWrapper) Upstream() any {
|
|||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewECHClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
@@ -94,7 +97,7 @@ func NewECHClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig cftls.Config
|
var tlsConfig cftls.Config
|
||||||
tlsConfig.Time = router.TimeFunc()
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
@@ -168,24 +171,36 @@ func NewECHClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
tlsConfig.ECHEnabled = true
|
tlsConfig.ECHEnabled = true
|
||||||
tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled
|
tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled
|
||||||
tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled
|
tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled
|
||||||
if options.ECH.Config != "" {
|
|
||||||
clientConfigContent, err := base64.StdEncoding.DecodeString(options.ECH.Config)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, E.Cause(err, "read key")
|
||||||
}
|
}
|
||||||
clientConfig, err := cftls.UnmarshalECHConfigs(clientConfigContent)
|
echConfig = content
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig.ClientECHConfigs = clientConfig
|
|
||||||
} else {
|
|
||||||
tlsConfig.GetClientECHConfigs = fetchECHClientConfig(router)
|
|
||||||
}
|
}
|
||||||
return &ECHClientConfig{&tlsConfig}, nil
|
|
||||||
|
if len(echConfig) > 0 {
|
||||||
|
block, rest := pem.Decode(echConfig)
|
||||||
|
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
||||||
|
return nil, E.New("invalid ECH configs pem")
|
||||||
|
}
|
||||||
|
echConfigs, err := cftls.UnmarshalECHConfigs(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse ECH configs")
|
||||||
|
}
|
||||||
|
tlsConfig.ClientECHConfigs = echConfigs
|
||||||
|
} else {
|
||||||
|
tlsConfig.GetClientECHConfigs = fetchECHClientConfig(ctx)
|
||||||
|
}
|
||||||
|
return &echClientConfig{&tlsConfig}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
||||||
return func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
return func(_ context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
||||||
message := &mDNS.Msg{
|
message := &mDNS.Msg{
|
||||||
MsgHdr: mDNS.MsgHdr{
|
MsgHdr: mDNS.MsgHdr{
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
@@ -198,7 +213,7 @@ func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serve
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
response, err := router.Exchange(ctx, message)
|
response, err := adapter.RouterFromContext(ctx).Exchange(ctx, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
//go:build with_ech
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/pem"
|
||||||
|
|
||||||
|
cftls "github.com/sagernet/cloudflare-tls"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/hpke"
|
||||||
|
"github.com/cloudflare/circl/kem"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ECHKeygenDefault(serverName string, pqSignatureSchemesEnabled bool) (configPem string, keyPem string, err error) {
|
||||||
|
cipherSuites := []echCipherSuite{
|
||||||
|
{
|
||||||
|
kdf: hpke.KDF_HKDF_SHA256,
|
||||||
|
aead: hpke.AEAD_AES128GCM,
|
||||||
|
}, {
|
||||||
|
kdf: hpke.KDF_HKDF_SHA256,
|
||||||
|
aead: hpke.AEAD_ChaCha20Poly1305,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
keyConfig := []myECHKeyConfig{
|
||||||
|
{id: 0, kem: hpke.KEM_X25519_HKDF_SHA256},
|
||||||
|
}
|
||||||
|
if pqSignatureSchemesEnabled {
|
||||||
|
keyConfig = append(keyConfig, myECHKeyConfig{id: 1, kem: hpke.KEM_X25519_KYBER768_DRAFT00})
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPairs, err := echKeygen(0xfe0d, serverName, keyConfig, cipherSuites)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var configBuffer bytes.Buffer
|
||||||
|
var totalLen uint16
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
totalLen += uint16(len(keyPair.rawConf))
|
||||||
|
}
|
||||||
|
binary.Write(&configBuffer, binary.BigEndian, totalLen)
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
configBuffer.Write(keyPair.rawConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyBuffer bytes.Buffer
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
keyBuffer.Write(keyPair.rawKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer.Bytes()}))
|
||||||
|
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer.Bytes()}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type echKeyConfigPair struct {
|
||||||
|
id uint8
|
||||||
|
key cftls.EXP_ECHKey
|
||||||
|
rawKey []byte
|
||||||
|
conf myECHKeyConfig
|
||||||
|
rawConf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type echCipherSuite struct {
|
||||||
|
kdf hpke.KDF
|
||||||
|
aead hpke.AEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
type myECHKeyConfig struct {
|
||||||
|
id uint8
|
||||||
|
kem hpke.KEM
|
||||||
|
seed []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite []echCipherSuite) ([]echKeyConfigPair, error) {
|
||||||
|
be := binary.BigEndian
|
||||||
|
// prepare for future update
|
||||||
|
if version != 0xfe0d {
|
||||||
|
return nil, E.New("unsupported ECH version", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
suiteBuf := make([]byte, 0, len(suite)*4+2)
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(len(suite))*4)
|
||||||
|
for _, s := range suite {
|
||||||
|
if !s.kdf.IsValid() || !s.aead.IsValid() {
|
||||||
|
return nil, E.New("invalid HPKE cipher suite")
|
||||||
|
}
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.kdf))
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.aead))
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs := []echKeyConfigPair{}
|
||||||
|
for _, c := range conf {
|
||||||
|
pair := echKeyConfigPair{}
|
||||||
|
pair.id = c.id
|
||||||
|
pair.conf = c
|
||||||
|
|
||||||
|
if !c.kem.IsValid() {
|
||||||
|
return nil, E.New("invalid HPKE KEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
kpGenerator := c.kem.Scheme().GenerateKeyPair
|
||||||
|
if len(c.seed) > 0 {
|
||||||
|
kpGenerator = func() (kem.PublicKey, kem.PrivateKey, error) {
|
||||||
|
pub, sec := c.kem.Scheme().DeriveKeyPair(c.seed)
|
||||||
|
return pub, sec, nil
|
||||||
|
}
|
||||||
|
if len(c.seed) < c.kem.Scheme().PrivateKeySize() {
|
||||||
|
return nil, E.New("HPKE KEM seed too short")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, sec, err := kpGenerator()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "generate ECH config key pair")
|
||||||
|
}
|
||||||
|
b := []byte{}
|
||||||
|
b = be.AppendUint16(b, version)
|
||||||
|
b = be.AppendUint16(b, 0) // length field
|
||||||
|
// contents
|
||||||
|
// key config
|
||||||
|
b = append(b, c.id)
|
||||||
|
b = be.AppendUint16(b, uint16(c.kem))
|
||||||
|
pubBuf, err := pub.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "serialize ECH public key")
|
||||||
|
}
|
||||||
|
b = be.AppendUint16(b, uint16(len(pubBuf)))
|
||||||
|
b = append(b, pubBuf...)
|
||||||
|
|
||||||
|
b = append(b, suiteBuf...)
|
||||||
|
// end key config
|
||||||
|
// max name len, not supported
|
||||||
|
b = append(b, 0)
|
||||||
|
// server name
|
||||||
|
b = append(b, byte(len(serverName)))
|
||||||
|
b = append(b, []byte(serverName)...)
|
||||||
|
// extensions, not supported
|
||||||
|
b = be.AppendUint16(b, 0)
|
||||||
|
|
||||||
|
be.PutUint16(b[2:], uint16(len(b)-4))
|
||||||
|
|
||||||
|
pair.rawConf = b
|
||||||
|
|
||||||
|
secBuf, err := sec.MarshalBinary()
|
||||||
|
sk := []byte{}
|
||||||
|
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
||||||
|
sk = append(sk, secBuf...)
|
||||||
|
sk = be.AppendUint16(sk, uint16(len(b)))
|
||||||
|
sk = append(sk, b...)
|
||||||
|
|
||||||
|
cfECHKeys, err := cftls.EXP_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)
|
||||||
|
}
|
||||||
|
return pairs, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
//go:build with_quic && with_ech
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sagernet/cloudflare-tls"
|
||||||
|
"github.com/sagernet/quic-go/ech"
|
||||||
|
"github.com/sagernet/quic-go/http3_ech"
|
||||||
|
"github.com/sagernet/sing-box/common/qtls"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ qtls.QUICConfig = (*echClientConfig)(nil)
|
||||||
|
_ qtls.QUICServerConfig = (*echServerConfig)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *echClientConfig) Dial(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.Connection, error) {
|
||||||
|
return quic.Dial(ctx, conn, addr, c.config, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
return quic.DialEarly(ctx, conn, addr, c.config, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config, enableDatagrams bool) http.RoundTripper {
|
||||||
|
return &http3.RoundTripper{
|
||||||
|
TLSClientConfig: c.config,
|
||||||
|
QuicConfig: quicConfig,
|
||||||
|
EnableDatagrams: enableDatagrams,
|
||||||
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
quicConn, err := quic.DialEarly(ctx, conn, serverAddr.UDPAddr(), tlsCfg, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
*quicConnPtr = quicConn
|
||||||
|
return quicConn, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Listen(conn net.PacketConn, config *quic.Config) (qtls.QUICListener, error) {
|
||||||
|
return quic.Listen(conn, c.config, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) ListenEarly(conn net.PacketConn, config *quic.Config) (qtls.QUICEarlyListener, error) {
|
||||||
|
return quic.ListenEarly(conn, c.config, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) ConfigureHTTP3() {
|
||||||
|
http3.ConfigureTLSConfig(c.config)
|
||||||
|
}
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
//go:build with_ech
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/pem"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
cftls "github.com/sagernet/cloudflare-tls"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
type echServerConfig struct {
|
||||||
|
config *cftls.Config
|
||||||
|
logger log.Logger
|
||||||
|
certificate []byte
|
||||||
|
key []byte
|
||||||
|
certificatePath string
|
||||||
|
keyPath string
|
||||||
|
watcher *fsnotify.Watcher
|
||||||
|
echKeyPath string
|
||||||
|
echWatcher *fsnotify.Watcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) ServerName() string {
|
||||||
|
return c.config.ServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) SetServerName(serverName string) {
|
||||||
|
c.config.ServerName = serverName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) NextProtos() []string {
|
||||||
|
return c.config.NextProtos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) SetNextProtos(nextProto []string) {
|
||||||
|
c.config.NextProtos = nextProto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Config() (*STDConfig, error) {
|
||||||
|
return nil, E.New("unsupported usage for ECH")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
|
return &echConnWrapper{cftls.Client(conn, c.config)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Server(conn net.Conn) (Conn, error) {
|
||||||
|
return &echConnWrapper{cftls.Server(conn, c.config)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Clone() Config {
|
||||||
|
return &echServerConfig{
|
||||||
|
config: c.config.Clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Start() error {
|
||||||
|
if c.certificatePath != "" && c.keyPath != "" {
|
||||||
|
err := c.startWatcher()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn("create fsnotify watcher: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.echKeyPath != "" {
|
||||||
|
err := c.startECHWatcher()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn("create fsnotify watcher: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) startWatcher() error {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c.certificatePath != "" {
|
||||||
|
err = watcher.Add(c.certificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.keyPath != "" {
|
||||||
|
err = watcher.Add(c.keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.watcher = watcher
|
||||||
|
go c.loopUpdate()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) loopUpdate() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-c.watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if event.Op&fsnotify.Write != fsnotify.Write {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := c.reloadKeyPair()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error(E.Cause(err, "reload TLS key pair"))
|
||||||
|
}
|
||||||
|
case err, ok := <-c.watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.logger.Error(E.Cause(err, "fsnotify error"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) reloadKeyPair() error {
|
||||||
|
if c.certificatePath != "" {
|
||||||
|
certificate, err := os.ReadFile(c.certificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "reload certificate from ", c.certificatePath)
|
||||||
|
}
|
||||||
|
c.certificate = certificate
|
||||||
|
}
|
||||||
|
if c.keyPath != "" {
|
||||||
|
key, err := os.ReadFile(c.keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "reload key from ", c.keyPath)
|
||||||
|
}
|
||||||
|
c.key = key
|
||||||
|
}
|
||||||
|
keyPair, err := cftls.X509KeyPair(c.certificate, c.key)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "reload key pair")
|
||||||
|
}
|
||||||
|
c.config.Certificates = []cftls.Certificate{keyPair}
|
||||||
|
c.logger.Info("reloaded TLS certificate")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) startECHWatcher() error {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = watcher.Add(c.echKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.watcher = watcher
|
||||||
|
go c.loopECHUpdate()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) loopECHUpdate() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-c.echWatcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if event.Op&fsnotify.Write != fsnotify.Write {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := c.reloadECHKey()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error(E.Cause(err, "reload ECH key"))
|
||||||
|
}
|
||||||
|
case err, ok := <-c.watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.logger.Error(E.Cause(err, "fsnotify error"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) reloadECHKey() error {
|
||||||
|
echKeyContent, err := os.ReadFile(c.echKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
block, rest := pem.Decode(echKeyContent)
|
||||||
|
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
|
||||||
|
return E.New("invalid ECH keys pem")
|
||||||
|
}
|
||||||
|
echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "parse ECH keys")
|
||||||
|
}
|
||||||
|
echKeySet, err := cftls.EXP_NewECHKeySet(echKeys)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "create ECH key set")
|
||||||
|
}
|
||||||
|
c.config.ServerECHProvider = echKeySet
|
||||||
|
c.logger.Info("reloaded ECH keys")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Close() error {
|
||||||
|
var err error
|
||||||
|
if c.watcher != nil {
|
||||||
|
err = E.Append(err, c.watcher.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close certificate watcher")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if c.echWatcher != nil {
|
||||||
|
err = E.Append(err, c.echWatcher.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close ECH key watcher")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewECHServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
|
if !options.Enabled {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var tlsConfig cftls.Config
|
||||||
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
|
return nil, E.New("acme is unavailable in ech")
|
||||||
|
}
|
||||||
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
|
if options.ServerName != "" {
|
||||||
|
tlsConfig.ServerName = options.ServerName
|
||||||
|
}
|
||||||
|
if len(options.ALPN) > 0 {
|
||||||
|
tlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...)
|
||||||
|
}
|
||||||
|
if options.MinVersion != "" {
|
||||||
|
minVersion, err := ParseTLSVersion(options.MinVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse min_version")
|
||||||
|
}
|
||||||
|
tlsConfig.MinVersion = minVersion
|
||||||
|
}
|
||||||
|
if options.MaxVersion != "" {
|
||||||
|
maxVersion, err := ParseTLSVersion(options.MaxVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse max_version")
|
||||||
|
}
|
||||||
|
tlsConfig.MaxVersion = maxVersion
|
||||||
|
}
|
||||||
|
if options.CipherSuites != nil {
|
||||||
|
find:
|
||||||
|
for _, cipherSuite := range options.CipherSuites {
|
||||||
|
for _, tlsCipherSuite := range tls.CipherSuites() {
|
||||||
|
if cipherSuite == tlsCipherSuite.Name {
|
||||||
|
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
|
||||||
|
continue find
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var certificate []byte
|
||||||
|
var key []byte
|
||||||
|
if len(options.Certificate) > 0 {
|
||||||
|
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
||||||
|
} else if options.CertificatePath != "" {
|
||||||
|
content, err := os.ReadFile(options.CertificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read certificate")
|
||||||
|
}
|
||||||
|
certificate = content
|
||||||
|
}
|
||||||
|
if len(options.Key) > 0 {
|
||||||
|
key = []byte(strings.Join(options.Key, "\n"))
|
||||||
|
} else if options.KeyPath != "" {
|
||||||
|
content, err := os.ReadFile(options.KeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read key")
|
||||||
|
}
|
||||||
|
key = content
|
||||||
|
}
|
||||||
|
|
||||||
|
if certificate == nil {
|
||||||
|
return nil, E.New("missing certificate")
|
||||||
|
} else if key == nil {
|
||||||
|
return nil, E.New("missing key")
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPair, err := cftls.X509KeyPair(certificate, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse x509 key pair")
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []cftls.Certificate{keyPair}
|
||||||
|
|
||||||
|
var echKey []byte
|
||||||
|
if len(options.ECH.Key) > 0 {
|
||||||
|
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
|
||||||
|
} else if options.KeyPath != "" {
|
||||||
|
content, err := os.ReadFile(options.ECH.KeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read 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 := cftls.EXP_UnmarshalECHKeys(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse ECH keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
echKeySet, err := cftls.EXP_NewECHKeySet(echKeys)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "create ECH key set")
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig.ECHEnabled = true
|
||||||
|
tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled
|
||||||
|
tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled
|
||||||
|
tlsConfig.ServerECHProvider = echKeySet
|
||||||
|
|
||||||
|
return &echServerConfig{
|
||||||
|
config: &tlsConfig,
|
||||||
|
logger: logger,
|
||||||
|
certificate: certificate,
|
||||||
|
key: key,
|
||||||
|
certificatePath: options.CertificatePath,
|
||||||
|
keyPath: options.KeyPath,
|
||||||
|
echKeyPath: options.ECH.KeyPath,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
+15
-3
@@ -3,11 +3,23 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
var errECHNotIncluded = E.New(`ECH is not included in this build, rebuild with -tags with_ech`)
|
||||||
return nil, E.New(`ECH is not included in this build, rebuild with -tags with_ech`)
|
|
||||||
|
func NewECHServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
|
return nil, errECHNotIncluded
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewECHClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
|
return nil, errECHNotIncluded
|
||||||
|
}
|
||||||
|
|
||||||
|
func ECHKeygenDefault(host string, pqSignatureSchemesEnabled bool) (configPem string, keyPem string, err error) {
|
||||||
|
return "", "", errECHNotIncluded
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/debug"
|
"github.com/sagernet/sing/common/debug"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -45,12 +44,12 @@ type RealityClientConfig struct {
|
|||||||
shortID [8]byte
|
shortID [8]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) {
|
func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) {
|
||||||
if options.UTLS == nil || !options.UTLS.Enabled {
|
if options.UTLS == nil || !options.UTLS.Enabled {
|
||||||
return nil, E.New("uTLS is required by reality client")
|
return nil, E.New("uTLS is required by reality client")
|
||||||
}
|
}
|
||||||
|
|
||||||
uClient, err := NewUTLSClient(router, serverAddress, options)
|
uClient, err := NewUTLSClient(ctx, serverAddress, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
||||||
@@ -27,13 +28,13 @@ type RealityServerConfig struct {
|
|||||||
config *reality.Config
|
config *reality.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
||||||
var tlsConfig reality.Config
|
var tlsConfig reality.Config
|
||||||
|
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
return nil, E.New("acme is unavailable in reality")
|
return nil, E.New("acme is unavailable in reality")
|
||||||
}
|
}
|
||||||
tlsConfig.Time = router.TimeFunc()
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
tlsConfig.ServerName = options.ServerName
|
tlsConfig.ServerName = options.ServerName
|
||||||
}
|
}
|
||||||
@@ -66,10 +67,10 @@ func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Log
|
|||||||
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.Certificate != "" || options.CertificatePath != "" {
|
if len(options.Certificate) > 0 || options.CertificatePath != "" {
|
||||||
return nil, E.New("certificate is unavailable in reality")
|
return nil, E.New("certificate is unavailable in reality")
|
||||||
}
|
}
|
||||||
if options.Key != "" || options.KeyPath != "" {
|
if len(options.Key) > 0 || options.KeyPath != "" {
|
||||||
return nil, E.New("key is unavailable in reality")
|
return nil, E.New("key is unavailable in reality")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Log
|
|||||||
tlsConfig.ShortIds[shortID] = true
|
tlsConfig.ShortIds[shortID] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
handshakeDialer, err := dialer.New(router, options.Reality.Handshake.DialerOptions)
|
handshakeDialer, err := dialer.New(adapter.RouterFromContext(ctx), options.Reality.Handshake.DialerOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
|
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,22 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return NewRealityServer(ctx, router, logger, options)
|
return NewECHServer(ctx, logger, options)
|
||||||
|
} else if options.Reality != nil && options.Reality.Enabled {
|
||||||
|
return NewRealityServer(ctx, logger, options)
|
||||||
} else {
|
} else {
|
||||||
return NewSTDServer(ctx, router, logger, options)
|
return NewSTDServer(ctx, logger, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type STDClientConfig struct {
|
type STDClientConfig struct {
|
||||||
@@ -44,7 +45,7 @@ func (s *STDClientConfig) Clone() Config {
|
|||||||
return &STDClientConfig{s.config.Clone()}
|
return &STDClientConfig{s.config.Clone()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
@@ -58,7 +59,7 @@ func NewSTDClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
tlsConfig.Time = router.TimeFunc()
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
)
|
)
|
||||||
@@ -156,7 +158,7 @@ func (c *STDServerConfig) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -175,7 +177,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
} else {
|
} else {
|
||||||
tlsConfig = &tls.Config{}
|
tlsConfig = &tls.Config{}
|
||||||
}
|
}
|
||||||
tlsConfig.Time = router.TimeFunc()
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
tlsConfig.ServerName = options.ServerName
|
tlsConfig.ServerName = options.ServerName
|
||||||
}
|
}
|
||||||
@@ -211,8 +213,8 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
var certificate []byte
|
var certificate []byte
|
||||||
var key []byte
|
var key []byte
|
||||||
if acmeService == nil {
|
if acmeService == nil {
|
||||||
if options.Certificate != "" {
|
if len(options.Certificate) > 0 {
|
||||||
certificate = []byte(options.Certificate)
|
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
||||||
} else if options.CertificatePath != "" {
|
} else if options.CertificatePath != "" {
|
||||||
content, err := os.ReadFile(options.CertificatePath)
|
content, err := os.ReadFile(options.CertificatePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -220,8 +222,8 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
}
|
}
|
||||||
certificate = content
|
certificate = content
|
||||||
}
|
}
|
||||||
if options.Key != "" {
|
if len(options.Key) > 0 {
|
||||||
key = []byte(options.Key)
|
key = []byte(strings.Join(options.Key, "\n"))
|
||||||
} else if options.KeyPath != "" {
|
} else if options.KeyPath != "" {
|
||||||
content, err := os.ReadFile(options.KeyPath)
|
content, err := os.ReadFile(options.KeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -231,7 +233,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
}
|
}
|
||||||
if certificate == nil && key == nil && options.Insecure {
|
if certificate == nil && key == nil && options.Insecure {
|
||||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
return GenerateKeyPair(router.TimeFunc(), info.ServerName)
|
return GenerateKeyPair(ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
utls "github.com/sagernet/utls"
|
utls "github.com/sagernet/utls"
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
@@ -113,7 +113,7 @@ func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
|
|||||||
return c.UConn.HandshakeContext(ctx)
|
return c.UConn.HandshakeContext(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
@@ -127,7 +127,7 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig utls.Config
|
var tlsConfig utls.Config
|
||||||
tlsConfig.Time = router.TimeFunc()
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,15 +3,16 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"context"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`)
|
return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
|
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const (
|
|||||||
TypeShadowsocksR = "shadowsocksr"
|
TypeShadowsocksR = "shadowsocksr"
|
||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
|
TypeHysteria2 = "hysteria2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -65,6 +66,8 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "VLESS"
|
return "VLESS"
|
||||||
case TypeTUIC:
|
case TypeTUIC:
|
||||||
return "TUIC"
|
return "TUIC"
|
||||||
|
case TypeHysteria2:
|
||||||
|
return "Hysteria2"
|
||||||
case TypeSelector:
|
case TypeSelector:
|
||||||
return "Selector"
|
return "Selector"
|
||||||
case TypeURLTest:
|
case TypeURLTest:
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@ package box
|
|||||||
import (
|
import (
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ func applyDebugOptions(options option.DebugOptions) {
|
|||||||
}
|
}
|
||||||
if options.MemoryLimit != 0 {
|
if options.MemoryLimit != 0 {
|
||||||
// debug.SetMemoryLimit(int64(options.MemoryLimit))
|
// debug.SetMemoryLimit(int64(options.MemoryLimit))
|
||||||
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
||||||
}
|
}
|
||||||
if options.OOMKiller != nil {
|
if options.OOMKiller != nil {
|
||||||
conntrack.KillerEnabled = *options.OOMKiller
|
conntrack.KillerEnabled = *options.OOMKiller
|
||||||
|
|||||||
+3
-3
@@ -5,7 +5,7 @@ package box
|
|||||||
import (
|
import (
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,8 +27,8 @@ func applyDebugOptions(options option.DebugOptions) {
|
|||||||
debug.SetTraceback(options.TraceBack)
|
debug.SetTraceback(options.TraceBack)
|
||||||
}
|
}
|
||||||
if options.MemoryLimit != 0 {
|
if options.MemoryLimit != 0 {
|
||||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
|
debug.SetMemoryLimit(int64(options.MemoryLimit))
|
||||||
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
||||||
}
|
}
|
||||||
if options.OOMKiller != nil {
|
if options.OOMKiller != nil {
|
||||||
conntrack.KillerEnabled = *options.OOMKiller
|
conntrack.KillerEnabled = *options.OOMKiller
|
||||||
|
|||||||
+4
-4
@@ -7,12 +7,12 @@ import (
|
|||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/badjson"
|
"github.com/sagernet/sing-box/common/badjson"
|
||||||
"github.com/sagernet/sing-box/common/humanize"
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
"github.com/sagernet/sing-box/common/json"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,9 +37,9 @@ func applyDebugListenOption(options option.DebugOptions) {
|
|||||||
runtime.ReadMemStats(&memStats)
|
runtime.ReadMemStats(&memStats)
|
||||||
|
|
||||||
var memObject badjson.JSONObject
|
var memObject badjson.JSONObject
|
||||||
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
|
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
|
||||||
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
|
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
|
||||||
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||||
memObject.Put("rss", rusageMaxRSS())
|
memObject.Put("rss", rusageMaxRSS())
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,30 @@
|
|||||||
|
#### 1.5.0-beta.2
|
||||||
|
|
||||||
|
* Add hysteria2 protocol support **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
See [Hysteria2 inbound](/configuration/inbound/hysteria2) and [Hysteria2 outbound](/configuration/outbound/hysteria2)
|
||||||
|
|
||||||
|
For protocol description, please refer to [https://v2.hysteria.network](https://v2.hysteria.network)
|
||||||
|
|
||||||
|
#### 1.5.0-beta.1
|
||||||
|
|
||||||
|
* Add TLS [ECH server](/configuration/shared/tls) support
|
||||||
|
* Improve TLS TCH client configuration
|
||||||
|
* Add TLS ECH key pair generator **1**
|
||||||
|
* Add TLS ECH support for QUIC based protocols **2**
|
||||||
|
* Add KDE support for the `set_system_proxy` option in HTTP inbound
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Command: `sing-box generate ech-keypair <plain_server_name> [-pq-signature-schemes-enabled]`
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
All inbounds and outbounds are supported, including `Naiveproxy`, `Hysteria`, `TUIC` and `V2ray QUIC transport`.
|
||||||
|
|
||||||
#### 1.4.1
|
#### 1.4.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "hysteria2",
|
||||||
|
"tag": "hy2-in",
|
||||||
|
|
||||||
|
... // Listen Fields
|
||||||
|
|
||||||
|
"up_mbps": 100,
|
||||||
|
"down_mbps": 100,
|
||||||
|
"obfs": {
|
||||||
|
"type": "salamander",
|
||||||
|
"password": "cry_me_a_r1ver"
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "tobyxdd",
|
||||||
|
"password": "goofy_ahh_password"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ignore_client_bandwidth": false,
|
||||||
|
"masquerade": "",
|
||||||
|
"tls": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
QUIC, which is required by Hysteria2 is not included by default, see [Installation](/#installation).
|
||||||
|
|
||||||
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### up_mbps, down_mbps
|
||||||
|
|
||||||
|
Max bandwidth, in Mbps.
|
||||||
|
|
||||||
|
Not limited if empty.
|
||||||
|
|
||||||
|
Conflict with `ignore_client_bandwidth`.
|
||||||
|
|
||||||
|
#### obfs.type
|
||||||
|
|
||||||
|
QUIC traffic obfuscator type, only available with `salamander`.
|
||||||
|
|
||||||
|
Disabled if empty.
|
||||||
|
|
||||||
|
#### obfs.password
|
||||||
|
|
||||||
|
QUIC traffic obfuscator password.
|
||||||
|
|
||||||
|
#### users
|
||||||
|
|
||||||
|
Hysteria2 users
|
||||||
|
|
||||||
|
#### users.password
|
||||||
|
|
||||||
|
Authentication password
|
||||||
|
|
||||||
|
#### ignore_client_bandwidth
|
||||||
|
|
||||||
|
Commands the client to use the BBR flow control algorithm instead of Hysteria CC.
|
||||||
|
|
||||||
|
Conflict with `up_mbps` and `down_mbps`.
|
||||||
|
|
||||||
|
#### masquerade
|
||||||
|
|
||||||
|
HTTP3 server behavior when authentication fails.
|
||||||
|
|
||||||
|
| Scheme | Example | Description |
|
||||||
|
|--------------|-------------------------|--------------------|
|
||||||
|
| `file` | `file:///var/www` | As a file server |
|
||||||
|
| `http/https` | `http://127.0.0.1:8080` | As a reverse proxy |
|
||||||
|
|
||||||
|
A 404 page will be returned if empty.
|
||||||
|
|
||||||
|
#### tls
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "hysteria2",
|
||||||
|
"tag": "hy2-in",
|
||||||
|
|
||||||
|
... // 监听字段
|
||||||
|
|
||||||
|
"up_mbps": 100,
|
||||||
|
"down_mbps": 100,
|
||||||
|
"obfs": {
|
||||||
|
"type": "salamander",
|
||||||
|
"password": "cry_me_a_r1ver"
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "tobyxdd",
|
||||||
|
"password": "goofy_ahh_password"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ignore_client_bandwidth": false,
|
||||||
|
"masquerade": "",
|
||||||
|
"tls": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
默认安装不包含被 Hysteria2 依赖的 QUIC,参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### up_mbps, down_mbps
|
||||||
|
|
||||||
|
支持的速率,默认不限制。
|
||||||
|
|
||||||
|
与 `ignore_client_bandwidth` 冲突。
|
||||||
|
|
||||||
|
#### obfs.type
|
||||||
|
|
||||||
|
QUIC 流量混淆器类型,仅可设为 `salamander`。
|
||||||
|
|
||||||
|
如果为空则禁用。
|
||||||
|
|
||||||
|
#### obfs.password
|
||||||
|
|
||||||
|
QUIC 流量混淆器密码.
|
||||||
|
|
||||||
|
#### users
|
||||||
|
|
||||||
|
Hysteria 用户
|
||||||
|
|
||||||
|
#### users.password
|
||||||
|
|
||||||
|
认证密码。
|
||||||
|
|
||||||
|
#### ignore_client_bandwidth
|
||||||
|
|
||||||
|
命令客户端使用 BBR 流量控制算法而不是 Hysteria CC。
|
||||||
|
|
||||||
|
与 `up_mbps` 和 `down_mbps` 冲突。
|
||||||
|
|
||||||
|
#### masquerade
|
||||||
|
|
||||||
|
HTTP3 服务器认证失败时的行为。
|
||||||
|
|
||||||
|
| Scheme | 示例 | 描述 |
|
||||||
|
|--------------|-------------------------|---------|
|
||||||
|
| `file` | `file:///var/www` | 作为文件服务器 |
|
||||||
|
| `http/https` | `http://127.0.0.1:8080` | 作为反向代理 |
|
||||||
|
|
||||||
|
如果为空,则返回 404 页。
|
||||||
|
|
||||||
|
#### tls
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
| `naive` | [Naive](./naive) | X |
|
| `naive` | [Naive](./naive) | X |
|
||||||
| `hysteria` | [Hysteria](./hysteria) | X |
|
| `hysteria` | [Hysteria](./hysteria) | X |
|
||||||
| `shadowtls` | [ShadowTLS](./shadowtls) | TCP |
|
| `shadowtls` | [ShadowTLS](./shadowtls) | TCP |
|
||||||
|
| `tuic` | [TUIC](./tuic) | X |
|
||||||
|
| `hysteria2` | [Hysteria2](./hysteria2) | X |
|
||||||
| `vless` | [VLESS](./vless) | TCP |
|
| `vless` | [VLESS](./vless) | TCP |
|
||||||
| `tun` | [Tun](./tun) | X |
|
| `tun` | [Tun](./tun) | X |
|
||||||
| `redirect` | [Redirect](./redirect) | X |
|
| `redirect` | [Redirect](./redirect) | X |
|
||||||
|
|||||||
@@ -26,6 +26,10 @@
|
|||||||
| `trojan` | [Trojan](./trojan) | TCP |
|
| `trojan` | [Trojan](./trojan) | TCP |
|
||||||
| `naive` | [Naive](./naive) | X |
|
| `naive` | [Naive](./naive) | X |
|
||||||
| `hysteria` | [Hysteria](./hysteria) | X |
|
| `hysteria` | [Hysteria](./hysteria) | X |
|
||||||
|
| `shadowtls` | [ShadowTLS](./shadowtls) | TCP |
|
||||||
|
| `tuic` | [TUIC](./tuic) | X |
|
||||||
|
| `hysteria2` | [Hysteria2](./hysteria2) | X |
|
||||||
|
| `vless` | [VLESS](./vless) | TCP |
|
||||||
| `tun` | [Tun](./tun) | X |
|
| `tun` | [Tun](./tun) | X |
|
||||||
| `redirect` | [Redirect](./redirect) | X |
|
| `redirect` | [Redirect](./redirect) | X |
|
||||||
| `tproxy` | [TProxy](./tproxy) | X |
|
| `tproxy` | [TProxy](./tproxy) | X |
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "hysteria2",
|
||||||
|
"tag": "hy2-out",
|
||||||
|
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 1080,
|
||||||
|
"up_mbps": 100,
|
||||||
|
"down_mbps": 100,
|
||||||
|
"obfs": {
|
||||||
|
"type": "salamander",
|
||||||
|
"password": "cry_me_a_r1ver"
|
||||||
|
},
|
||||||
|
"password": "goofy_ahh_password",
|
||||||
|
"network": "tcp",
|
||||||
|
"tls": {},
|
||||||
|
|
||||||
|
... // Dial Fields
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
QUIC, which is required by Hysteria2 is not included by default, see [Installation](/#installation).
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
The server address.
|
||||||
|
|
||||||
|
#### server_port
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
The server port.
|
||||||
|
|
||||||
|
#### up_mbps, down_mbps
|
||||||
|
|
||||||
|
Max bandwidth, in Mbps.
|
||||||
|
|
||||||
|
If empty, the BBR congestion control algorithm will be used instead of Hysteria CC.
|
||||||
|
|
||||||
|
#### obfs.type
|
||||||
|
|
||||||
|
QUIC traffic obfuscator type, only available with `salamander`.
|
||||||
|
|
||||||
|
Disabled if empty.
|
||||||
|
|
||||||
|
#### obfs.password
|
||||||
|
|
||||||
|
QUIC traffic obfuscator password.
|
||||||
|
|
||||||
|
#### password
|
||||||
|
|
||||||
|
Authentication password.
|
||||||
|
|
||||||
|
#### network
|
||||||
|
|
||||||
|
Enabled network
|
||||||
|
|
||||||
|
One of `tcp` `udp`.
|
||||||
|
|
||||||
|
Both is enabled by default.
|
||||||
|
|
||||||
|
#### tls
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||||
|
|
||||||
|
### Dial Fields
|
||||||
|
|
||||||
|
See [Dial Fields](/configuration/shared/dial) for details.
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "hysteria2",
|
||||||
|
"tag": "hy2-out",
|
||||||
|
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 1080,
|
||||||
|
"up_mbps": 100,
|
||||||
|
"down_mbps": 100,
|
||||||
|
"obfs": {
|
||||||
|
"type": "salamander",
|
||||||
|
"password": "cry_me_a_r1ver"
|
||||||
|
},
|
||||||
|
"password": "goofy_ahh_password",
|
||||||
|
"network": "tcp",
|
||||||
|
"tls": {},
|
||||||
|
|
||||||
|
... // 拨号字段
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
默认安装不包含被 Hysteria2 依赖的 QUIC,参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
服务器地址。
|
||||||
|
|
||||||
|
#### server_port
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
服务器端口。
|
||||||
|
|
||||||
|
#### up_mbps, down_mbps
|
||||||
|
|
||||||
|
最大带宽。
|
||||||
|
|
||||||
|
如果为空,将使用 BBR 流量控制算法而不是 Hysteria CC。
|
||||||
|
|
||||||
|
#### obfs.type
|
||||||
|
|
||||||
|
QUIC 流量混淆器类型,仅可设为 `salamander`。
|
||||||
|
|
||||||
|
如果为空则禁用。
|
||||||
|
|
||||||
|
#### obfs.password
|
||||||
|
|
||||||
|
QUIC 流量混淆器密码.
|
||||||
|
|
||||||
|
#### password
|
||||||
|
|
||||||
|
认证密码。
|
||||||
|
|
||||||
|
#### network
|
||||||
|
|
||||||
|
启用的网络协议。
|
||||||
|
|
||||||
|
`tcp` 或 `udp`。
|
||||||
|
|
||||||
|
默认所有。
|
||||||
|
|
||||||
|
#### tls
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
||||||
|
|
||||||
|
|
||||||
|
### 拨号字段
|
||||||
|
|
||||||
|
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||||
@@ -29,6 +29,8 @@
|
|||||||
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
||||||
| `vless` | [VLESS](./vless) |
|
| `vless` | [VLESS](./vless) |
|
||||||
| `shadowtls` | [ShadowTLS](./shadowtls) |
|
| `shadowtls` | [ShadowTLS](./shadowtls) |
|
||||||
|
| `tuic` | [TUIC](./tuic) |
|
||||||
|
| `hysteria2` | [Hysteria2](./hysteria2) |
|
||||||
| `tor` | [Tor](./tor) |
|
| `tor` | [Tor](./tor) |
|
||||||
| `ssh` | [SSH](./ssh) |
|
| `ssh` | [SSH](./ssh) |
|
||||||
| `dns` | [DNS](./dns) |
|
| `dns` | [DNS](./dns) |
|
||||||
|
|||||||
@@ -28,6 +28,9 @@
|
|||||||
| `hysteria` | [Hysteria](./hysteria) |
|
| `hysteria` | [Hysteria](./hysteria) |
|
||||||
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
||||||
| `vless` | [VLESS](./vless) |
|
| `vless` | [VLESS](./vless) |
|
||||||
|
| `shadowtls` | [ShadowTLS](./shadowtls) |
|
||||||
|
| `tuic` | [TUIC](./tuic) |
|
||||||
|
| `hysteria2` | [Hysteria2](./hysteria2) |
|
||||||
| `tor` | [Tor](./tor) |
|
| `tor` | [Tor](./tor) |
|
||||||
| `ssh` | [SSH](./ssh) |
|
| `ssh` | [SSH](./ssh) |
|
||||||
| `dns` | [DNS](./dns) |
|
| `dns` | [DNS](./dns) |
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
"min_version": "",
|
"min_version": "",
|
||||||
"max_version": "",
|
"max_version": "",
|
||||||
"cipher_suites": [],
|
"cipher_suites": [],
|
||||||
"certificate": "",
|
"certificate": [],
|
||||||
"certificate_path": "",
|
"certificate_path": "",
|
||||||
"key": "",
|
"key": [],
|
||||||
"key_path": "",
|
"key_path": "",
|
||||||
"acme": {
|
"acme": {
|
||||||
"domain": [],
|
"domain": [],
|
||||||
@@ -27,6 +27,13 @@
|
|||||||
"mac_key": ""
|
"mac_key": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ech": {
|
||||||
|
"enabled": false,
|
||||||
|
"pq_signature_schemes_enabled": false,
|
||||||
|
"dynamic_record_sizing_disabled": false,
|
||||||
|
"key": [],
|
||||||
|
"key_path": ""
|
||||||
|
},
|
||||||
"reality": {
|
"reality": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"handshake": {
|
"handshake": {
|
||||||
@@ -62,7 +69,8 @@
|
|||||||
"enabled": false,
|
"enabled": false,
|
||||||
"pq_signature_schemes_enabled": false,
|
"pq_signature_schemes_enabled": false,
|
||||||
"dynamic_record_sizing_disabled": false,
|
"dynamic_record_sizing_disabled": false,
|
||||||
"config": ""
|
"config": [],
|
||||||
|
"config_path": ""
|
||||||
},
|
},
|
||||||
"utls": {
|
"utls": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@@ -162,7 +170,7 @@ This may change in the future.
|
|||||||
|
|
||||||
#### certificate
|
#### certificate
|
||||||
|
|
||||||
The server certificate, in PEM format.
|
The server certificate line array, in PEM format.
|
||||||
|
|
||||||
#### certificate_path
|
#### certificate_path
|
||||||
|
|
||||||
@@ -172,7 +180,7 @@ The path to the server certificate, in PEM format.
|
|||||||
|
|
||||||
==Server only==
|
==Server only==
|
||||||
|
|
||||||
The server private key, in PEM format.
|
The server private key line array, in PEM format.
|
||||||
|
|
||||||
#### key_path
|
#### key_path
|
||||||
|
|
||||||
@@ -180,18 +188,11 @@ The server private key, in PEM format.
|
|||||||
|
|
||||||
The path to the server private key, in PEM format.
|
The path to the server private key, in PEM format.
|
||||||
|
|
||||||
#### ech
|
## Custom TLS support
|
||||||
|
|
||||||
==Client only==
|
!!! info "QUIC support"
|
||||||
|
|
||||||
!!! warning ""
|
Only ECH is supported in QUIC.
|
||||||
|
|
||||||
ECH is not included by default, see [Installation](/#installation).
|
|
||||||
|
|
||||||
ECH (Encrypted Client Hello) is a TLS extension that allows a client to encrypt the first part of its ClientHello
|
|
||||||
message.
|
|
||||||
|
|
||||||
If you don't know how to fill in the other configuration, just set `enabled`.
|
|
||||||
|
|
||||||
#### utls
|
#### utls
|
||||||
|
|
||||||
@@ -222,6 +223,58 @@ Available fingerprint values:
|
|||||||
|
|
||||||
Chrome fingerprint will be used if empty.
|
Chrome fingerprint will be used if empty.
|
||||||
|
|
||||||
|
### ECH Fields
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
ECH is not included by default, see [Installation](/#installation).
|
||||||
|
|
||||||
|
ECH (Encrypted Client Hello) is a TLS extension that allows a client to encrypt the first part of its ClientHello
|
||||||
|
message.
|
||||||
|
|
||||||
|
The ECH key and configuration can be generated by `sing-box generate ech-keypair [-pq-signature-schemes-enabled]`.
|
||||||
|
|
||||||
|
#### pq_signature_schemes_enabled
|
||||||
|
|
||||||
|
Enable support for post-quantum peer certificate signature schemes.
|
||||||
|
|
||||||
|
It is recommended to match the parameters of `sing-box generate ech-keypair`.
|
||||||
|
|
||||||
|
#### dynamic_record_sizing_disabled
|
||||||
|
|
||||||
|
Disables adaptive sizing of TLS records.
|
||||||
|
|
||||||
|
When true, the largest possible TLS record size is always used.
|
||||||
|
When false, the size of TLS records may be adjusted in an attempt to improve latency.
|
||||||
|
|
||||||
|
#### key
|
||||||
|
|
||||||
|
==Server only==
|
||||||
|
|
||||||
|
ECH key line array, in PEM format.
|
||||||
|
|
||||||
|
#### key_path
|
||||||
|
|
||||||
|
==Server only==
|
||||||
|
|
||||||
|
The path to ECH key, in PEM format.
|
||||||
|
|
||||||
|
#### config
|
||||||
|
|
||||||
|
==Client only==
|
||||||
|
|
||||||
|
ECH configuration line array, in PEM format.
|
||||||
|
|
||||||
|
If empty, load from DNS will be attempted.
|
||||||
|
|
||||||
|
#### config_path
|
||||||
|
|
||||||
|
==Client only==
|
||||||
|
|
||||||
|
The path to ECH configuration, in PEM format.
|
||||||
|
|
||||||
|
If empty, load from DNS will be attempted.
|
||||||
|
|
||||||
### ACME Fields
|
### ACME Fields
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
@@ -345,4 +398,4 @@ Check disabled if empty.
|
|||||||
|
|
||||||
### Reload
|
### Reload
|
||||||
|
|
||||||
For server configuration, certificate and key will be automatically reloaded if modified.
|
For server configuration, certificate, key and ECH key will be automatically reloaded if modified.
|
||||||
@@ -8,9 +8,9 @@
|
|||||||
"min_version": "",
|
"min_version": "",
|
||||||
"max_version": "",
|
"max_version": "",
|
||||||
"cipher_suites": [],
|
"cipher_suites": [],
|
||||||
"certificate": "",
|
"certificate": [],
|
||||||
"certificate_path": "",
|
"certificate_path": "",
|
||||||
"key": "",
|
"key": [],
|
||||||
"key_path": "",
|
"key_path": "",
|
||||||
"acme": {
|
"acme": {
|
||||||
"domain": [],
|
"domain": [],
|
||||||
@@ -27,6 +27,13 @@
|
|||||||
"mac_key": ""
|
"mac_key": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ech": {
|
||||||
|
"enabled": false,
|
||||||
|
"pq_signature_schemes_enabled": false,
|
||||||
|
"dynamic_record_sizing_disabled": false,
|
||||||
|
"key": [],
|
||||||
|
"key_path": ""
|
||||||
|
},
|
||||||
"reality": {
|
"reality": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"handshake": {
|
"handshake": {
|
||||||
@@ -56,13 +63,14 @@
|
|||||||
"min_version": "",
|
"min_version": "",
|
||||||
"max_version": "",
|
"max_version": "",
|
||||||
"cipher_suites": [],
|
"cipher_suites": [],
|
||||||
"certificate": "",
|
"certificate": [],
|
||||||
"certificate_path": "",
|
"certificate_path": "",
|
||||||
"ech": {
|
"ech": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"pq_signature_schemes_enabled": false,
|
"pq_signature_schemes_enabled": false,
|
||||||
"dynamic_record_sizing_disabled": false,
|
"dynamic_record_sizing_disabled": false,
|
||||||
"config": ""
|
"config": [],
|
||||||
|
"config_path": ""
|
||||||
},
|
},
|
||||||
"utls": {
|
"utls": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@@ -162,7 +170,7 @@ TLS 版本值:
|
|||||||
|
|
||||||
#### certificate
|
#### certificate
|
||||||
|
|
||||||
服务器 PEM 证书。
|
服务器 PEM 证书行数组。
|
||||||
|
|
||||||
#### certificate_path
|
#### certificate_path
|
||||||
|
|
||||||
@@ -172,7 +180,7 @@ TLS 版本值:
|
|||||||
|
|
||||||
==仅服务器==
|
==仅服务器==
|
||||||
|
|
||||||
服务器 PEM 私钥。
|
服务器 PEM 私钥行数组。
|
||||||
|
|
||||||
#### key_path
|
#### key_path
|
||||||
|
|
||||||
@@ -180,19 +188,6 @@ TLS 版本值:
|
|||||||
|
|
||||||
服务器 PEM 私钥路径。
|
服务器 PEM 私钥路径。
|
||||||
|
|
||||||
#### ech
|
|
||||||
|
|
||||||
==仅客户端==
|
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
默认安装不包含 ECH, 参阅 [安装](/zh/#_2)。
|
|
||||||
|
|
||||||
ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ClientHello 的第一部分
|
|
||||||
信息。
|
|
||||||
|
|
||||||
如果您不知道如何填写其他配置,只需设置 `enabled` 即可。
|
|
||||||
|
|
||||||
#### utls
|
#### utls
|
||||||
|
|
||||||
==仅客户端==
|
==仅客户端==
|
||||||
@@ -222,6 +217,59 @@ uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻
|
|||||||
|
|
||||||
默认使用 chrome 指纹。
|
默认使用 chrome 指纹。
|
||||||
|
|
||||||
|
## ECH 字段
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
默认安装不包含 ECH, 参阅 [安装](/zh/#_2)。
|
||||||
|
|
||||||
|
ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ClientHello 的第一部分
|
||||||
|
信息。
|
||||||
|
|
||||||
|
|
||||||
|
ECH 配置和密钥可以通过 `sing-box generated ech-keypair [-pq-signature-schemes-enabled]` 生成。
|
||||||
|
|
||||||
|
#### pq_signature_schemes_enabled
|
||||||
|
|
||||||
|
启用对后量子对等证书签名方案的支持。
|
||||||
|
|
||||||
|
建议匹配 `sing-box generated ech-keypair` 的参数。
|
||||||
|
|
||||||
|
#### dynamic_record_sizing_disabled
|
||||||
|
|
||||||
|
禁用 TLS 记录的自适应大小调整。
|
||||||
|
|
||||||
|
如果为 true,则始终使用最大可能的 TLS 记录大小。
|
||||||
|
如果为 false,则可能会调整 TLS 记录的大小以尝试改善延迟。
|
||||||
|
|
||||||
|
#### key
|
||||||
|
|
||||||
|
==仅服务器==
|
||||||
|
|
||||||
|
ECH PEM 密钥行数组
|
||||||
|
|
||||||
|
#### key_path
|
||||||
|
|
||||||
|
==仅服务器==
|
||||||
|
|
||||||
|
ECH PEM 密钥路径
|
||||||
|
|
||||||
|
#### config
|
||||||
|
|
||||||
|
==仅客户端==
|
||||||
|
|
||||||
|
ECH PEM 配置行数组
|
||||||
|
|
||||||
|
如果为空,将尝试从 DNS 加载。
|
||||||
|
|
||||||
|
#### config_path
|
||||||
|
|
||||||
|
==仅客户端==
|
||||||
|
|
||||||
|
ECH PEM 配置路径
|
||||||
|
|
||||||
|
如果为空,将尝试从 DNS 加载。
|
||||||
|
|
||||||
### ACME 字段
|
### ACME 字段
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
|||||||
defaultMode = options.DefaultMode
|
defaultMode = options.DefaultMode
|
||||||
}
|
}
|
||||||
if !common.Contains(server.modeList, defaultMode) {
|
if !common.Contains(server.modeList, defaultMode) {
|
||||||
server.modeList = append([]string{defaultMode}, server.modeList...)
|
server.modeList = append(server.modeList, defaultMode)
|
||||||
}
|
}
|
||||||
server.mode = defaultMode
|
server.mode = defaultMode
|
||||||
if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.ExternalController == "" {
|
if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.ExternalController == "" {
|
||||||
|
|||||||
@@ -11,6 +11,4 @@ const (
|
|||||||
CommandGroupExpand
|
CommandGroupExpand
|
||||||
CommandClashMode
|
CommandClashMode
|
||||||
CommandSetClashMode
|
CommandSetClashMode
|
||||||
CommandGetSystemProxyStatus
|
|
||||||
CommandSetSystemProxyEnabled
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -54,24 +53,9 @@ func (c *CommandClient) directConnect() (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandClient) directConnectWithRetry() (net.Conn, error) {
|
|
||||||
var (
|
|
||||||
conn net.Conn
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
conn, err = c.directConnect()
|
|
||||||
if err == nil {
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
time.Sleep(time.Duration(100+i*50) * time.Millisecond)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CommandClient) Connect() error {
|
func (c *CommandClient) Connect() error {
|
||||||
common.Close(c.conn)
|
common.Close(c.conn)
|
||||||
conn, err := c.directConnectWithRetry()
|
conn, err := c.directConnect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
runtimeDebug "runtime/debug"
|
runtimeDebug "runtime/debug"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *CommandClient) CloseConnections() error {
|
func (c *CommandClient) CloseConnections() error {
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ type CommandServer struct {
|
|||||||
|
|
||||||
type CommandServerHandler interface {
|
type CommandServerHandler interface {
|
||||||
ServiceReload() error
|
ServiceReload() error
|
||||||
GetSystemProxyStatus() *SystemProxyStatus
|
|
||||||
SetSystemProxyEnabled(isEnabled bool) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer {
|
func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer {
|
||||||
@@ -161,10 +159,6 @@ func (s *CommandServer) handleConnection(conn net.Conn) error {
|
|||||||
return s.handleModeConn(conn)
|
return s.handleModeConn(conn)
|
||||||
case CommandSetClashMode:
|
case CommandSetClashMode:
|
||||||
return s.handleSetClashMode(conn)
|
return s.handleSetClashMode(conn)
|
||||||
case CommandGetSystemProxyStatus:
|
|
||||||
return s.handleGetSystemProxyStatus(conn)
|
|
||||||
case CommandSetSystemProxyEnabled:
|
|
||||||
return s.handleSetSystemProxyEnabled(conn)
|
|
||||||
default:
|
default:
|
||||||
return E.New("unknown command: ", command)
|
return E.New("unknown command: ", command)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,13 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/memory"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatusMessage struct {
|
type StatusMessage struct {
|
||||||
Memory int64
|
Memory int64
|
||||||
MemoryInuse int64
|
|
||||||
Goroutines int32
|
Goroutines int32
|
||||||
ConnectionsIn int32
|
ConnectionsIn int32
|
||||||
ConnectionsOut int32
|
ConnectionsOut int32
|
||||||
@@ -26,8 +24,10 @@ type StatusMessage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CommandServer) readStatus() StatusMessage {
|
func (s *CommandServer) readStatus() StatusMessage {
|
||||||
|
var memStats runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&memStats)
|
||||||
var message StatusMessage
|
var message StatusMessage
|
||||||
message.Memory = int64(memory.Inuse())
|
message.Memory = int64(memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased)
|
||||||
message.Goroutines = int32(runtime.NumGoroutine())
|
message.Goroutines = int32(runtime.NumGoroutine())
|
||||||
message.ConnectionsOut = int32(conntrack.Count())
|
message.ConnectionsOut = int32(conntrack.Count())
|
||||||
|
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SystemProxyStatus struct {
|
|
||||||
Available bool
|
|
||||||
Enabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) {
|
|
||||||
conn, err := c.directConnectWithRetry()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandGetSystemProxyStatus))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var status SystemProxyStatus
|
|
||||||
err = binary.Read(conn, binary.BigEndian, &status.Available)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if status.Available {
|
|
||||||
err = binary.Read(conn, binary.BigEndian, &status.Enabled)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &status, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommandServer) handleGetSystemProxyStatus(conn net.Conn) error {
|
|
||||||
defer conn.Close()
|
|
||||||
status := s.handler.GetSystemProxyStatus()
|
|
||||||
err := binary.Write(conn, binary.BigEndian, status.Available)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if status.Available {
|
|
||||||
err = binary.Write(conn, binary.BigEndian, status.Enabled)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error {
|
|
||||||
conn, err := c.directConnect()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandSetSystemProxyEnabled))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = binary.Write(conn, binary.BigEndian, isEnabled)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return readError(conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommandServer) handleSetSystemProxyEnabled(conn net.Conn) error {
|
|
||||||
defer conn.Close()
|
|
||||||
var isEnabled bool
|
|
||||||
err := binary.Read(conn, binary.BigEndian, &isEnabled)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.handler.SetSystemProxyEnabled(isEnabled)
|
|
||||||
if err != nil {
|
|
||||||
return writeError(conn, err)
|
|
||||||
}
|
|
||||||
return writeError(conn, nil)
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ package libbox
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@@ -19,14 +18,12 @@ func RedirectStderr(path string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if runtime.GOOS != "android" {
|
if sUserID > 0 {
|
||||||
if sUserID > 0 {
|
err = outputFile.Chown(sUserID, sGroupID)
|
||||||
err = outputFile.Chown(sUserID, sGroupID)
|
if err != nil {
|
||||||
if err != nil {
|
outputFile.Close()
|
||||||
outputFile.Close()
|
os.Remove(outputFile.Name())
|
||||||
os.Remove(outputFile.Name())
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = unix.Dup2(int(outputFile.Fd()), int(os.Stderr.Fd()))
|
err = unix.Dup2(int(outputFile.Fd()), int(os.Stderr.Fd()))
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
runtimeDebug "runtime/debug"
|
runtimeDebug "runtime/debug"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetMemoryLimit(enabled bool) {
|
func SetMemoryLimit(enabled bool) {
|
||||||
const memoryLimit = 45 * 1024 * 1024
|
const memoryLimit = 30 * 1024 * 1024
|
||||||
const memoryLimitGo = memoryLimit / 1.5
|
|
||||||
if enabled {
|
if enabled {
|
||||||
runtimeDebug.SetGCPercent(10)
|
runtimeDebug.SetGCPercent(10)
|
||||||
runtimeDebug.SetMemoryLimit(memoryLimitGo)
|
runtimeDebug.SetMemoryLimit(memoryLimit)
|
||||||
conntrack.KillerEnabled = true
|
conntrack.KillerEnabled = true
|
||||||
conntrack.MemoryLimit = memoryLimit
|
conntrack.MemoryLimit = memoryLimit
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -163,29 +163,25 @@ func DecodeProfileContentRequest(data []byte) (*ProfileContentRequest, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProfileContent struct {
|
type ProfileContent struct {
|
||||||
Name string
|
Name string
|
||||||
Type int32
|
Type int32
|
||||||
Config string
|
Config string
|
||||||
RemotePath string
|
RemotePath string
|
||||||
AutoUpdate bool
|
AutoUpdate bool
|
||||||
AutoUpdateInterval int32
|
LastUpdated int64
|
||||||
LastUpdated int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ProfileContent) Encode() []byte {
|
func (c *ProfileContent) Encode() []byte {
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
buffer.WriteByte(MessageTypeProfileContent)
|
buffer.WriteByte(MessageTypeProfileContent)
|
||||||
buffer.WriteByte(1)
|
buffer.WriteByte(0)
|
||||||
writer := gzip.NewWriter(buffer)
|
writer := gzip.NewWriter(buffer)
|
||||||
rw.WriteVString(writer, c.Name)
|
rw.WriteVString(writer, c.Name)
|
||||||
binary.Write(writer, binary.BigEndian, c.Type)
|
binary.Write(writer, binary.BigEndian, c.Type)
|
||||||
rw.WriteVString(writer, c.Config)
|
rw.WriteVString(writer, c.Config)
|
||||||
if c.Type != ProfileTypeLocal {
|
if c.Type != ProfileTypeLocal {
|
||||||
rw.WriteVString(writer, c.RemotePath)
|
rw.WriteVString(writer, c.RemotePath)
|
||||||
}
|
|
||||||
if c.Type == ProfileTypeRemote {
|
|
||||||
binary.Write(writer, binary.BigEndian, c.AutoUpdate)
|
binary.Write(writer, binary.BigEndian, c.AutoUpdate)
|
||||||
binary.Write(writer, binary.BigEndian, c.AutoUpdateInterval)
|
|
||||||
binary.Write(writer, binary.BigEndian, c.LastUpdated)
|
binary.Write(writer, binary.BigEndian, c.LastUpdated)
|
||||||
}
|
}
|
||||||
writer.Flush()
|
writer.Flush()
|
||||||
@@ -206,8 +202,12 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
reader, err = gzip.NewReader(reader)
|
if version == 0 {
|
||||||
if err != nil {
|
reader, err = gzip.NewReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "unsupported profile")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return nil, E.Cause(err, "unsupported profile")
|
return nil, E.Cause(err, "unsupported profile")
|
||||||
}
|
}
|
||||||
var content ProfileContent
|
var content ProfileContent
|
||||||
@@ -228,18 +228,10 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if content.Type == ProfileTypeRemote || (version == 0 && content.Type != ProfileTypeLocal) {
|
|
||||||
err = binary.Read(reader, binary.BigEndian, &content.AutoUpdate)
|
err = binary.Read(reader, binary.BigEndian, &content.AutoUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if version >= 1 {
|
|
||||||
err = binary.Read(reader, binary.BigEndian, &content.AutoUpdateInterval)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = binary.Read(reader, binary.BigEndian, &content.LastUpdated)
|
err = binary.Read(reader, binary.BigEndian, &content.LastUpdated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import (
|
|||||||
"os/user"
|
"os/user"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/humanize"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -45,11 +46,7 @@ func Version() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FormatBytes(length int64) string {
|
func FormatBytes(length int64) string {
|
||||||
return humanize.Bytes(uint64(length))
|
return humanize.IBytes(uint64(length))
|
||||||
}
|
|
||||||
|
|
||||||
func FormatMemoryBytes(length int64) string {
|
|
||||||
return humanize.MemoryBytes(uint64(length))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProxyDisplayType(proxyType string) string {
|
func ProxyDisplayType(proxyType string) string {
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ require (
|
|||||||
berty.tech/go-libtor v1.0.385
|
berty.tech/go-libtor v1.0.385
|
||||||
github.com/Dreamacro/clash v1.17.0
|
github.com/Dreamacro/clash v1.17.0
|
||||||
github.com/caddyserver/certmagic v0.19.2
|
github.com/caddyserver/certmagic v0.19.2
|
||||||
|
github.com/cloudflare/circl v1.3.3
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
|
github.com/dustin/go-humanize v1.0.1
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
github.com/go-chi/chi/v5 v5.0.10
|
github.com/go-chi/chi/v5 v5.0.10
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
@@ -20,18 +22,18 @@ require (
|
|||||||
github.com/oschwald/maxminddb-golang v1.12.0
|
github.com/oschwald/maxminddb-golang v1.12.0
|
||||||
github.com/pires/go-proxyproto v0.7.0
|
github.com/pires/go-proxyproto v0.7.0
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a
|
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a
|
||||||
github.com/sagernet/gomobile v0.0.0-20230915142329-c6740b6d2950
|
github.com/sagernet/gomobile v0.0.0-20230728014906-3de089147f59
|
||||||
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2
|
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2
|
||||||
github.com/sagernet/quic-go v0.0.0-20230911082307-390b7c274032
|
github.com/sagernet/quic-go v0.0.0-20230831052420-45809eee2e86
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||||
github.com/sagernet/sing v0.2.10-0.20230925102642-b0e07300a98c
|
github.com/sagernet/sing v0.2.10-0.20230830132630-30bf19f2833c
|
||||||
github.com/sagernet/sing-dns v0.1.9-0.20230925101650-9cc09becd01e
|
github.com/sagernet/sing-dns v0.1.9-0.20230824120133-4d5cbceb40c1
|
||||||
github.com/sagernet/sing-mux v0.1.3-0.20230907005326-7befbadbf314
|
github.com/sagernet/sing-mux v0.1.3-0.20230830095209-2a10ebd53ba8
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.5-0.20230907005610-126234728ca0
|
github.com/sagernet/sing-shadowsocks v0.2.4
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.1.4-0.20230907005906-5d2917b29248
|
github.com/sagernet/sing-shadowsocks2 v0.1.3
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4
|
github.com/sagernet/sing-shadowtls v0.1.4
|
||||||
github.com/sagernet/sing-tun v0.1.12-0.20230925100705-ef831c3485cf
|
github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641
|
||||||
github.com/sagernet/sing-vmess v0.1.8-0.20230907010359-161fb0ac716b
|
github.com/sagernet/sing-vmess v0.1.7
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
|
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
|
||||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||||
@@ -45,7 +47,7 @@ require (
|
|||||||
golang.org/x/crypto v0.12.0
|
golang.org/x/crypto v0.12.0
|
||||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
|
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
|
||||||
golang.org/x/net v0.14.0
|
golang.org/x/net v0.14.0
|
||||||
golang.org/x/sys v0.12.0
|
golang.org/x/sys v0.11.0
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
google.golang.org/grpc v1.57.0
|
google.golang.org/grpc v1.57.0
|
||||||
google.golang.org/protobuf v1.31.0
|
google.golang.org/protobuf v1.31.0
|
||||||
@@ -58,7 +60,6 @@ require (
|
|||||||
github.com/Dreamacro/protobytes v0.0.0-20230617041236-6500a9f4f158 // indirect
|
github.com/Dreamacro/protobytes v0.0.0-20230617041236-6500a9f4f158 // indirect
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
github.com/cloudflare/circl v1.3.3 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||||
@@ -98,34 +100,34 @@ github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a h1:wZHruBx
|
|||||||
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a/go.mod h1:dNV1ZP9y3qx5ltULeKaQZTZWTLHflgW5DES+Ses7cMI=
|
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a/go.mod h1:dNV1ZP9y3qx5ltULeKaQZTZWTLHflgW5DES+Ses7cMI=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||||
github.com/sagernet/gomobile v0.0.0-20230915142329-c6740b6d2950 h1:hUz/2mJLgi7l2H36JGpDY+jou9FmI6kAm0ZkU+xPpgE=
|
github.com/sagernet/gomobile v0.0.0-20230728014906-3de089147f59 h1:vN4divY6LYHcYmiTsCHNPmGZtEsEKJzh81LyvgAQfEQ=
|
||||||
github.com/sagernet/gomobile v0.0.0-20230915142329-c6740b6d2950/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU=
|
github.com/sagernet/gomobile v0.0.0-20230728014906-3de089147f59/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU=
|
||||||
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTSWt6hdPrARORfoYvuUczynvRLrueo=
|
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTSWt6hdPrARORfoYvuUczynvRLrueo=
|
||||||
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA=
|
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/quic-go v0.0.0-20230911082307-390b7c274032 h1:J900zKCRGU+0gnPLIj+qXdmun4/AQ3iUmNREJ9fNdHQ=
|
github.com/sagernet/quic-go v0.0.0-20230831052420-45809eee2e86 h1:g4TEg9inAtA1FDTXpNrvmx72nN5mTOLQrJce6fVxF9g=
|
||||||
github.com/sagernet/quic-go v0.0.0-20230911082307-390b7c274032/go.mod h1:O4Cj7TmMOvqD6S0XMqJRZfcYzA3m0H0ARbbaJFB0p7A=
|
github.com/sagernet/quic-go v0.0.0-20230831052420-45809eee2e86/go.mod h1:O4Cj7TmMOvqD6S0XMqJRZfcYzA3m0H0ARbbaJFB0p7A=
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
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/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||||
github.com/sagernet/sing v0.2.10-0.20230925102642-b0e07300a98c h1:Vb5aH/BPW1elmMx9Miz8SqzoqlVRCzikttyMo7wEQUM=
|
github.com/sagernet/sing v0.2.10-0.20230830132630-30bf19f2833c h1:J2ptRncTNy+ZHfcFYSBfTmpvmgNlSEUZz6sDjh1np/Y=
|
||||||
github.com/sagernet/sing v0.2.10-0.20230925102642-b0e07300a98c/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
|
github.com/sagernet/sing v0.2.10-0.20230830132630-30bf19f2833c/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
|
||||||
github.com/sagernet/sing-dns v0.1.9-0.20230925101650-9cc09becd01e h1:h903oI5Z2dIZv/lGLvQ67cC8O79uaeGrFhdxVssvh5c=
|
github.com/sagernet/sing-dns v0.1.9-0.20230824120133-4d5cbceb40c1 h1:5w+jXz8y/8UQAxO74TjftN5okYkpg5mGvVxXunlKdqI=
|
||||||
github.com/sagernet/sing-dns v0.1.9-0.20230925101650-9cc09becd01e/go.mod h1:Kg98PBJEg/08jsNFtmZWmPomhskn9Ausn50ecNm4M+8=
|
github.com/sagernet/sing-dns v0.1.9-0.20230824120133-4d5cbceb40c1/go.mod h1:Kg98PBJEg/08jsNFtmZWmPomhskn9Ausn50ecNm4M+8=
|
||||||
github.com/sagernet/sing-mux v0.1.3-0.20230907005326-7befbadbf314 h1:P5+NZGMH8KSI3L8lKw1znxdRi0tIpWbGYjmv8GrFHrQ=
|
github.com/sagernet/sing-mux v0.1.3-0.20230830095209-2a10ebd53ba8 h1:UyUkEUEGqfIGqzOJ7OuJry4slgcT/qb0etDJ+89LTAs=
|
||||||
github.com/sagernet/sing-mux v0.1.3-0.20230907005326-7befbadbf314/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY=
|
github.com/sagernet/sing-mux v0.1.3-0.20230830095209-2a10ebd53ba8/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.5-0.20230907005610-126234728ca0 h1:9wHYWxH+fcs01PM2+DylA8LNNY3ElnZykQo9rysng8U=
|
github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.5-0.20230907005610-126234728ca0/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM=
|
github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.1.4-0.20230907005906-5d2917b29248 h1:JTFfy/LDmVFEK4KZJEujmC1iO8+aoF4unYhhZZRzRq4=
|
github.com/sagernet/sing-shadowsocks2 v0.1.3 h1:WXoLvCFi5JTFBRYorf1YePGYIQyJ/zbsBM6Fwbl5kGA=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.1.4-0.20230907005906-5d2917b29248/go.mod h1:DOhJc/cLeqRv0wuePrQso+iUmDxOnWF4eT/oMcRzYFw=
|
github.com/sagernet/sing-shadowsocks2 v0.1.3/go.mod h1:DOhJc/cLeqRv0wuePrQso+iUmDxOnWF4eT/oMcRzYFw=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
||||||
github.com/sagernet/sing-tun v0.1.12-0.20230925100705-ef831c3485cf h1:7migLbdEzt57BCCzBxpTbmyszhjsoebMxKyT24Wu0Y8=
|
github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641 h1:a8lktNrCWZJisB+nPraW+qB73ZofgPtGmlfqNYcO79g=
|
||||||
github.com/sagernet/sing-tun v0.1.12-0.20230925100705-ef831c3485cf/go.mod h1:+YImslQMLgMQcVgZZ9IK4ue1o/605VSU90amHUcp4hA=
|
github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641/go.mod h1:+YImslQMLgMQcVgZZ9IK4ue1o/605VSU90amHUcp4hA=
|
||||||
github.com/sagernet/sing-vmess v0.1.8-0.20230907010359-161fb0ac716b h1:2ezfJtH5JosiEwJhVa+rimQ6ps/t2+7h+mOzMoiaZnA=
|
github.com/sagernet/sing-vmess v0.1.7 h1:TM8FFLsXmlXH9XT8/oDgc6PC5BOzrg6OzyEe01is2r4=
|
||||||
github.com/sagernet/sing-vmess v0.1.8-0.20230907010359-161fb0ac716b/go.mod h1:1qkC1L1T2sxnS/NuO6HU72S8TkltV+EXoKGR29m/Yss=
|
github.com/sagernet/sing-vmess v0.1.7/go.mod h1:1qkC1L1T2sxnS/NuO6HU72S8TkltV+EXoKGR29m/Yss=
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q=
|
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q=
|
||||||
@@ -190,8 +192,8 @@ golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
|
|||||||
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
|
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
|
||||||
case C.TypeTUIC:
|
case C.TypeTUIC:
|
||||||
return NewTUIC(ctx, router, logger, options.Tag, options.TUICOptions)
|
return NewTUIC(ctx, router, logger, options.Tag, options.TUICOptions)
|
||||||
|
case C.TypeHysteria2:
|
||||||
|
return NewHysteria2(ctx, router, logger, options.Tag, options.Hysteria2Options)
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown inbound type: ", options.Type)
|
return nil, E.New("unknown inbound type: ", options.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge
|
|||||||
authenticator: auth.NewAuthenticator(options.Users),
|
authenticator: auth.NewAuthenticator(options.Users),
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-8
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/sagernet/quic-go"
|
"github.com/sagernet/quic-go"
|
||||||
"github.com/sagernet/quic-go/congestion"
|
"github.com/sagernet/quic-go/congestion"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/qtls"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -35,7 +36,7 @@ type Hysteria struct {
|
|||||||
xplusKey []byte
|
xplusKey []byte
|
||||||
sendBPS uint64
|
sendBPS uint64
|
||||||
recvBPS uint64
|
recvBPS uint64
|
||||||
listener *quic.Listener
|
listener qtls.QUICListener
|
||||||
udpAccess sync.RWMutex
|
udpAccess sync.RWMutex
|
||||||
udpSessionId uint32
|
udpSessionId uint32
|
||||||
udpSessions map[uint32]chan *hysteria.UDPMessage
|
udpSessions map[uint32]chan *hysteria.UDPMessage
|
||||||
@@ -126,7 +127,7 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if len(options.TLS.ALPN) == 0 {
|
if len(options.TLS.ALPN) == 0 {
|
||||||
options.TLS.ALPN = []string{hysteria.DefaultALPN}
|
options.TLS.ALPN = []string{hysteria.DefaultALPN}
|
||||||
}
|
}
|
||||||
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -147,11 +148,7 @@ func (h *Hysteria) Start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rawConfig, err := h.tlsConfig.Config()
|
listener, err := qtls.Listen(packetConn, h.tlsConfig, h.quicConfig)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
listener, err := quic.Listen(packetConn, rawConfig, h.quicConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -333,7 +330,7 @@ func (h *Hysteria) Close() error {
|
|||||||
h.udpAccess.Unlock()
|
h.udpAccess.Unlock()
|
||||||
return common.Close(
|
return common.Close(
|
||||||
&h.myInboundAdapter,
|
&h.myInboundAdapter,
|
||||||
common.PtrOrNil(h.listener),
|
h.listener,
|
||||||
h.tlsConfig,
|
h.tlsConfig,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
//go:build with_quic
|
||||||
|
|
||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/transport/hysteria2"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/auth"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.Inbound = (*Hysteria2)(nil)
|
||||||
|
|
||||||
|
type Hysteria2 struct {
|
||||||
|
myInboundAdapter
|
||||||
|
tlsConfig tls.ServerConfig
|
||||||
|
server *hysteria2.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (*Hysteria2, error) {
|
||||||
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
|
return nil, C.ErrTLSRequired
|
||||||
|
}
|
||||||
|
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var salamanderPassword string
|
||||||
|
if options.Obfs != nil {
|
||||||
|
if options.Obfs.Password == "" {
|
||||||
|
return nil, E.New("missing obfs password")
|
||||||
|
}
|
||||||
|
switch options.Obfs.Type {
|
||||||
|
case hysteria2.ObfsTypeSalamander:
|
||||||
|
salamanderPassword = options.Obfs.Password
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown obfs type: ", options.Obfs.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var masqueradeHandler http.Handler
|
||||||
|
if options.Masquerade != "" {
|
||||||
|
masqueradeURL, err := url.Parse(options.Masquerade)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse masquerade URL")
|
||||||
|
}
|
||||||
|
switch masqueradeURL.Scheme {
|
||||||
|
case "file":
|
||||||
|
masqueradeHandler = http.FileServer(http.Dir(masqueradeURL.Path))
|
||||||
|
case "http", "https":
|
||||||
|
masqueradeHandler = &httputil.ReverseProxy{
|
||||||
|
Rewrite: func(r *httputil.ProxyRequest) {
|
||||||
|
r.SetURL(masqueradeURL)
|
||||||
|
r.Out.Host = r.In.Host
|
||||||
|
},
|
||||||
|
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
w.WriteHeader(http.StatusBadGateway)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inbound := &Hysteria2{
|
||||||
|
myInboundAdapter: myInboundAdapter{
|
||||||
|
protocol: C.TypeHysteria2,
|
||||||
|
network: []string{N.NetworkUDP},
|
||||||
|
ctx: ctx,
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
tag: tag,
|
||||||
|
listenOptions: options.ListenOptions,
|
||||||
|
},
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
server, err := hysteria2.NewServer(hysteria2.ServerOptions{
|
||||||
|
Context: ctx,
|
||||||
|
Logger: logger,
|
||||||
|
SendBPS: uint64(options.UpMbps * 1024 * 1024),
|
||||||
|
ReceiveBPS: uint64(options.DownMbps * 1024 * 1024),
|
||||||
|
SalamanderPassword: salamanderPassword,
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
|
Users: common.Map(options.Users, func(it option.Hysteria2User) hysteria2.User {
|
||||||
|
return hysteria2.User(it)
|
||||||
|
}),
|
||||||
|
IgnoreClientBandwidth: options.IgnoreClientBandwidth,
|
||||||
|
Handler: adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, nil),
|
||||||
|
MasqueradeHandler: masqueradeHandler,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inbound.server = server
|
||||||
|
return inbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria2) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
ctx = log.ContextWithNewID(ctx)
|
||||||
|
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||||
|
metadata = h.createMetadata(conn, metadata)
|
||||||
|
metadata.User, _ = auth.UserFromContext[string](ctx)
|
||||||
|
return h.router.RouteConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria2) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
ctx = log.ContextWithNewID(ctx)
|
||||||
|
metadata = h.createPacketMetadata(conn, metadata)
|
||||||
|
metadata.User, _ = auth.UserFromContext[string](ctx)
|
||||||
|
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
|
||||||
|
return h.router.RoutePacketConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria2) Start() error {
|
||||||
|
if h.tlsConfig != nil {
|
||||||
|
err := h.tlsConfig.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
packetConn, err := h.myInboundAdapter.ListenUDP()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return h.server.Start(packetConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria2) Close() error {
|
||||||
|
return common.Close(
|
||||||
|
&h.myInboundAdapter,
|
||||||
|
h.tlsConfig,
|
||||||
|
common.PtrOrNil(h.server),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -14,3 +14,7 @@ import (
|
|||||||
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) {
|
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) {
|
||||||
return nil, C.ErrQUICNotIncluded
|
return nil, C.ErrQUICNotIncluded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) {
|
||||||
|
return nil, C.ErrQUICNotIncluded
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@ func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
return nil, E.New("missing users")
|
return nil, E.New("missing users")
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-7
@@ -3,28 +3,39 @@
|
|||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/sagernet/quic-go"
|
||||||
"github.com/sagernet/quic-go/http3"
|
"github.com/sagernet/quic-go/http3"
|
||||||
|
"github.com/sagernet/sing-box/common/qtls"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (n *Naive) configureHTTP3Listener() error {
|
func (n *Naive) configureHTTP3Listener() error {
|
||||||
tlsConfig, err := n.tlsConfig.Config()
|
err := qtls.ConfigureHTTP3(n.tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
h3Server := &http3.Server{
|
|
||||||
Port: int(n.listenOptions.ListenPort),
|
|
||||||
TLSConfig: tlsConfig,
|
|
||||||
Handler: n,
|
|
||||||
}
|
|
||||||
|
|
||||||
udpConn, err := n.ListenUDP()
|
udpConn, err := n.ListenUDP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
quicListener, err := qtls.ListenEarly(udpConn, n.tlsConfig, &quic.Config{
|
||||||
|
MaxIncomingStreams: 1 << 60,
|
||||||
|
Allow0RTT: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
udpConn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h3Server := &http3.Server{
|
||||||
|
Port: int(n.listenOptions.ListenPort),
|
||||||
|
Handler: n,
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
sErr := h3Server.Serve(udpConn)
|
sErr := h3Server.ServeListener(quicListener)
|
||||||
udpConn.Close()
|
udpConn.Close()
|
||||||
if sErr != nil && !E.IsClosedOrCanceled(sErr) {
|
if sErr != nil && !E.IsClosedOrCanceled(sErr) {
|
||||||
n.logger.Error("http3 server serve error: ", sErr)
|
n.logger.Error("http3 server serve error: ", sErr)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
|
func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
|
||||||
@@ -68,7 +69,7 @@ func newShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
|||||||
case common.Contains(shadowaead.List, options.Method):
|
case common.Contains(shadowaead.List, options.Method):
|
||||||
inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, udpTimeout, inbound.upstreamContextHandler())
|
inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, udpTimeout, inbound.upstreamContextHandler())
|
||||||
case common.Contains(shadowaead_2022.List, options.Method):
|
case common.Contains(shadowaead_2022.List, options.Method):
|
||||||
inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, udpTimeout, inbound.upstreamContextHandler(), router.TimeFunc())
|
inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, udpTimeout, inbound.upstreamContextHandler(), ntp.TimeFuncFromContext(ctx))
|
||||||
default:
|
default:
|
||||||
err = E.New("unsupported method: ", options.Method)
|
err = E.New("unsupported method: ", options.Method)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -61,7 +62,7 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.
|
|||||||
options.Password,
|
options.Password,
|
||||||
udpTimeout,
|
udpTimeout,
|
||||||
adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound),
|
adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound),
|
||||||
router.TimeFunc(),
|
ntp.TimeFuncFromContext(ctx),
|
||||||
)
|
)
|
||||||
} else if common.Contains(shadowaead.List, options.Method) {
|
} else if common.Contains(shadowaead.List, options.Method) {
|
||||||
service, err = shadowaead.NewMultiService[int](
|
service, err = shadowaead.NewMultiService[int](
|
||||||
|
|||||||
+1
-12
@@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-shadowtls"
|
"github.com/sagernet/sing-shadowtls"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/auth"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,7 +66,7 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
},
|
},
|
||||||
HandshakeForServerName: handshakeForServerName,
|
HandshakeForServerName: handshakeForServerName,
|
||||||
StrictMode: options.StrictMode,
|
StrictMode: options.StrictMode,
|
||||||
Handler: adapter.NewUpstreamContextHandler(inbound.newConnection, nil, inbound),
|
Handler: inbound.upstreamContextHandler(),
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -81,13 +80,3 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
func (h *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (h *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ShadowTLS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
|
||||||
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
|
|
||||||
metadata.User = userName
|
|
||||||
h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination)
|
|
||||||
} else {
|
|
||||||
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
|
||||||
}
|
|
||||||
return h.router.RouteConnection(ctx, conn, metadata)
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
|
|||||||
users: options.Users,
|
users: options.Users,
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-6
@@ -34,11 +34,7 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
|
|||||||
if options.TLS == nil || !options.TLS.Enabled {
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
return nil, C.ErrTLSRequired
|
return nil, C.ErrTLSRequired
|
||||||
}
|
}
|
||||||
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawConfig, err := tlsConfig.Config()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -67,7 +63,7 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
|
|||||||
server, err := tuic.NewServer(tuic.ServerOptions{
|
server, err := tuic.NewServer(tuic.ServerOptions{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
TLSConfig: rawConfig,
|
TLSConfig: tlsConfig,
|
||||||
Users: users,
|
Users: users,
|
||||||
CongestionControl: options.CongestionControl,
|
CongestionControl: options.CongestionControl,
|
||||||
AuthTimeout: time.Duration(options.AuthTimeout),
|
AuthTimeout: time.Duration(options.AuthTimeout),
|
||||||
@@ -84,6 +80,7 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
|
|||||||
|
|
||||||
func (h *TUIC) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (h *TUIC) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
ctx = log.ContextWithNewID(ctx)
|
ctx = log.ContextWithNewID(ctx)
|
||||||
|
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||||
metadata = h.createMetadata(conn, metadata)
|
metadata = h.createMetadata(conn, metadata)
|
||||||
metadata.User, _ = auth.UserFromContext[string](ctx)
|
metadata.User, _ = auth.UserFromContext[string](ctx)
|
||||||
return h.router.RouteConnection(ctx, conn, metadata)
|
return h.router.RouteConnection(ctx, conn, metadata)
|
||||||
@@ -114,6 +111,7 @@ func (h *TUIC) Start() error {
|
|||||||
func (h *TUIC) Close() error {
|
func (h *TUIC) Close() error {
|
||||||
return common.Close(
|
return common.Close(
|
||||||
&h.myInboundAdapter,
|
&h.myInboundAdapter,
|
||||||
|
h.tlsConfig,
|
||||||
common.PtrOrNil(h.server),
|
common.PtrOrNil(h.server),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
inbound.service = service
|
inbound.service = service
|
||||||
var err error
|
var err error
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
inbound.tlsConfig, err = tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
inbound.tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -19,6 +19,7 @@ import (
|
|||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -50,7 +51,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
users: options.Users,
|
users: options.Users,
|
||||||
}
|
}
|
||||||
var serviceOptions []vmess.ServiceOption
|
var serviceOptions []vmess.ServiceOption
|
||||||
if timeFunc := router.TimeFunc(); timeFunc != nil {
|
if timeFunc := ntp.TimeFuncFromContext(ctx); timeFunc != nil {
|
||||||
serviceOptions = append(serviceOptions, vmess.ServiceWithTimeFunc(timeFunc))
|
serviceOptions = append(serviceOptions, vmess.ServiceWithTimeFunc(timeFunc))
|
||||||
}
|
}
|
||||||
if options.Transport != nil && options.Transport.Type != "" {
|
if options.Transport != nil && options.Transport.Type != "" {
|
||||||
@@ -69,7 +70,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
inbound.tlsConfig, err = tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
inbound.tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ nav:
|
|||||||
- ShadowTLS: configuration/inbound/shadowtls.md
|
- ShadowTLS: configuration/inbound/shadowtls.md
|
||||||
- VLESS: configuration/inbound/vless.md
|
- VLESS: configuration/inbound/vless.md
|
||||||
- TUIC: configuration/inbound/tuic.md
|
- TUIC: configuration/inbound/tuic.md
|
||||||
|
- Hysteria2: configuration/inbound/hysteria2.md
|
||||||
- Tun: configuration/inbound/tun.md
|
- Tun: configuration/inbound/tun.md
|
||||||
- Redirect: configuration/inbound/redirect.md
|
- Redirect: configuration/inbound/redirect.md
|
||||||
- TProxy: configuration/inbound/tproxy.md
|
- TProxy: configuration/inbound/tproxy.md
|
||||||
@@ -105,6 +106,7 @@ nav:
|
|||||||
- ShadowsocksR: configuration/outbound/shadowsocksr.md
|
- ShadowsocksR: configuration/outbound/shadowsocksr.md
|
||||||
- VLESS: configuration/outbound/vless.md
|
- VLESS: configuration/outbound/vless.md
|
||||||
- TUIC: configuration/outbound/tuic.md
|
- TUIC: configuration/outbound/tuic.md
|
||||||
|
- Hysteria2: configuration/outbound/hysteria2.md
|
||||||
- Tor: configuration/outbound/tor.md
|
- Tor: configuration/outbound/tor.md
|
||||||
- SSH: configuration/outbound/ssh.md
|
- SSH: configuration/outbound/ssh.md
|
||||||
- DNS: configuration/outbound/dns.md
|
- DNS: configuration/outbound/dns.md
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.TimeService = (*Service)(nil)
|
var _ ntp.TimeService = (*Service)(nil)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|||||||
+9
-9
@@ -3,7 +3,7 @@ package option
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/humanize"
|
"github.com/dustin/go-humanize"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DebugOptions struct {
|
type DebugOptions struct {
|
||||||
@@ -13,21 +13,21 @@ type DebugOptions struct {
|
|||||||
MaxThreads *int `json:"max_threads,omitempty"`
|
MaxThreads *int `json:"max_threads,omitempty"`
|
||||||
PanicOnFault *bool `json:"panic_on_fault,omitempty"`
|
PanicOnFault *bool `json:"panic_on_fault,omitempty"`
|
||||||
TraceBack string `json:"trace_back,omitempty"`
|
TraceBack string `json:"trace_back,omitempty"`
|
||||||
MemoryLimit MemoryBytes `json:"memory_limit,omitempty"`
|
MemoryLimit BytesLength `json:"memory_limit,omitempty"`
|
||||||
OOMKiller *bool `json:"oom_killer,omitempty"`
|
OOMKiller *bool `json:"oom_killer,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MemoryBytes uint64
|
type BytesLength int64
|
||||||
|
|
||||||
func (l MemoryBytes) MarshalJSON() ([]byte, error) {
|
func (l BytesLength) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(humanize.MemoryBytes(uint64(l)))
|
return json.Marshal(humanize.IBytes(uint64(l)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *MemoryBytes) UnmarshalJSON(bytes []byte) error {
|
func (l *BytesLength) UnmarshalJSON(bytes []byte) error {
|
||||||
var valueInteger int64
|
var valueInteger int64
|
||||||
err := json.Unmarshal(bytes, &valueInteger)
|
err := json.Unmarshal(bytes, &valueInteger)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
*l = MemoryBytes(valueInteger)
|
*l = BytesLength(valueInteger)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var valueString string
|
var valueString string
|
||||||
@@ -35,10 +35,10 @@ func (l *MemoryBytes) UnmarshalJSON(bytes []byte) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
parsedValue, err := humanize.ParseMemoryBytes(valueString)
|
parsedValue, err := humanize.ParseBytes(valueString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
*l = MemoryBytes(parsedValue)
|
*l = BytesLength(parsedValue)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
type Hysteria2InboundOptions struct {
|
||||||
|
ListenOptions
|
||||||
|
UpMbps int `json:"up_mbps,omitempty"`
|
||||||
|
DownMbps int `json:"down_mbps,omitempty"`
|
||||||
|
Obfs *Hysteria2Obfs `json:"obfs,omitempty"`
|
||||||
|
Users []Hysteria2User `json:"users,omitempty"`
|
||||||
|
IgnoreClientBandwidth bool `json:"ignore_client_bandwidth,omitempty"`
|
||||||
|
TLS *InboundTLSOptions `json:"tls,omitempty"`
|
||||||
|
Masquerade string `json:"masquerade,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hysteria2Obfs struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hysteria2User struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hysteria2OutboundOptions struct {
|
||||||
|
DialerOptions
|
||||||
|
ServerOptions
|
||||||
|
UpMbps int `json:"up_mbps,omitempty"`
|
||||||
|
DownMbps int `json:"down_mbps,omitempty"`
|
||||||
|
Obfs *Hysteria2Obfs `json:"obfs,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
Network NetworkList `json:"network,omitempty"`
|
||||||
|
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ type _Inbound struct {
|
|||||||
ShadowTLSOptions ShadowTLSInboundOptions `json:"-"`
|
ShadowTLSOptions ShadowTLSInboundOptions `json:"-"`
|
||||||
VLESSOptions VLESSInboundOptions `json:"-"`
|
VLESSOptions VLESSInboundOptions `json:"-"`
|
||||||
TUICOptions TUICInboundOptions `json:"-"`
|
TUICOptions TUICInboundOptions `json:"-"`
|
||||||
|
Hysteria2Options Hysteria2InboundOptions `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Inbound _Inbound
|
type Inbound _Inbound
|
||||||
@@ -61,6 +62,8 @@ func (h Inbound) MarshalJSON() ([]byte, error) {
|
|||||||
v = h.VLESSOptions
|
v = h.VLESSOptions
|
||||||
case C.TypeTUIC:
|
case C.TypeTUIC:
|
||||||
v = h.TUICOptions
|
v = h.TUICOptions
|
||||||
|
case C.TypeHysteria2:
|
||||||
|
v = h.Hysteria2Options
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown inbound type: ", h.Type)
|
return nil, E.New("unknown inbound type: ", h.Type)
|
||||||
}
|
}
|
||||||
@@ -104,6 +107,8 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
|
|||||||
v = &h.VLESSOptions
|
v = &h.VLESSOptions
|
||||||
case C.TypeTUIC:
|
case C.TypeTUIC:
|
||||||
v = &h.TUICOptions
|
v = &h.TUICOptions
|
||||||
|
case C.TypeHysteria2:
|
||||||
|
v = &h.Hysteria2Options
|
||||||
default:
|
default:
|
||||||
return E.New("unknown inbound type: ", h.Type)
|
return E.New("unknown inbound type: ", h.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type _Outbound struct {
|
|||||||
ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"`
|
ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"`
|
||||||
VLESSOptions VLESSOutboundOptions `json:"-"`
|
VLESSOptions VLESSOutboundOptions `json:"-"`
|
||||||
TUICOptions TUICOutboundOptions `json:"-"`
|
TUICOptions TUICOutboundOptions `json:"-"`
|
||||||
|
Hysteria2Options Hysteria2OutboundOptions `json:"-"`
|
||||||
SelectorOptions SelectorOutboundOptions `json:"-"`
|
SelectorOptions SelectorOutboundOptions `json:"-"`
|
||||||
URLTestOptions URLTestOutboundOptions `json:"-"`
|
URLTestOptions URLTestOutboundOptions `json:"-"`
|
||||||
}
|
}
|
||||||
@@ -63,6 +64,8 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
|
|||||||
v = h.VLESSOptions
|
v = h.VLESSOptions
|
||||||
case C.TypeTUIC:
|
case C.TypeTUIC:
|
||||||
v = h.TUICOptions
|
v = h.TUICOptions
|
||||||
|
case C.TypeHysteria2:
|
||||||
|
v = h.Hysteria2Options
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
v = h.SelectorOptions
|
v = h.SelectorOptions
|
||||||
case C.TypeURLTest:
|
case C.TypeURLTest:
|
||||||
@@ -110,6 +113,8 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
|
|||||||
v = &h.VLESSOptions
|
v = &h.VLESSOptions
|
||||||
case C.TypeTUIC:
|
case C.TypeTUIC:
|
||||||
v = &h.TUICOptions
|
v = &h.TUICOptions
|
||||||
|
case C.TypeHysteria2:
|
||||||
|
v = &h.Hysteria2Options
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
v = &h.SelectorOptions
|
v = &h.SelectorOptions
|
||||||
case C.TypeURLTest:
|
case C.TypeURLTest:
|
||||||
|
|||||||
+16
-6
@@ -8,11 +8,12 @@ type InboundTLSOptions struct {
|
|||||||
MinVersion string `json:"min_version,omitempty"`
|
MinVersion string `json:"min_version,omitempty"`
|
||||||
MaxVersion string `json:"max_version,omitempty"`
|
MaxVersion string `json:"max_version,omitempty"`
|
||||||
CipherSuites Listable[string] `json:"cipher_suites,omitempty"`
|
CipherSuites Listable[string] `json:"cipher_suites,omitempty"`
|
||||||
Certificate string `json:"certificate,omitempty"`
|
Certificate Listable[string] `json:"certificate,omitempty"`
|
||||||
CertificatePath string `json:"certificate_path,omitempty"`
|
CertificatePath string `json:"certificate_path,omitempty"`
|
||||||
Key string `json:"key,omitempty"`
|
Key Listable[string] `json:"key,omitempty"`
|
||||||
KeyPath string `json:"key_path,omitempty"`
|
KeyPath string `json:"key_path,omitempty"`
|
||||||
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
||||||
|
ECH *InboundECHOptions `json:"ech,omitempty"`
|
||||||
Reality *InboundRealityOptions `json:"reality,omitempty"`
|
Reality *InboundRealityOptions `json:"reality,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,11 +46,20 @@ type InboundRealityHandshakeOptions struct {
|
|||||||
DialerOptions
|
DialerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InboundECHOptions struct {
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"`
|
||||||
|
DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"`
|
||||||
|
Key Listable[string] `json:"key,omitempty"`
|
||||||
|
KeyPath string `json:"key_path,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type OutboundECHOptions struct {
|
type OutboundECHOptions struct {
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"`
|
PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"`
|
||||||
DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"`
|
DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"`
|
||||||
Config string `json:"config,omitempty"`
|
Config Listable[string] `json:"config,omitempty"`
|
||||||
|
ConfigPath string `json:"config_path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundUTLSOptions struct {
|
type OutboundUTLSOptions struct {
|
||||||
|
|||||||
+3
-1
@@ -30,7 +30,7 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, t
|
|||||||
case C.TypeSOCKS:
|
case C.TypeSOCKS:
|
||||||
return NewSocks(router, logger, tag, options.SocksOptions)
|
return NewSocks(router, logger, tag, options.SocksOptions)
|
||||||
case C.TypeHTTP:
|
case C.TypeHTTP:
|
||||||
return NewHTTP(router, logger, tag, options.HTTPOptions)
|
return NewHTTP(ctx, router, logger, tag, options.HTTPOptions)
|
||||||
case C.TypeShadowsocks:
|
case C.TypeShadowsocks:
|
||||||
return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions)
|
return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions)
|
||||||
case C.TypeVMess:
|
case C.TypeVMess:
|
||||||
@@ -53,6 +53,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, t
|
|||||||
return NewVLESS(ctx, router, logger, tag, options.VLESSOptions)
|
return NewVLESS(ctx, router, logger, tag, options.VLESSOptions)
|
||||||
case C.TypeTUIC:
|
case C.TypeTUIC:
|
||||||
return NewTUIC(ctx, router, logger, tag, options.TUICOptions)
|
return NewTUIC(ctx, router, logger, tag, options.TUICOptions)
|
||||||
|
case C.TypeHysteria2:
|
||||||
|
return NewHysteria2(ctx, router, logger, tag, options.Hysteria2Options)
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
return NewSelector(router, logger, tag, options.SelectorOptions)
|
return NewSelector(router, logger, tag, options.SelectorOptions)
|
||||||
case C.TypeURLTest:
|
case C.TypeURLTest:
|
||||||
|
|||||||
+1
-62
@@ -70,28 +70,6 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
|
|||||||
return CopyEarlyConn(ctx, conn, outConn)
|
return CopyEarlyConn(ctx, conn, outConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
|
||||||
ctx = adapter.WithContext(ctx, &metadata)
|
|
||||||
var outConn net.Conn
|
|
||||||
var err error
|
|
||||||
if len(metadata.DestinationAddresses) > 0 {
|
|
||||||
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
|
|
||||||
} else if metadata.Destination.IsFqdn() {
|
|
||||||
var destinationAddresses []netip.Addr
|
|
||||||
destinationAddresses, err = router.LookupDefault(ctx, metadata.Destination.Fqdn)
|
|
||||||
if err != nil {
|
|
||||||
return N.HandshakeFailure(conn, err)
|
|
||||||
}
|
|
||||||
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, destinationAddresses)
|
|
||||||
} else {
|
|
||||||
outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return N.HandshakeFailure(conn, err)
|
|
||||||
}
|
|
||||||
return CopyEarlyConn(ctx, conn, outConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error {
|
func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
ctx = adapter.WithContext(ctx, &metadata)
|
ctx = adapter.WithContext(ctx, &metadata)
|
||||||
var outConn net.PacketConn
|
var outConn net.PacketConn
|
||||||
@@ -121,48 +99,11 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
|
|||||||
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn))
|
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn))
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error {
|
|
||||||
ctx = adapter.WithContext(ctx, &metadata)
|
|
||||||
var outConn net.PacketConn
|
|
||||||
var destinationAddress netip.Addr
|
|
||||||
var err error
|
|
||||||
if len(metadata.DestinationAddresses) > 0 {
|
|
||||||
outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
|
|
||||||
} else if metadata.Destination.IsFqdn() {
|
|
||||||
var destinationAddresses []netip.Addr
|
|
||||||
destinationAddresses, err = router.LookupDefault(ctx, metadata.Destination.Fqdn)
|
|
||||||
if err != nil {
|
|
||||||
return N.HandshakeFailure(conn, err)
|
|
||||||
}
|
|
||||||
outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, destinationAddresses)
|
|
||||||
} else {
|
|
||||||
outConn, err = this.ListenPacket(ctx, metadata.Destination)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return N.HandshakeFailure(conn, err)
|
|
||||||
}
|
|
||||||
if destinationAddress.IsValid() {
|
|
||||||
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
|
|
||||||
natConn.UpdateDestination(destinationAddress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch metadata.Protocol {
|
|
||||||
case C.ProtocolSTUN:
|
|
||||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout)
|
|
||||||
case C.ProtocolQUIC:
|
|
||||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.QUICTimeout)
|
|
||||||
case C.ProtocolDNS:
|
|
||||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout)
|
|
||||||
}
|
|
||||||
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn))
|
|
||||||
}
|
|
||||||
|
|
||||||
func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error {
|
func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error {
|
||||||
if cachedReader, isCached := conn.(N.CachedReader); isCached {
|
if cachedReader, isCached := conn.(N.CachedReader); isCached {
|
||||||
payload := cachedReader.ReadCached()
|
payload := cachedReader.ReadCached()
|
||||||
if payload != nil && !payload.IsEmpty() {
|
if payload != nil && !payload.IsEmpty() {
|
||||||
_, err := serverConn.Write(payload.Bytes())
|
_, err := serverConn.Write(payload.Bytes())
|
||||||
payload.Release()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -174,12 +115,10 @@ func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) erro
|
|||||||
err := conn.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout))
|
err := conn.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout))
|
||||||
if err != os.ErrInvalid {
|
if err != os.ErrInvalid {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
payload.Release()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = payload.ReadOnceFrom(conn)
|
_, err = payload.ReadOnceFrom(conn)
|
||||||
if err != nil && !E.IsTimeout(err) {
|
if err != nil && !E.IsTimeout(err) {
|
||||||
payload.Release()
|
|
||||||
return E.Cause(err, "read payload")
|
return E.Cause(err, "read payload")
|
||||||
}
|
}
|
||||||
err = conn.SetReadDeadline(time.Time{})
|
err = conn.SetReadDeadline(time.Time{})
|
||||||
@@ -189,10 +128,10 @@ func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = serverConn.Write(payload.Bytes())
|
_, err = serverConn.Write(payload.Bytes())
|
||||||
payload.Release()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return N.HandshakeFailure(conn, err)
|
return N.HandshakeFailure(conn, err)
|
||||||
}
|
}
|
||||||
|
payload.Release()
|
||||||
}
|
}
|
||||||
return bufio.CopyConn(ctx, conn, serverConn)
|
return bufio.CopyConn(ctx, conn, serverConn)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -25,12 +25,12 @@ type HTTP struct {
|
|||||||
client *sHTTP.Client
|
client *sHTTP.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) {
|
func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) {
|
||||||
outboundDialer, err := dialer.New(router, options.DialerOptions)
|
outboundDialer, err := dialer.New(router, options.DialerOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
detour, err := tls.NewDialerFromOptions(router, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS))
|
detour, err := tls.NewDialerFromOptions(ctx, router, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-10
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/sagernet/quic-go/congestion"
|
"github.com/sagernet/quic-go/congestion"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
"github.com/sagernet/sing-box/common/qtls"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -33,7 +34,7 @@ type Hysteria struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
tlsConfig *tls.STDConfig
|
tlsConfig tls.Config
|
||||||
quicConfig *quic.Config
|
quicConfig *quic.Config
|
||||||
authKey []byte
|
authKey []byte
|
||||||
xplusKey []byte
|
xplusKey []byte
|
||||||
@@ -52,17 +53,12 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if options.TLS == nil || !options.TLS.Enabled {
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
return nil, C.ErrTLSRequired
|
return nil, C.ErrTLSRequired
|
||||||
}
|
}
|
||||||
abstractTLSConfig, err := tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig, err := abstractTLSConfig.Config()
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
if err != nil {
|
tlsConfig.SetNextProtos([]string{hysteria.DefaultALPN})
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig.MinVersion = tls.VersionTLS13
|
|
||||||
if len(tlsConfig.NextProtos) == 0 {
|
|
||||||
tlsConfig.NextProtos = []string{hysteria.DefaultALPN}
|
|
||||||
}
|
}
|
||||||
quicConfig := &quic.Config{
|
quicConfig := &quic.Config{
|
||||||
InitialStreamReceiveWindow: options.ReceiveWindowConn,
|
InitialStreamReceiveWindow: options.ReceiveWindowConn,
|
||||||
@@ -182,7 +178,7 @@ func (h *Hysteria) offerNew(ctx context.Context) (quic.Connection, error) {
|
|||||||
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
|
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
|
||||||
}
|
}
|
||||||
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
|
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
|
||||||
quicConn, err := quic.Dial(h.ctx, packetConn, udpConn.RemoteAddr(), h.tlsConfig, h.quicConfig)
|
quicConn, err := qtls.Dial(h.ctx, packetConn, udpConn.RemoteAddr(), h.tlsConfig, h.quicConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
packetConn.Close()
|
packetConn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
//go:build with_quic
|
||||||
|
|
||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/transport/hysteria2"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ adapter.Outbound = (*TUIC)(nil)
|
||||||
|
_ adapter.InterfaceUpdateListener = (*TUIC)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Hysteria2 struct {
|
||||||
|
myOutboundAdapter
|
||||||
|
client *hysteria2.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (*Hysteria2, error) {
|
||||||
|
options.UDPFragmentDefault = true
|
||||||
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
|
return nil, C.ErrTLSRequired
|
||||||
|
}
|
||||||
|
tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var salamanderPassword string
|
||||||
|
if options.Obfs != nil {
|
||||||
|
if options.Obfs.Password == "" {
|
||||||
|
return nil, E.New("missing obfs password")
|
||||||
|
}
|
||||||
|
switch options.Obfs.Type {
|
||||||
|
case hysteria2.ObfsTypeSalamander:
|
||||||
|
salamanderPassword = options.Obfs.Password
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown obfs type: ", options.Obfs.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outboundDialer, err := dialer.New(router, options.DialerOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
networkList := options.Network.Build()
|
||||||
|
client, err := hysteria2.NewClient(hysteria2.ClientOptions{
|
||||||
|
Context: ctx,
|
||||||
|
Dialer: outboundDialer,
|
||||||
|
ServerAddress: options.ServerOptions.Build(),
|
||||||
|
SendBPS: uint64(options.UpMbps * 1024 * 1024),
|
||||||
|
ReceiveBPS: uint64(options.DownMbps * 1024 * 1024),
|
||||||
|
SalamanderPassword: salamanderPassword,
|
||||||
|
Password: options.Password,
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
|
UDPDisabled: !common.Contains(networkList, N.NetworkUDP),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Hysteria2{
|
||||||
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
|
protocol: C.TypeHysteria2,
|
||||||
|
network: networkList,
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
tag: tag,
|
||||||
|
dependencies: withDialerDependency(options.DialerOptions),
|
||||||
|
},
|
||||||
|
client: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria2) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
switch N.NetworkName(network) {
|
||||||
|
case N.NetworkTCP:
|
||||||
|
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||||
|
return h.client.DialConn(ctx, destination)
|
||||||
|
case N.NetworkUDP:
|
||||||
|
conn, err := h.ListenPacket(ctx, destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bufio.NewBindPacketConn(conn, destination), nil
|
||||||
|
default:
|
||||||
|
return nil, E.New("unsupported network: ", network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria2) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
|
return h.client.ListenPacket(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria2) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
return NewConnection(ctx, h, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria2) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
return NewPacketConnection(ctx, h, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria2) InterfaceUpdated() error {
|
||||||
|
return h.client.CloseWithError(E.New("network changed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria2) Close() error {
|
||||||
|
return h.client.CloseWithError(os.ErrClosed)
|
||||||
|
}
|
||||||
@@ -14,3 +14,7 @@ import (
|
|||||||
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) {
|
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) {
|
||||||
return nil, C.ErrQUICNotIncluded
|
return nil, C.ErrQUICNotIncluded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) {
|
||||||
|
return nil, C.ErrQUICNotIncluded
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
|||||||
serverAddr: options.ServerOptions.Build(),
|
serverAddr: options.ServerOptions.Build(),
|
||||||
}
|
}
|
||||||
if options.Plugin != "" {
|
if options.Plugin != "" {
|
||||||
outbound.plugin, err = sip003.CreatePlugin(options.Plugin, options.PluginOptions, router, outbound.dialer, outbound.serverAddr)
|
outbound.plugin, err = sip003.CreatePlugin(ctx, options.Plugin, options.PluginOptions, router, outbound.dialer, outbound.serverAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
options.TLS.MinVersion = "1.2"
|
options.TLS.MinVersion = "1.2"
|
||||||
options.TLS.MaxVersion = "1.2"
|
options.TLS.MaxVersion = "1.2"
|
||||||
}
|
}
|
||||||
tlsConfig, err := tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-23
@@ -80,11 +80,11 @@ func (h *Socks) DialContext(ctx context.Context, network string, destination M.S
|
|||||||
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||||
}
|
}
|
||||||
if h.resolve && destination.IsFqdn() {
|
if h.resolve && destination.IsFqdn() {
|
||||||
destinationAddresses, err := h.router.LookupDefault(ctx, destination.Fqdn)
|
addrs, err := h.router.LookupDefault(ctx, destination.Fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return N.DialSerial(ctx, h.client, network, destination, destinationAddresses)
|
return N.DialSerial(ctx, h.client, network, destination, addrs)
|
||||||
}
|
}
|
||||||
return h.client.DialContext(ctx, network, destination)
|
return h.client.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
@@ -97,33 +97,14 @@ func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.
|
|||||||
h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination)
|
||||||
return h.uotClient.ListenPacket(ctx, destination)
|
return h.uotClient.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
if h.resolve && destination.IsFqdn() {
|
|
||||||
destinationAddresses, err := h.router.LookupDefault(ctx, destination.Fqdn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
packetConn, _, err := N.ListenSerial(ctx, h.client, destination, destinationAddresses)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return packetConn, nil
|
|
||||||
}
|
|
||||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
return h.client.ListenPacket(ctx, destination)
|
return h.client.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
if h.resolve {
|
return NewConnection(ctx, h, conn, metadata)
|
||||||
return NewDirectConnection(ctx, h.router, h, conn, metadata)
|
|
||||||
} else {
|
|
||||||
return NewConnection(ctx, h, conn, metadata)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
if h.resolve {
|
return NewPacketConnection(ctx, h, conn, metadata)
|
||||||
return NewDirectPacketConnection(ctx, h.router, h, conn, metadata)
|
|
||||||
} else {
|
|
||||||
return NewPacketConnection(ctx, h, conn, metadata)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -51,7 +51,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
|
|||||||
key: trojan.Key(options.Password),
|
key: trojan.Key(options.Password),
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
|
outbound.tlsConfig, err = tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-5
@@ -41,11 +41,7 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
|
|||||||
if options.TLS == nil || !options.TLS.Enabled {
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
return nil, C.ErrTLSRequired
|
return nil, C.ErrTLSRequired
|
||||||
}
|
}
|
||||||
abstractTLSConfig, err := tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig, err := abstractTLSConfig.Config()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
serverAddr: options.ServerOptions.Build(),
|
serverAddr: options.ServerOptions.Build(),
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
|
outbound.tlsConfig, err = tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -18,6 +18,7 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Outbound = (*VMess)(nil)
|
var _ adapter.Outbound = (*VMess)(nil)
|
||||||
@@ -52,7 +53,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
serverAddr: options.ServerOptions.Build(),
|
serverAddr: options.ServerOptions.Build(),
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
|
outbound.tlsConfig, err = tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -77,7 +78,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
|
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
|
||||||
}
|
}
|
||||||
var clientOptions []vmess.ClientOption
|
var clientOptions []vmess.ClientOption
|
||||||
if timeFunc := router.TimeFunc(); timeFunc != nil {
|
if timeFunc := ntp.TimeFuncFromContext(ctx); timeFunc != nil {
|
||||||
clientOptions = append(clientOptions, vmess.ClientWithTimeFunc(timeFunc))
|
clientOptions = append(clientOptions, vmess.ClientWithTimeFunc(timeFunc))
|
||||||
}
|
}
|
||||||
if options.GlobalPadding {
|
if options.GlobalPadding {
|
||||||
|
|||||||
+4
-15
@@ -202,37 +202,26 @@ func (w *WireGuard) DialContext(ctx context.Context, network string, destination
|
|||||||
w.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
w.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
}
|
}
|
||||||
if destination.IsFqdn() {
|
if destination.IsFqdn() {
|
||||||
destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn)
|
addrs, err := w.router.LookupDefault(ctx, destination.Fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return N.DialSerial(ctx, w.tunDevice, network, destination, destinationAddresses)
|
return N.DialSerial(ctx, w.tunDevice, network, destination, addrs)
|
||||||
}
|
}
|
||||||
return w.tunDevice.DialContext(ctx, network, destination)
|
return w.tunDevice.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
w.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
w.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
if destination.IsFqdn() {
|
|
||||||
destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
packetConn, _, err := N.ListenSerial(ctx, w.tunDevice, destination, destinationAddresses)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return packetConn, err
|
|
||||||
}
|
|
||||||
return w.tunDevice.ListenPacket(ctx, destination)
|
return w.tunDevice.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
return NewDirectConnection(ctx, w.router, w, conn, metadata)
|
return NewConnection(ctx, w, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
return NewDirectPacketConnection(ctx, w.router, w, conn, metadata)
|
return NewPacketConnection(ctx, w, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireGuard) Start() error {
|
func (w *WireGuard) Start() error {
|
||||||
|
|||||||
+6
-23
@@ -12,8 +12,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
"github.com/sagernet/sing-box/common/geosite"
|
"github.com/sagernet/sing-box/common/geosite"
|
||||||
"github.com/sagernet/sing-box/common/mux"
|
"github.com/sagernet/sing-box/common/mux"
|
||||||
@@ -81,7 +81,7 @@ type Router struct {
|
|||||||
interfaceMonitor tun.DefaultInterfaceMonitor
|
interfaceMonitor tun.DefaultInterfaceMonitor
|
||||||
packageManager tun.PackageManager
|
packageManager tun.PackageManager
|
||||||
processSearcher process.Searcher
|
processSearcher process.Searcher
|
||||||
timeService adapter.TimeService
|
timeService *ntp.Service
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
clashServer adapter.ClashServer
|
clashServer adapter.ClashServer
|
||||||
v2rayServer adapter.V2RayServer
|
v2rayServer adapter.V2RayServer
|
||||||
@@ -521,7 +521,7 @@ func (r *Router) Close() error {
|
|||||||
return E.Cause(err, "close dns transport[", i, "]")
|
return E.Cause(err, "close dns transport[", i, "]")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if r.geoIPReader != nil {
|
if r.geositeReader != nil {
|
||||||
r.logger.Trace("closing geoip reader")
|
r.logger.Trace("closing geoip reader")
|
||||||
err = E.Append(err, common.Close(r.geoIPReader), func(err error) error {
|
err = E.Append(err, common.Close(r.geoIPReader), func(err error) error {
|
||||||
return E.Cause(err, "close geoip reader")
|
return E.Cause(err, "close geoip reader")
|
||||||
@@ -602,7 +602,6 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
conntrack.KillerCheck()
|
|
||||||
metadata.Network = N.NetworkTCP
|
metadata.Network = N.NetworkTCP
|
||||||
switch metadata.Destination.Fqdn {
|
switch metadata.Destination.Fqdn {
|
||||||
case mux.Destination.Fqdn:
|
case mux.Destination.Fqdn:
|
||||||
@@ -739,7 +738,6 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
conntrack.KillerCheck()
|
|
||||||
metadata.Network = N.NetworkUDP
|
metadata.Network = N.NetworkUDP
|
||||||
|
|
||||||
if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) {
|
if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) {
|
||||||
@@ -917,21 +915,13 @@ func (r *Router) AutoDetectInterfaceFunc() control.Func {
|
|||||||
if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() {
|
if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() {
|
||||||
return r.platformInterface.AutoDetectInterfaceControl()
|
return r.platformInterface.AutoDetectInterfaceControl()
|
||||||
} else {
|
} else {
|
||||||
return control.BindToInterfaceFunc(r.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int, err error) {
|
return control.BindToInterfaceFunc(r.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) {
|
||||||
remoteAddr := M.ParseSocksaddr(address).Addr
|
remoteAddr := M.ParseSocksaddr(address).Addr
|
||||||
if C.IsLinux {
|
if C.IsLinux {
|
||||||
interfaceName = r.InterfaceMonitor().DefaultInterfaceName(remoteAddr)
|
return r.InterfaceMonitor().DefaultInterfaceName(remoteAddr), -1
|
||||||
interfaceIndex = -1
|
|
||||||
if interfaceName == "" {
|
|
||||||
err = tun.ErrNoRoute
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
interfaceIndex = r.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
|
return "", r.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
|
||||||
if interfaceIndex == -1 {
|
|
||||||
err = tun.ErrNoRoute
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -960,13 +950,6 @@ func (r *Router) PackageManager() tun.PackageManager {
|
|||||||
return r.packageManager
|
return r.packageManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) TimeFunc() func() time.Time {
|
|
||||||
if r.timeService == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return r.timeService.TimeFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) ClashServer() adapter.ClashServer {
|
func (r *Router) ClashServer() adapter.ClashServer {
|
||||||
return r.clashServer
|
return r.clashServer
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -133,11 +133,11 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS
|
|||||||
addrs, err := r.dnsClient.Lookup(ctx, transport, domain, strategy)
|
addrs, err := r.dnsClient.Lookup(ctx, transport, domain, strategy)
|
||||||
if len(addrs) > 0 {
|
if len(addrs) > 0 {
|
||||||
r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(addrs), " "))
|
r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(addrs), " "))
|
||||||
} else if err != nil {
|
|
||||||
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
|
||||||
} else {
|
} else {
|
||||||
r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
|
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
||||||
err = dns.RCodeNameError
|
if err == nil {
|
||||||
|
err = dns.RCodeNameError
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return addrs, err
|
return addrs, err
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user