Compare commits

..

12 Commits

Author SHA1 Message Date
世界
eb6629dc5c documentation: Update changelog 2023-09-02 11:48:23 +08:00
世界
87237191f3 documentation: Add hysteria2 2023-09-02 11:17:40 +08:00
世界
fc2cf83ded Add hysteria2 protocol 2023-09-02 11:17:40 +08:00
世界
e61c67cbc2 Fix ECH server config 2023-09-01 17:27:16 +08:00
世界
406e089d13 documentation: Bump version 2023-09-01 11:26:29 +08:00
世界
256adf4a94 Add ECH support for QUIC based protocols 2023-09-01 11:00:57 +08:00
Hiddify
a6cf3697c3 Add KDE set system proxy support
Co-authored-by: Hiddify <114227601+hiddify1@users.noreply.github.com>
2023-09-01 11:00:57 +08:00
世界
9a42943070 documentation: Update TLS ECH struct 2023-09-01 11:00:57 +08:00
世界
e812102bda Enable with_ech by default 2023-09-01 11:00:57 +08:00
世界
8ab8734614 Add ECH keypair generator 2023-09-01 11:00:57 +08:00
世界
5b92b7183f Remove legacy NTP usages 2023-09-01 10:40:28 +08:00
世界
9aff92b89a Improve ECH support 2023-09-01 10:40:28 +08:00
137 changed files with 4420 additions and 946 deletions

View File

@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get latest go version
@@ -48,7 +48,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go
@@ -68,7 +68,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go
@@ -199,7 +199,7 @@ jobs:
TAGS: with_clash_api,with_quic
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get latest go version

View File

@@ -9,20 +9,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@v3
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
- name: Setup QEMU for Docker Buildx
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v2
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker metadata
id: metadata
uses: docker/metadata-action@v5
uses: docker/metadata-action@v4
with:
images: ghcr.io/sagernet/sing-box
- name: Get tag to build
@@ -35,7 +35,7 @@ jobs:
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
fi
- name: Build and release Docker images
uses: docker/build-push-action@v5
uses: docker/build-push-action@v4
with:
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
target: dist

View File

@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get latest go version

View File

@@ -9,7 +9,7 @@ RUN set -ex \
&& apk add git build-base \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& 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 \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
./cmd/sing-box

View File

@@ -1,7 +1,7 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
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_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
build_android:
cd ../sing-box-for-android && ./gradlew :app:assembleRelease && ./gradlew --stop
cd ../sing-box-for-android && ./gradlew :app:assembleRelease
upload_android:
mkdir -p dist/release_android
@@ -84,7 +84,7 @@ upload_android:
release_android: lib_android update_android_version build_android upload_android
publish_android:
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadRelease && ./gradlew --stop
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadRelease
build_ios:
cd ../sing-box-for-apple && \
@@ -137,6 +137,7 @@ release_macos_independent: build_macos_independent notarize_macos_independent wa
build_tvos:
cd ../sing-box-for-apple && \
rm -rf build/SFT.xcarchive && \
export DEVELOPER_DIR=/Applications/Xcode-beta.app/Contents/Developer && \
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive
upload_tvos_app_store:
@@ -178,8 +179,8 @@ lib:
lib_install:
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/gobind@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-20230728014906-3de089147f59
clean:
rm -rf bin dist sing-box

View File

@@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
mdns "github.com/miekg/dns"
)
@@ -45,8 +46,6 @@ type Router interface {
PackageManager() tun.PackageManager
Rules() []Rule
TimeService
ClashServer() ClashServer
SetClashServer(server ClashServer)
@@ -56,18 +55,12 @@ type Router interface {
ResetNetwork() error
}
type routerContextKey struct{}
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 {
metadata := ctx.Value((*routerContextKey)(nil))
if metadata == nil {
return nil
}
return metadata.(Router)
return service.FromContext[Router](ctx)
}
type Rule interface {

2
box.go
View File

@@ -19,6 +19,7 @@ import (
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"
)
@@ -47,6 +48,7 @@ func New(options Options) (*Box, error) {
if ctx == nil {
ctx = context.Background()
}
ctx = service.ContextWithDefaultRegistry(ctx)
ctx = pause.ContextWithDefaultManager(ctx)
createdAt := time.Now()
experimentalOptions := common.PtrValueOrDefault(options.Experimental)

View File

@@ -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
}

View File

@@ -143,16 +143,14 @@ func create() (*box.Box, context.CancelFunc, error) {
signal.Stop(osSignals)
close(osSignals)
}()
startCtx, finishStart := context.WithCancel(context.Background())
go func() {
_, loaded := <-osSignals
if loaded {
cancel()
closeMonitor(startCtx)
}
}()
err = instance.Start()
finishStart()
if err != nil {
cancel()
return nil, nil, E.Cause(err, "start service")

View File

@@ -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
}

View File

@@ -17,7 +17,7 @@ func NewConn(conn net.Conn) (net.Conn, error) {
element := openConnection.PushBack(conn)
connAccess.Unlock()
if KillerEnabled {
err := KillerCheck()
err := killerCheck()
if err != nil {
conn.Close()
return nil, err

View File

@@ -1,20 +1,20 @@
package conntrack
import (
"runtime"
runtimeDebug "runtime/debug"
"time"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/memory"
)
var (
KillerEnabled bool
MemoryLimit uint64
MemoryLimit int64
killerLastCheck time.Time
)
func KillerCheck() error {
func killerCheck() error {
if !KillerEnabled {
return nil
}
@@ -23,7 +23,10 @@ func KillerCheck() error {
return nil
}
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()
go func() {
time.Sleep(time.Second)

View File

@@ -18,7 +18,7 @@ func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
element := openConnection.PushBack(conn)
connAccess.Unlock()
if KillerEnabled {
err := KillerCheck()
err := killerCheck()
if err != nil {
conn.Close()
return nil, err

View File

@@ -6,7 +6,7 @@ import (
"time"
"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"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/control"

View File

@@ -7,7 +7,6 @@ import (
"io"
"net"
"os"
"sync"
"time"
"github.com/sagernet/sing/common"
@@ -25,7 +24,6 @@ type slowOpenConn struct {
destination M.Socksaddr
conn net.Conn
create chan struct{}
access sync.Mutex
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) {
if c.conn != nil {
return c.conn.Write(b)
}
c.access.Lock()
defer c.access.Unlock()
select {
case <-c.create:
if c.err != nil {
return 0, c.err
if c.conn == nil {
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
if err != nil {
c.conn = nil
c.err = E.Cause(err, "dial tcp fast open")
}
return c.conn.Write(b)
default:
close(c.create)
return
}
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
if err != nil {
c.conn = nil
c.err = E.Cause(err, "dial tcp fast open")
}
close(c.create)
return
return c.conn.Write(b)
}
func (c *slowOpenConn) Close() error {

View File

@@ -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)
}

120
common/qtls/wrapper.go Normal file
View File

@@ -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
}

View File

@@ -16,10 +16,12 @@ import (
var (
hasGSettings bool
isKDE5 bool
sudoUser string
)
func init() {
isKDE5 = common.Error(exec.LookPath("kwriteconfig5")) == nil
hasGSettings = common.Error(exec.LookPath("gsettings")) == nil
if os.Getuid() == 0 {
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) {
if !hasGSettings {
return nil, E.New("unsupported desktop environment")
if hasGSettings {
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 err != nil {
return nil, err
if isKDE5 {
err := runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "ProxyType", "1")
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 {
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
return nil, E.New("unsupported desktop environment")
}
func setGnomeProxy(port uint16, proxyTypes ...string) error {
@@ -78,3 +109,27 @@ func setGnomeProxy(port uint16, proxyTypes ...string) error {
}
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
}

View File

@@ -13,29 +13,29 @@ import (
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 {
return dialer, nil
}
config, err := NewClient(router, serverAddress, options)
config, err := NewClient(ctx, serverAddress, options)
if err != nil {
return nil, err
}
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 {
return nil, nil
}
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 {
return NewRealityClient(router, serverAddress, options)
return NewRealityClient(ctx, serverAddress, options)
} else if options.UTLS != nil && options.UTLS.Enabled {
return NewUTLSClient(router, serverAddress, options)
return NewUTLSClient(ctx, serverAddress, options)
} else {
return NewSTDClient(router, serverAddress, options)
return NewSTDClient(ctx, serverAddress, options)
}
}

View File

@@ -7,50 +7,53 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"net"
"net/netip"
"os"
"strings"
cftls "github.com/sagernet/cloudflare-tls"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
mDNS "github.com/miekg/dns"
)
type ECHClientConfig struct {
type echClientConfig struct {
config *cftls.Config
}
func (e *ECHClientConfig) ServerName() string {
return e.config.ServerName
func (c *echClientConfig) ServerName() string {
return c.config.ServerName
}
func (e *ECHClientConfig) SetServerName(serverName string) {
e.config.ServerName = serverName
func (c *echClientConfig) SetServerName(serverName string) {
c.config.ServerName = serverName
}
func (e *ECHClientConfig) NextProtos() []string {
return e.config.NextProtos
func (c *echClientConfig) NextProtos() []string {
return c.config.NextProtos
}
func (e *ECHClientConfig) SetNextProtos(nextProto []string) {
e.config.NextProtos = nextProto
func (c *echClientConfig) SetNextProtos(nextProto []string) {
c.config.NextProtos = nextProto
}
func (e *ECHClientConfig) Config() (*STDConfig, error) {
func (c *echClientConfig) Config() (*STDConfig, error) {
return nil, E.New("unsupported usage for ECH")
}
func (e *ECHClientConfig) Client(conn net.Conn) (Conn, error) {
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
func (c *echClientConfig) Client(conn net.Conn) (Conn, error) {
return &echConnWrapper{cftls.Client(conn, c.config)}, nil
}
func (e *ECHClientConfig) Clone() Config {
return &ECHClientConfig{
config: e.config.Clone(),
func (c *echClientConfig) Clone() Config {
return &echClientConfig{
config: c.config.Clone(),
}
}
@@ -80,7 +83,7 @@ func (c *echConnWrapper) Upstream() any {
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
if options.ServerName != "" {
serverName = options.ServerName
@@ -94,7 +97,7 @@ func NewECHClient(router adapter.Router, serverAddress string, options option.Ou
}
var tlsConfig cftls.Config
tlsConfig.Time = router.TimeFunc()
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {
@@ -168,24 +171,36 @@ func NewECHClient(router adapter.Router, serverAddress string, options option.Ou
tlsConfig.ECHEnabled = true
tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled
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 {
return nil, err
return nil, E.Cause(err, "read key")
}
clientConfig, err := cftls.UnmarshalECHConfigs(clientConfigContent)
if err != nil {
return nil, err
}
tlsConfig.ClientECHConfigs = clientConfig
} else {
tlsConfig.GetClientECHConfigs = fetchECHClientConfig(router)
echConfig = content
}
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) {
return func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverName string) ([]cftls.ECHConfig, error) {
return func(_ context.Context, serverName string) ([]cftls.ECHConfig, error) {
message := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
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 {
return nil, err
}

169
common/tls/ech_keygen.go Normal file
View File

@@ -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
}

56
common/tls/ech_quic.go Normal file
View File

@@ -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)
}

343
common/tls/ech_server.go Normal file
View File

@@ -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
}

View File

@@ -3,11 +3,23 @@
package tls
import (
"github.com/sagernet/sing-box/adapter"
"context"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
return nil, E.New(`ECH is not included in this build, rebuild with -tags with_ech`)
var errECHNotIncluded = 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
}

View File

@@ -26,7 +26,6 @@ import (
"time"
"unsafe"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
@@ -45,12 +44,12 @@ type RealityClientConfig struct {
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 {
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 {
return nil, err
}

View File

@@ -19,6 +19,7 @@ import (
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
)
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
@@ -27,13 +28,13 @@ type RealityServerConfig struct {
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
if options.ACME != nil && len(options.ACME.Domain) > 0 {
return nil, E.New("acme is unavailable in reality")
}
tlsConfig.Time = router.TimeFunc()
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
if 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)
}
}
if options.Certificate != "" || options.CertificatePath != "" {
if len(options.Certificate) > 0 || options.CertificatePath != "" {
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")
}
@@ -101,7 +102,7 @@ func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Log
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 {
return nil, err
}

View File

@@ -5,12 +5,11 @@ package tls
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
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`)
}

View File

@@ -4,21 +4,22 @@ import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
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 {
return nil, nil
}
if options.Reality != nil && options.Reality.Enabled {
return NewRealityServer(ctx, router, logger, options)
if options.ECH != nil && options.ECH.Enabled {
return NewECHServer(ctx, logger, options)
} else if options.Reality != nil && options.Reality.Enabled {
return NewRealityServer(ctx, logger, options)
} else {
return NewSTDServer(ctx, router, logger, options)
return NewSTDServer(ctx, logger, options)
}
}

View File

@@ -1,15 +1,16 @@
package tls
import (
"context"
"crypto/tls"
"crypto/x509"
"net"
"net/netip"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
)
type STDClientConfig struct {
@@ -44,7 +45,7 @@ func (s *STDClientConfig) Clone() Config {
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
if options.ServerName != "" {
serverName = options.ServerName
@@ -58,7 +59,7 @@ func NewSTDClient(router adapter.Router, serverAddress string, options option.Ou
}
var tlsConfig tls.Config
tlsConfig.Time = router.TimeFunc()
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {

View File

@@ -5,12 +5,14 @@ import (
"crypto/tls"
"net"
"os"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
"github.com/fsnotify/fsnotify"
)
@@ -156,7 +158,7 @@ func (c *STDServerConfig) Close() error {
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 {
return nil, nil
}
@@ -175,7 +177,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
} else {
tlsConfig = &tls.Config{}
}
tlsConfig.Time = router.TimeFunc()
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
if 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 key []byte
if acmeService == nil {
if options.Certificate != "" {
certificate = []byte(options.Certificate)
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 {
@@ -220,8 +222,8 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
}
certificate = content
}
if options.Key != "" {
key = []byte(options.Key)
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 {
@@ -231,7 +233,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
}
if certificate == nil && key == nil && options.Insecure {
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return GenerateKeyPair(router.TimeFunc(), info.ServerName)
return GenerateKeyPair(ntp.TimeFuncFromContext(ctx), info.ServerName)
}
} else {
if certificate == nil {

View File

@@ -11,9 +11,9 @@ import (
"net/netip"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
utls "github.com/sagernet/utls"
"golang.org/x/net/http2"
@@ -113,7 +113,7 @@ func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
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
if options.ServerName != "" {
serverName = options.ServerName
@@ -127,7 +127,7 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
}
var tlsConfig utls.Config
tlsConfig.Time = router.TimeFunc()
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {

View File

@@ -3,15 +3,16 @@
package tls
import (
"github.com/sagernet/sing-box/adapter"
"context"
"github.com/sagernet/sing-box/option"
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`)
}
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`)
}

View File

@@ -22,6 +22,7 @@ const (
TypeShadowsocksR = "shadowsocksr"
TypeVLESS = "vless"
TypeTUIC = "tuic"
TypeHysteria2 = "hysteria2"
)
const (
@@ -65,6 +66,8 @@ func ProxyDisplayName(proxyType string) string {
return "VLESS"
case TypeTUIC:
return "TUIC"
case TypeHysteria2:
return "Hysteria2"
case TypeSelector:
return "Selector"
case TypeURLTest:

View File

@@ -5,7 +5,7 @@ package box
import (
"runtime/debug"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/common/dialer/conntrack"
"github.com/sagernet/sing-box/option"
)
@@ -28,7 +28,7 @@ func applyDebugOptions(options option.DebugOptions) {
}
if options.MemoryLimit != 0 {
// debug.SetMemoryLimit(int64(options.MemoryLimit))
conntrack.MemoryLimit = uint64(options.MemoryLimit)
conntrack.MemoryLimit = int64(options.MemoryLimit)
}
if options.OOMKiller != nil {
conntrack.KillerEnabled = *options.OOMKiller

View File

@@ -5,7 +5,7 @@ package box
import (
"runtime/debug"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/common/dialer/conntrack"
"github.com/sagernet/sing-box/option"
)
@@ -27,8 +27,8 @@ func applyDebugOptions(options option.DebugOptions) {
debug.SetTraceback(options.TraceBack)
}
if options.MemoryLimit != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
conntrack.MemoryLimit = uint64(options.MemoryLimit)
debug.SetMemoryLimit(int64(options.MemoryLimit))
conntrack.MemoryLimit = int64(options.MemoryLimit)
}
if options.OOMKiller != nil {
conntrack.KillerEnabled = *options.OOMKiller

View File

@@ -7,12 +7,12 @@ import (
"runtime/debug"
"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/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/dustin/go-humanize"
"github.com/go-chi/chi/v5"
)
@@ -37,9 +37,9 @@ func applyDebugListenOption(options option.DebugOptions) {
runtime.ReadMemStats(&memStats)
var memObject badjson.JSONObject
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("goroutines", runtime.NumGoroutine())
memObject.Put("rss", rusageMaxRSS())

View File

@@ -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
* Fixes and improvements

View File

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

View File

@@ -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)。

View File

@@ -27,6 +27,8 @@
| `naive` | [Naive](./naive) | 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 |
| `redirect` | [Redirect](./redirect) | X |

View File

@@ -26,6 +26,10 @@
| `trojan` | [Trojan](./trojan) | TCP |
| `naive` | [Naive](./naive) | 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 |
| `redirect` | [Redirect](./redirect) | X |
| `tproxy` | [TProxy](./tproxy) | X |

View File

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

View File

@@ -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/)。

View File

@@ -29,6 +29,8 @@
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
| `vless` | [VLESS](./vless) |
| `shadowtls` | [ShadowTLS](./shadowtls) |
| `tuic` | [TUIC](./tuic) |
| `hysteria2` | [Hysteria2](./hysteria2) |
| `tor` | [Tor](./tor) |
| `ssh` | [SSH](./ssh) |
| `dns` | [DNS](./dns) |

View File

@@ -28,6 +28,9 @@
| `hysteria` | [Hysteria](./hysteria) |
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
| `vless` | [VLESS](./vless) |
| `shadowtls` | [ShadowTLS](./shadowtls) |
| `tuic` | [TUIC](./tuic) |
| `hysteria2` | [Hysteria2](./hysteria2) |
| `tor` | [Tor](./tor) |
| `ssh` | [SSH](./ssh) |
| `dns` | [DNS](./dns) |

View File

@@ -8,9 +8,9 @@
"min_version": "",
"max_version": "",
"cipher_suites": [],
"certificate": "",
"certificate": [],
"certificate_path": "",
"key": "",
"key": [],
"key_path": "",
"acme": {
"domain": [],
@@ -27,6 +27,13 @@
"mac_key": ""
}
},
"ech": {
"enabled": false,
"pq_signature_schemes_enabled": false,
"dynamic_record_sizing_disabled": false,
"key": [],
"key_path": ""
},
"reality": {
"enabled": false,
"handshake": {
@@ -62,7 +69,8 @@
"enabled": false,
"pq_signature_schemes_enabled": false,
"dynamic_record_sizing_disabled": false,
"config": ""
"config": [],
"config_path": ""
},
"utls": {
"enabled": false,
@@ -162,7 +170,7 @@ This may change in the future.
#### certificate
The server certificate, in PEM format.
The server certificate line array, in PEM format.
#### certificate_path
@@ -172,7 +180,7 @@ The path to the server certificate, in PEM format.
==Server only==
The server private key, in PEM format.
The server private key line array, in PEM format.
#### key_path
@@ -180,18 +188,11 @@ 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 ""
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`.
Only ECH is supported in QUIC.
#### utls
@@ -222,6 +223,58 @@ Available fingerprint values:
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
!!! warning ""
@@ -345,4 +398,4 @@ Check disabled if empty.
### 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.

View File

@@ -8,9 +8,9 @@
"min_version": "",
"max_version": "",
"cipher_suites": [],
"certificate": "",
"certificate": [],
"certificate_path": "",
"key": "",
"key": [],
"key_path": "",
"acme": {
"domain": [],
@@ -27,6 +27,13 @@
"mac_key": ""
}
},
"ech": {
"enabled": false,
"pq_signature_schemes_enabled": false,
"dynamic_record_sizing_disabled": false,
"key": [],
"key_path": ""
},
"reality": {
"enabled": false,
"handshake": {
@@ -56,13 +63,14 @@
"min_version": "",
"max_version": "",
"cipher_suites": [],
"certificate": "",
"certificate": [],
"certificate_path": "",
"ech": {
"enabled": false,
"pq_signature_schemes_enabled": false,
"dynamic_record_sizing_disabled": false,
"config": ""
"config": [],
"config_path": ""
},
"utls": {
"enabled": false,
@@ -162,7 +170,7 @@ TLS 版本值:
#### certificate
服务器 PEM 证书。
服务器 PEM 证书行数组
#### certificate_path
@@ -172,7 +180,7 @@ TLS 版本值:
==仅服务器==
服务器 PEM 私钥。
服务器 PEM 私钥行数组
#### key_path
@@ -180,19 +188,6 @@ TLS 版本值:
服务器 PEM 私钥路径。
#### ech
==仅客户端==
!!! warning ""
默认安装不包含 ECH, 参阅 [安装](/zh/#_2)。
ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ClientHello 的第一部分
信息。
如果您不知道如何填写其他配置,只需设置 `enabled` 即可。
#### utls
==仅客户端==
@@ -222,6 +217,59 @@ uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻
默认使用 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 字段
!!! warning ""

View File

@@ -90,7 +90,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
defaultMode = options.DefaultMode
}
if !common.Contains(server.modeList, defaultMode) {
server.modeList = append([]string{defaultMode}, server.modeList...)
server.modeList = append(server.modeList, defaultMode)
}
server.mode = defaultMode
if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.ExternalController == "" {

View File

@@ -11,6 +11,4 @@ const (
CommandGroupExpand
CommandClashMode
CommandSetClashMode
CommandGetSystemProxyStatus
CommandSetSystemProxyEnabled
)

View File

@@ -5,7 +5,6 @@ import (
"net"
"os"
"path/filepath"
"time"
"github.com/sagernet/sing/common"
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 {
common.Close(c.conn)
conn, err := c.directConnectWithRetry()
conn, err := c.directConnect()
if err != nil {
return err
}

View File

@@ -6,7 +6,7 @@ import (
runtimeDebug "runtime/debug"
"time"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/common/dialer/conntrack"
)
func (c *CommandClient) CloseConnections() error {

View File

@@ -35,8 +35,6 @@ type CommandServer struct {
type CommandServerHandler interface {
ServiceReload() error
GetSystemProxyStatus() *SystemProxyStatus
SetSystemProxyEnabled(isEnabled bool) error
}
func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer {
@@ -161,10 +159,6 @@ func (s *CommandServer) handleConnection(conn net.Conn) error {
return s.handleModeConn(conn)
case CommandSetClashMode:
return s.handleSetClashMode(conn)
case CommandGetSystemProxyStatus:
return s.handleGetSystemProxyStatus(conn)
case CommandSetSystemProxyEnabled:
return s.handleSetSystemProxyEnabled(conn)
default:
return E.New("unknown command: ", command)
}

View File

@@ -6,15 +6,13 @@ import (
"runtime"
"time"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/common/dialer/conntrack"
"github.com/sagernet/sing-box/experimental/clashapi"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/memory"
)
type StatusMessage struct {
Memory int64
MemoryInuse int64
Goroutines int32
ConnectionsIn int32
ConnectionsOut int32
@@ -26,8 +24,10 @@ type StatusMessage struct {
}
func (s *CommandServer) readStatus() StatusMessage {
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
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.ConnectionsOut = int32(conntrack.Count())

View File

@@ -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)
}

View File

@@ -4,7 +4,6 @@ package libbox
import (
"os"
"runtime"
"golang.org/x/sys/unix"
)
@@ -19,14 +18,12 @@ func RedirectStderr(path string) error {
if err != nil {
return err
}
if runtime.GOOS != "android" {
if sUserID > 0 {
err = outputFile.Chown(sUserID, sGroupID)
if err != nil {
outputFile.Close()
os.Remove(outputFile.Name())
return err
}
if sUserID > 0 {
err = outputFile.Chown(sUserID, sGroupID)
if err != nil {
outputFile.Close()
os.Remove(outputFile.Name())
return err
}
}
err = unix.Dup2(int(outputFile.Fd()), int(os.Stderr.Fd()))

View File

@@ -4,15 +4,14 @@ import (
"math"
runtimeDebug "runtime/debug"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/common/dialer/conntrack"
)
func SetMemoryLimit(enabled bool) {
const memoryLimit = 45 * 1024 * 1024
const memoryLimitGo = memoryLimit / 1.5
const memoryLimit = 30 * 1024 * 1024
if enabled {
runtimeDebug.SetGCPercent(10)
runtimeDebug.SetMemoryLimit(memoryLimitGo)
runtimeDebug.SetMemoryLimit(memoryLimit)
conntrack.KillerEnabled = true
conntrack.MemoryLimit = memoryLimit
} else {

View File

@@ -163,29 +163,25 @@ func DecodeProfileContentRequest(data []byte) (*ProfileContentRequest, error) {
}
type ProfileContent struct {
Name string
Type int32
Config string
RemotePath string
AutoUpdate bool
AutoUpdateInterval int32
LastUpdated int64
Name string
Type int32
Config string
RemotePath string
AutoUpdate bool
LastUpdated int64
}
func (c *ProfileContent) Encode() []byte {
buffer := new(bytes.Buffer)
buffer.WriteByte(MessageTypeProfileContent)
buffer.WriteByte(1)
buffer.WriteByte(0)
writer := gzip.NewWriter(buffer)
rw.WriteVString(writer, c.Name)
binary.Write(writer, binary.BigEndian, c.Type)
rw.WriteVString(writer, c.Config)
if c.Type != ProfileTypeLocal {
rw.WriteVString(writer, c.RemotePath)
}
if c.Type == ProfileTypeRemote {
binary.Write(writer, binary.BigEndian, c.AutoUpdate)
binary.Write(writer, binary.BigEndian, c.AutoUpdateInterval)
binary.Write(writer, binary.BigEndian, c.LastUpdated)
}
writer.Flush()
@@ -206,8 +202,12 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) {
if err != nil {
return nil, err
}
reader, err = gzip.NewReader(reader)
if err != nil {
if version == 0 {
reader, err = gzip.NewReader(reader)
if err != nil {
return nil, E.Cause(err, "unsupported profile")
}
} else {
return nil, E.Cause(err, "unsupported profile")
}
var content ProfileContent
@@ -228,18 +228,10 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) {
if err != nil {
return nil, err
}
}
if content.Type == ProfileTypeRemote || (version == 0 && content.Type != ProfileTypeLocal) {
err = binary.Read(reader, binary.BigEndian, &content.AutoUpdate)
if err != nil {
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)
if err != nil {
return nil, err

View File

@@ -5,8 +5,9 @@ import (
"os/user"
"strconv"
"github.com/sagernet/sing-box/common/humanize"
C "github.com/sagernet/sing-box/constant"
"github.com/dustin/go-humanize"
)
var (
@@ -45,11 +46,7 @@ func Version() string {
}
func FormatBytes(length int64) string {
return humanize.Bytes(uint64(length))
}
func FormatMemoryBytes(length int64) string {
return humanize.MemoryBytes(uint64(length))
return humanize.IBytes(uint64(length))
}
func ProxyDisplayType(proxyType string) string {

23
go.mod
View File

@@ -6,7 +6,9 @@ require (
berty.tech/go-libtor v1.0.385
github.com/Dreamacro/clash v1.17.0
github.com/caddyserver/certmagic v0.19.2
github.com/cloudflare/circl v1.3.3
github.com/cretz/bine v0.2.0
github.com/dustin/go-humanize v1.0.1
github.com/fsnotify/fsnotify v1.6.0
github.com/go-chi/chi/v5 v5.0.10
github.com/go-chi/cors v1.2.1
@@ -20,18 +22,18 @@ require (
github.com/oschwald/maxminddb-golang v1.12.0
github.com/pires/go-proxyproto v0.7.0
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/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/sing v0.2.10-0.20230925102642-b0e07300a98c
github.com/sagernet/sing-dns v0.1.9-0.20230925101650-9cc09becd01e
github.com/sagernet/sing-mux v0.1.3-0.20230907005326-7befbadbf314
github.com/sagernet/sing-shadowsocks v0.2.5-0.20230907005610-126234728ca0
github.com/sagernet/sing-shadowsocks2 v0.1.4-0.20230907005906-5d2917b29248
github.com/sagernet/sing v0.2.10-0.20230830132630-30bf19f2833c
github.com/sagernet/sing-dns v0.1.9-0.20230824120133-4d5cbceb40c1
github.com/sagernet/sing-mux v0.1.3-0.20230830095209-2a10ebd53ba8
github.com/sagernet/sing-shadowsocks v0.2.4
github.com/sagernet/sing-shadowsocks2 v0.1.3
github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/sing-tun v0.1.12-0.20230925100705-ef831c3485cf
github.com/sagernet/sing-vmess v0.1.8-0.20230907010359-161fb0ac716b
github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641
github.com/sagernet/sing-vmess v0.1.7
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
@@ -45,7 +47,7 @@ require (
golang.org/x/crypto v0.12.0
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
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
google.golang.org/grpc v1.57.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/ajg/form v1.5.1 // 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/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect

42
go.sum
View File

@@ -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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
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/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/gomobile v0.0.0-20230915142329-c6740b6d2950 h1:hUz/2mJLgi7l2H36JGpDY+jou9FmI6kAm0ZkU+xPpgE=
github.com/sagernet/gomobile v0.0.0-20230915142329-c6740b6d2950/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU=
github.com/sagernet/gomobile v0.0.0-20230728014906-3de089147f59 h1:vN4divY6LYHcYmiTsCHNPmGZtEsEKJzh81LyvgAQfEQ=
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/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/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-20230911082307-390b7c274032/go.mod h1:O4Cj7TmMOvqD6S0XMqJRZfcYzA3m0H0ARbbaJFB0p7A=
github.com/sagernet/quic-go v0.0.0-20230831052420-45809eee2e86 h1:g4TEg9inAtA1FDTXpNrvmx72nN5mTOLQrJce6fVxF9g=
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/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.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.20230925102642-b0e07300a98c/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.20230925101650-9cc09becd01e/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.20230907005326-7befbadbf314/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.5-0.20230907005610-126234728ca0/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.4-0.20230907005906-5d2917b29248/go.mod h1:DOhJc/cLeqRv0wuePrQso+iUmDxOnWF4eT/oMcRzYFw=
github.com/sagernet/sing v0.2.10-0.20230830132630-30bf19f2833c h1:J2ptRncTNy+ZHfcFYSBfTmpvmgNlSEUZz6sDjh1np/Y=
github.com/sagernet/sing v0.2.10-0.20230830132630-30bf19f2833c/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
github.com/sagernet/sing-dns v0.1.9-0.20230824120133-4d5cbceb40c1 h1:5w+jXz8y/8UQAxO74TjftN5okYkpg5mGvVxXunlKdqI=
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.20230830095209-2a10ebd53ba8 h1:UyUkEUEGqfIGqzOJ7OuJry4slgcT/qb0etDJ+89LTAs=
github.com/sagernet/sing-mux v0.1.3-0.20230830095209-2a10ebd53ba8/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY=
github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY=
github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM=
github.com/sagernet/sing-shadowsocks2 v0.1.3 h1:WXoLvCFi5JTFBRYorf1YePGYIQyJ/zbsBM6Fwbl5kGA=
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/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.20230925100705-ef831c3485cf/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.8-0.20230907010359-161fb0ac716b/go.mod h1:1qkC1L1T2sxnS/NuO6HU72S8TkltV+EXoKGR29m/Yss=
github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641 h1:a8lktNrCWZJisB+nPraW+qB73ZofgPtGmlfqNYcO79g=
github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641/go.mod h1:+YImslQMLgMQcVgZZ9IK4ue1o/605VSU90amHUcp4hA=
github.com/sagernet/sing-vmess v0.1.7 h1:TM8FFLsXmlXH9XT8/oDgc6PC5BOzrg6OzyEe01is2r4=
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/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
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.1.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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
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.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@@ -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)
case C.TypeTUIC:
return NewTUIC(ctx, router, logger, options.Tag, options.TUICOptions)
case C.TypeHysteria2:
return NewHysteria2(ctx, router, logger, options.Tag, options.Hysteria2Options)
default:
return nil, E.New("unknown inbound type: ", options.Type)
}

View File

@@ -44,7 +44,7 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge
authenticator: auth.NewAuthenticator(options.Users),
}
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 {
return nil, err
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/congestion"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/qtls"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
@@ -35,7 +36,7 @@ type Hysteria struct {
xplusKey []byte
sendBPS uint64
recvBPS uint64
listener *quic.Listener
listener qtls.QUICListener
udpAccess sync.RWMutex
udpSessionId uint32
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 {
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 {
return nil, err
}
@@ -147,11 +148,7 @@ func (h *Hysteria) Start() error {
if err != nil {
return err
}
rawConfig, err := h.tlsConfig.Config()
if err != nil {
return err
}
listener, err := quic.Listen(packetConn, rawConfig, h.quicConfig)
listener, err := qtls.Listen(packetConn, h.tlsConfig, h.quicConfig)
if err != nil {
return err
}
@@ -333,7 +330,7 @@ func (h *Hysteria) Close() error {
h.udpAccess.Unlock()
return common.Close(
&h.myInboundAdapter,
common.PtrOrNil(h.listener),
h.listener,
h.tlsConfig,
)
}

144
inbound/hysteria2.go Normal file
View File

@@ -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),
)
}

View File

@@ -14,3 +14,7 @@ import (
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) {
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
}

View File

@@ -60,7 +60,7 @@ func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogg
return nil, E.New("missing users")
}
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 {
return nil, err
}

View File

@@ -3,28 +3,39 @@
package inbound
import (
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/http3"
"github.com/sagernet/sing-box/common/qtls"
E "github.com/sagernet/sing/common/exceptions"
)
func (n *Naive) configureHTTP3Listener() error {
tlsConfig, err := n.tlsConfig.Config()
err := qtls.ConfigureHTTP3(n.tlsConfig)
if err != nil {
return err
}
h3Server := &http3.Server{
Port: int(n.listenOptions.ListenPort),
TLSConfig: tlsConfig,
Handler: n,
}
udpConn, err := n.ListenUDP()
if err != nil {
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() {
sErr := h3Server.Serve(udpConn)
sErr := h3Server.ServeListener(quicListener)
udpConn.Close()
if sErr != nil && !E.IsClosedOrCanceled(sErr) {
n.logger.Error("http3 server serve error: ", sErr)

View File

@@ -16,6 +16,7 @@ import (
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
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) {
@@ -68,7 +69,7 @@ func newShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
case common.Contains(shadowaead.List, options.Method):
inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, udpTimeout, inbound.upstreamContextHandler())
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:
err = E.New("unsupported method: ", options.Method)
}

View File

@@ -18,6 +18,7 @@ import (
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
)
var (
@@ -61,7 +62,7 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.
options.Password,
udpTimeout,
adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound),
router.TimeFunc(),
ntp.TimeFuncFromContext(ctx),
)
} else if common.Contains(shadowaead.List, options.Method) {
service, err = shadowaead.NewMultiService[int](

View File

@@ -11,7 +11,6 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-shadowtls"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
N "github.com/sagernet/sing/common/network"
)
@@ -67,7 +66,7 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
},
HandshakeForServerName: handshakeForServerName,
StrictMode: options.StrictMode,
Handler: adapter.NewUpstreamContextHandler(inbound.newConnection, nil, inbound),
Handler: inbound.upstreamContextHandler(),
Logger: logger,
})
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 {
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)
}

View File

@@ -49,7 +49,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
users: options.Users,
}
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 {
return nil, err
}

View File

@@ -34,11 +34,7 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
rawConfig, err := tlsConfig.Config()
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
@@ -67,7 +63,7 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
server, err := tuic.NewServer(tuic.ServerOptions{
Context: ctx,
Logger: logger,
TLSConfig: rawConfig,
TLSConfig: tlsConfig,
Users: users,
CongestionControl: options.CongestionControl,
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 {
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)
@@ -114,6 +111,7 @@ func (h *TUIC) Start() error {
func (h *TUIC) Close() error {
return common.Close(
&h.myInboundAdapter,
h.tlsConfig,
common.PtrOrNil(h.server),
)
}

View File

@@ -61,7 +61,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
inbound.service = service
var err error
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 {
return nil, err
}

View File

@@ -19,6 +19,7 @@ import (
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
)
var (
@@ -50,7 +51,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
users: options.Users,
}
var serviceOptions []vmess.ServiceOption
if timeFunc := router.TimeFunc(); timeFunc != nil {
if timeFunc := ntp.TimeFuncFromContext(ctx); timeFunc != nil {
serviceOptions = append(serviceOptions, vmess.ServiceWithTimeFunc(timeFunc))
}
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
}
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 {
return nil, err
}

View File

@@ -87,6 +87,7 @@ nav:
- ShadowTLS: configuration/inbound/shadowtls.md
- VLESS: configuration/inbound/vless.md
- TUIC: configuration/inbound/tuic.md
- Hysteria2: configuration/inbound/hysteria2.md
- Tun: configuration/inbound/tun.md
- Redirect: configuration/inbound/redirect.md
- TProxy: configuration/inbound/tproxy.md
@@ -105,6 +106,7 @@ nav:
- ShadowsocksR: configuration/outbound/shadowsocksr.md
- VLESS: configuration/outbound/vless.md
- TUIC: configuration/outbound/tuic.md
- Hysteria2: configuration/outbound/hysteria2.md
- Tor: configuration/outbound/tor.md
- SSH: configuration/outbound/ssh.md
- DNS: configuration/outbound/dns.md

View File

@@ -18,7 +18,7 @@ import (
"github.com/sagernet/sing/common/ntp"
)
var _ adapter.TimeService = (*Service)(nil)
var _ ntp.TimeService = (*Service)(nil)
type Service struct {
ctx context.Context

View File

@@ -3,7 +3,7 @@ package option
import (
"encoding/json"
"github.com/sagernet/sing-box/common/humanize"
"github.com/dustin/go-humanize"
)
type DebugOptions struct {
@@ -13,21 +13,21 @@ type DebugOptions struct {
MaxThreads *int `json:"max_threads,omitempty"`
PanicOnFault *bool `json:"panic_on_fault,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"`
}
type MemoryBytes uint64
type BytesLength int64
func (l MemoryBytes) MarshalJSON() ([]byte, error) {
return json.Marshal(humanize.MemoryBytes(uint64(l)))
func (l BytesLength) MarshalJSON() ([]byte, error) {
return json.Marshal(humanize.IBytes(uint64(l)))
}
func (l *MemoryBytes) UnmarshalJSON(bytes []byte) error {
func (l *BytesLength) UnmarshalJSON(bytes []byte) error {
var valueInteger int64
err := json.Unmarshal(bytes, &valueInteger)
if err == nil {
*l = MemoryBytes(valueInteger)
*l = BytesLength(valueInteger)
return nil
}
var valueString string
@@ -35,10 +35,10 @@ func (l *MemoryBytes) UnmarshalJSON(bytes []byte) error {
if err != nil {
return err
}
parsedValue, err := humanize.ParseMemoryBytes(valueString)
parsedValue, err := humanize.ParseBytes(valueString)
if err != nil {
return err
}
*l = MemoryBytes(parsedValue)
*l = BytesLength(parsedValue)
return nil
}

33
option/hysteria2.go Normal file
View File

@@ -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"`
}

View File

@@ -24,6 +24,7 @@ type _Inbound struct {
ShadowTLSOptions ShadowTLSInboundOptions `json:"-"`
VLESSOptions VLESSInboundOptions `json:"-"`
TUICOptions TUICInboundOptions `json:"-"`
Hysteria2Options Hysteria2InboundOptions `json:"-"`
}
type Inbound _Inbound
@@ -61,6 +62,8 @@ func (h Inbound) MarshalJSON() ([]byte, error) {
v = h.VLESSOptions
case C.TypeTUIC:
v = h.TUICOptions
case C.TypeHysteria2:
v = h.Hysteria2Options
default:
return nil, E.New("unknown inbound type: ", h.Type)
}
@@ -104,6 +107,8 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
v = &h.VLESSOptions
case C.TypeTUIC:
v = &h.TUICOptions
case C.TypeHysteria2:
v = &h.Hysteria2Options
default:
return E.New("unknown inbound type: ", h.Type)
}

View File

@@ -24,6 +24,7 @@ type _Outbound struct {
ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"`
VLESSOptions VLESSOutboundOptions `json:"-"`
TUICOptions TUICOutboundOptions `json:"-"`
Hysteria2Options Hysteria2OutboundOptions `json:"-"`
SelectorOptions SelectorOutboundOptions `json:"-"`
URLTestOptions URLTestOutboundOptions `json:"-"`
}
@@ -63,6 +64,8 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
v = h.VLESSOptions
case C.TypeTUIC:
v = h.TUICOptions
case C.TypeHysteria2:
v = h.Hysteria2Options
case C.TypeSelector:
v = h.SelectorOptions
case C.TypeURLTest:
@@ -110,6 +113,8 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
v = &h.VLESSOptions
case C.TypeTUIC:
v = &h.TUICOptions
case C.TypeHysteria2:
v = &h.Hysteria2Options
case C.TypeSelector:
v = &h.SelectorOptions
case C.TypeURLTest:

View File

@@ -8,11 +8,12 @@ type InboundTLSOptions struct {
MinVersion string `json:"min_version,omitempty"`
MaxVersion string `json:"max_version,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"`
Key string `json:"key,omitempty"`
Key Listable[string] `json:"key,omitempty"`
KeyPath string `json:"key_path,omitempty"`
ACME *InboundACMEOptions `json:"acme,omitempty"`
ECH *InboundECHOptions `json:"ech,omitempty"`
Reality *InboundRealityOptions `json:"reality,omitempty"`
}
@@ -45,11 +46,20 @@ type InboundRealityHandshakeOptions struct {
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 {
Enabled bool `json:"enabled,omitempty"`
PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"`
DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"`
Config string `json:"config,omitempty"`
Enabled bool `json:"enabled,omitempty"`
PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"`
DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"`
Config Listable[string] `json:"config,omitempty"`
ConfigPath string `json:"config_path,omitempty"`
}
type OutboundUTLSOptions struct {

View File

@@ -30,7 +30,7 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, t
case C.TypeSOCKS:
return NewSocks(router, logger, tag, options.SocksOptions)
case C.TypeHTTP:
return NewHTTP(router, logger, tag, options.HTTPOptions)
return NewHTTP(ctx, router, logger, tag, options.HTTPOptions)
case C.TypeShadowsocks:
return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions)
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)
case C.TypeTUIC:
return NewTUIC(ctx, router, logger, tag, options.TUICOptions)
case C.TypeHysteria2:
return NewHysteria2(ctx, router, logger, tag, options.Hysteria2Options)
case C.TypeSelector:
return NewSelector(router, logger, tag, options.SelectorOptions)
case C.TypeURLTest:

View File

@@ -70,28 +70,6 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
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 {
ctx = adapter.WithContext(ctx, &metadata)
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))
}
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 {
if cachedReader, isCached := conn.(N.CachedReader); isCached {
payload := cachedReader.ReadCached()
if payload != nil && !payload.IsEmpty() {
_, err := serverConn.Write(payload.Bytes())
payload.Release()
if err != nil {
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))
if err != os.ErrInvalid {
if err != nil {
payload.Release()
return err
}
_, err = payload.ReadOnceFrom(conn)
if err != nil && !E.IsTimeout(err) {
payload.Release()
return E.Cause(err, "read payload")
}
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())
payload.Release()
if err != nil {
return N.HandshakeFailure(conn, err)
}
payload.Release()
}
return bufio.CopyConn(ctx, conn, serverConn)
}

View File

@@ -25,12 +25,12 @@ type HTTP struct {
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)
if err != nil {
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 {
return nil, err
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/sagernet/quic-go/congestion"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/qtls"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
@@ -33,7 +34,7 @@ type Hysteria struct {
ctx context.Context
dialer N.Dialer
serverAddr M.Socksaddr
tlsConfig *tls.STDConfig
tlsConfig tls.Config
quicConfig *quic.Config
authKey []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 {
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 {
return nil, err
}
tlsConfig.MinVersion = tls.VersionTLS13
if len(tlsConfig.NextProtos) == 0 {
tlsConfig.NextProtos = []string{hysteria.DefaultALPN}
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{hysteria.DefaultALPN})
}
quicConfig := &quic.Config{
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.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 {
packetConn.Close()
return nil, err

122
outbound/hysteria2.go Normal file
View File

@@ -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)
}

View File

@@ -14,3 +14,7 @@ import (
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) {
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
}

View File

@@ -57,7 +57,7 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
serverAddr: options.ServerOptions.Build(),
}
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 {
return nil, err
}

View File

@@ -47,7 +47,7 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
options.TLS.MinVersion = "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 {
return nil, err
}

View File

@@ -80,11 +80,11 @@ func (h *Socks) DialContext(ctx context.Context, network string, destination M.S
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
if h.resolve && destination.IsFqdn() {
destinationAddresses, err := h.router.LookupDefault(ctx, destination.Fqdn)
addrs, err := h.router.LookupDefault(ctx, destination.Fqdn)
if err != nil {
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)
}
@@ -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)
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)
return h.client.ListenPacket(ctx, destination)
}
func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if h.resolve {
return NewDirectConnection(ctx, h.router, h, conn, metadata)
} else {
return NewConnection(ctx, h, conn, metadata)
}
return NewConnection(ctx, h, conn, metadata)
}
func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
if h.resolve {
return NewDirectPacketConnection(ctx, h.router, h, conn, metadata)
} else {
return NewPacketConnection(ctx, h, conn, metadata)
}
return NewPacketConnection(ctx, h, conn, metadata)
}

View File

@@ -51,7 +51,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
key: trojan.Key(options.Password),
}
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 {
return nil, err
}

View File

@@ -41,11 +41,7 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}
abstractTLSConfig, err := tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
tlsConfig, err := abstractTLSConfig.Config()
tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}

View File

@@ -53,7 +53,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
serverAddr: options.ServerOptions.Build(),
}
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 {
return nil, err
}

View File

@@ -18,6 +18,7 @@ import (
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
)
var _ adapter.Outbound = (*VMess)(nil)
@@ -52,7 +53,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
serverAddr: options.ServerOptions.Build(),
}
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 {
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)
}
var clientOptions []vmess.ClientOption
if timeFunc := router.TimeFunc(); timeFunc != nil {
if timeFunc := ntp.TimeFuncFromContext(ctx); timeFunc != nil {
clientOptions = append(clientOptions, vmess.ClientWithTimeFunc(timeFunc))
}
if options.GlobalPadding {

View File

@@ -202,37 +202,26 @@ func (w *WireGuard) DialContext(ctx context.Context, network string, destination
w.logger.InfoContext(ctx, "outbound packet connection to ", destination)
}
if destination.IsFqdn() {
destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn)
addrs, err := w.router.LookupDefault(ctx, destination.Fqdn)
if err != nil {
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)
}
func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
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)
}
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 {
return NewDirectPacketConnection(ctx, w.router, w, conn, metadata)
return NewPacketConnection(ctx, w, conn, metadata)
}
func (w *WireGuard) Start() error {

View File

@@ -12,8 +12,8 @@ import (
"time"
"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/conntrack"
"github.com/sagernet/sing-box/common/geoip"
"github.com/sagernet/sing-box/common/geosite"
"github.com/sagernet/sing-box/common/mux"
@@ -81,7 +81,7 @@ type Router struct {
interfaceMonitor tun.DefaultInterfaceMonitor
packageManager tun.PackageManager
processSearcher process.Searcher
timeService adapter.TimeService
timeService *ntp.Service
pauseManager pause.Manager
clashServer adapter.ClashServer
v2rayServer adapter.V2RayServer
@@ -521,7 +521,7 @@ func (r *Router) Close() error {
return E.Cause(err, "close dns transport[", i, "]")
})
}
if r.geoIPReader != nil {
if r.geositeReader != nil {
r.logger.Trace("closing geoip reader")
err = E.Append(err, common.Close(r.geoIPReader), func(err error) error {
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
}
conntrack.KillerCheck()
metadata.Network = N.NetworkTCP
switch metadata.Destination.Fqdn {
case mux.Destination.Fqdn:
@@ -739,7 +738,6 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
}
return nil
}
conntrack.KillerCheck()
metadata.Network = N.NetworkUDP
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() {
return r.platformInterface.AutoDetectInterfaceControl()
} 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
if C.IsLinux {
interfaceName = r.InterfaceMonitor().DefaultInterfaceName(remoteAddr)
interfaceIndex = -1
if interfaceName == "" {
err = tun.ErrNoRoute
}
return r.InterfaceMonitor().DefaultInterfaceName(remoteAddr), -1
} else {
interfaceIndex = r.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
if interfaceIndex == -1 {
err = tun.ErrNoRoute
}
return "", r.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
}
return
})
}
}
@@ -960,13 +950,6 @@ func (r *Router) PackageManager() tun.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 {
return r.clashServer
}

View File

@@ -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)
if len(addrs) > 0 {
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 {
r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
err = dns.RCodeNameError
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
if err == nil {
err = dns.RCodeNameError
}
}
return addrs, err
}

Some files were not shown because too many files have changed in this diff Show More