Compare commits

..

12 Commits

Author SHA1 Message Date
世界
46bfeb574c documentation: Bump version 2023-09-12 15:57:33 +08:00
世界
a1449ee40e Mark deprecated features 2023-09-12 13:26:23 +08:00
世界
8cb41b5fa6 documentation: Add hysteria2 2023-09-12 13:26:22 +08:00
世界
53475c7390 Add hysteria2 protocol 2023-09-12 13:26:21 +08:00
世界
5d8af150a7 Improve system proxy API 2023-09-12 13:26:21 +08:00
Hiddify
69499a51a5 Add KDE set system proxy support
Co-authored-by: Hiddify <114227601+hiddify1@users.noreply.github.com>
2023-09-12 13:26:20 +08:00
世界
4c050d7f4b Add ECH support for QUIC based protocols 2023-09-12 13:26:20 +08:00
世界
533fca9fa3 documentation: Update TLS ECH struct 2023-09-12 13:26:20 +08:00
世界
187bf2f7bc Enable with_ech by default 2023-09-12 13:26:19 +08:00
世界
983a4222ad Add ECH keypair generator 2023-09-12 13:26:19 +08:00
世界
2ea506aeb8 Remove legacy NTP usages 2023-09-12 13:26:19 +08:00
世界
5b343d4c72 Improve ECH support 2023-09-12 13:26:19 +08:00
133 changed files with 4683 additions and 719 deletions

View File

@@ -11,18 +11,18 @@ jobs:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
- 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

@@ -16,6 +16,7 @@ builds:
- with_quic
- with_dhcp
- with_wireguard
- with_ech
- with_utls
- with_reality_server
- with_clash_api
@@ -51,6 +52,7 @@ builds:
- with_quic
- with_dhcp
- with_wireguard
- with_ech
- with_utls
- with_clash_api
env:

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

@@ -54,7 +54,7 @@ func init() {
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api")
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
debugTags = append(debugTags, "debug")
}

View File

@@ -29,8 +29,7 @@ func main() {
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfa"}, newVersion.VersionString())
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.independent", "io.nekohasekai.sfa.system"}, newVersion.String())
if updated0 || updated1 {
log.Info("updated version to ", newVersion.VersionString())
common.Must(os.WriteFile("sing-box.xcodeproj/project.pbxproj.bak", []byte(projectContent), 0o644))
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
common.Must(os.WriteFile("sing-box.xcodeproj/project.pbxproj", []byte(newContent), 0o644))
} else {
log.Info("version not changed")

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

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

@@ -29,12 +29,7 @@ func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error)
}
domainStrategy := dns.DomainStrategy(options.DomainStrategy)
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
dialer = NewResolveDialer(
router,
dialer,
options.Detour == "" && !options.TCPFastOpen,
domainStrategy,
time.Duration(options.FallbackDelay))
dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))
}
return dialer, nil
}

View File

@@ -16,16 +16,14 @@ import (
type ResolveDialer struct {
dialer N.Dialer
parallel bool
router adapter.Router
strategy dns.DomainStrategy
fallbackDelay time.Duration
}
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
func NewResolveDialer(router adapter.Router, dialer N.Dialer, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
return &ResolveDialer{
dialer,
parallel,
router,
strategy,
fallbackDelay,
@@ -50,11 +48,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
if err != nil {
return nil, err
}
if d.parallel {
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay)
} else {
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
}
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay)
}
func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, 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

@@ -1,43 +1,73 @@
package settings
import (
"context"
"os"
"strings"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/shell"
)
var (
useRish bool
rishPath string
)
type AndroidSystemProxy struct {
useRish bool
rishPath string
serverAddr M.Socksaddr
supportSOCKS bool
isEnabled bool
}
func init() {
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*AndroidSystemProxy, error) {
userId := os.Getuid()
var (
useRish bool
rishPath string
)
if userId == 0 || userId == 1000 || userId == 2000 {
useRish = false
} else {
rishPath, useRish = C.FindPath("rish")
if !useRish {
return nil, E.Cause(os.ErrPermission, "root or system (adb) permission is required for set system proxy")
}
}
}
func runAndroidShell(name string, args ...string) error {
if !useRish {
return shell.Exec(name, args...).Attach().Run()
} else {
return shell.Exec("sh", rishPath, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
}
}
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
err := runAndroidShell("settings", "put", "global", "http_proxy", F.ToString("127.0.0.1:", port))
if err != nil {
return nil, err
}
return func() error {
return runAndroidShell("settings", "put", "global", "http_proxy", ":0")
return &AndroidSystemProxy{
useRish: useRish,
rishPath: rishPath,
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
}, nil
}
func (p *AndroidSystemProxy) IsEnabled() bool {
return p.isEnabled
}
func (p *AndroidSystemProxy) Enable() error {
err := p.runAndroidShell("settings", "put", "global", "http_proxy", p.serverAddr.String())
if err != nil {
return err
}
p.isEnabled = true
return nil
}
func (p *AndroidSystemProxy) Disable() error {
err := p.runAndroidShell("settings", "put", "global", "http_proxy", ":0")
if err != nil {
return err
}
p.isEnabled = false
return nil
}
func (p *AndroidSystemProxy) runAndroidShell(name string, args ...string) error {
if !p.useRish {
return shell.Exec(name, args...).Attach().Run()
} else {
return shell.Exec("sh", p.rishPath, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
}
}

View File

@@ -1,56 +1,56 @@
package settings
import (
"context"
"net/netip"
"strconv"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/shell"
"github.com/sagernet/sing/common/x/list"
)
type systemProxy struct {
type DarwinSystemProxy struct {
monitor tun.DefaultInterfaceMonitor
interfaceName string
element *list.Element[tun.DefaultInterfaceUpdateCallback]
port uint16
isMixed bool
serverAddr M.Socksaddr
supportSOCKS bool
isEnabled bool
}
func (p *systemProxy) update(event int) {
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
if p.interfaceName == newInterfaceName {
return
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {
interfaceMonitor := adapter.RouterFromContext(ctx).InterfaceMonitor()
if interfaceMonitor == nil {
return nil, E.New("missing interface monitor")
}
if p.interfaceName != "" {
_ = p.unset()
proxy := &DarwinSystemProxy{
monitor: interfaceMonitor,
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
}
p.interfaceName = newInterfaceName
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
if err != nil {
return
}
if p.isMixed {
err = shell.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
if err == nil {
err = shell.Exec("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
if err == nil {
_ = shell.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
return
proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
return proxy, nil
}
func (p *systemProxy) unset() error {
func (p *DarwinSystemProxy) IsEnabled() bool {
return p.isEnabled
}
func (p *DarwinSystemProxy) Enable() error {
return p.update0()
}
func (p *DarwinSystemProxy) Disable() error {
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
if err != nil {
return err
}
if p.isMixed {
if p.supportSOCKS {
err = shell.Exec("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off").Attach().Run()
}
if err == nil {
@@ -59,9 +59,53 @@ func (p *systemProxy) unset() error {
if err == nil {
err = shell.Exec("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off").Attach().Run()
}
if err == nil {
p.isEnabled = false
}
return err
}
func (p *DarwinSystemProxy) update(event int) {
if event&tun.EventInterfaceUpdate == 0 {
return
}
if !p.isEnabled {
return
}
_ = p.update0()
}
func (p *DarwinSystemProxy) update0() error {
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
if p.interfaceName == newInterfaceName {
return nil
}
if p.interfaceName != "" {
_ = p.Disable()
}
p.interfaceName = newInterfaceName
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
if err != nil {
return err
}
if p.supportSOCKS {
err = shell.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, p.serverAddr.AddrString(), strconv.Itoa(int(p.serverAddr.Port))).Attach().Run()
}
if err != nil {
return err
}
err = shell.Exec("networksetup", "-setwebproxy", interfaceDisplayName, p.serverAddr.AddrString(), strconv.Itoa(int(p.serverAddr.Port))).Attach().Run()
if err != nil {
return err
}
err = shell.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, p.serverAddr.AddrString(), strconv.Itoa(int(p.serverAddr.Port))).Attach().Run()
if err != nil {
return err
}
p.isEnabled = true
return nil
}
func getInterfaceDisplayName(name string) (string, error) {
content, err := shell.Exec("networksetup", "-listallhardwareports").ReadOutput()
if err != nil {
@@ -77,21 +121,3 @@ func getInterfaceDisplayName(name string) (string, error) {
}
return "", E.New(name, " not found in networksetup -listallhardwareports")
}
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
interfaceMonitor := router.InterfaceMonitor()
if interfaceMonitor == nil {
return nil, E.New("missing interface monitor")
}
proxy := &systemProxy{
monitor: interfaceMonitor,
port: port,
isMixed: isMixed,
}
proxy.update(tun.EventInterfaceUpdate)
proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
return func() error {
interfaceMonitor.UnregisterCallback(proxy.element)
return proxy.unset()
}, nil
}

View File

@@ -3,75 +3,161 @@
package settings
import (
"context"
"os"
"os/exec"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/shell"
)
var (
hasGSettings bool
sudoUser string
)
type LinuxSystemProxy struct {
hasGSettings bool
hasKWriteConfig5 bool
sudoUser string
serverAddr M.Socksaddr
supportSOCKS bool
isEnabled bool
}
func init() {
hasGSettings = common.Error(exec.LookPath("gsettings")) == nil
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*LinuxSystemProxy, error) {
hasGSettings := common.Error(exec.LookPath("gsettings")) == nil
hasKWriteConfig5 := common.Error(exec.LookPath("kwriteconfig5")) == nil
var sudoUser string
if os.Getuid() == 0 {
sudoUser = os.Getenv("SUDO_USER")
}
if !hasGSettings && !hasKWriteConfig5 {
return nil, E.New("unsupported desktop environment")
}
return &LinuxSystemProxy{
hasGSettings: hasGSettings,
hasKWriteConfig5: hasKWriteConfig5,
sudoUser: sudoUser,
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
}, nil
}
func runAsUser(name string, args ...string) error {
func (p *LinuxSystemProxy) IsEnabled() bool {
return p.isEnabled
}
func (p *LinuxSystemProxy) Enable() error {
if p.hasGSettings {
err := p.runAsUser("gsettings", "set", "org.gnome.system.proxy.http", "enabled", "true")
if err != nil {
return err
}
if p.supportSOCKS {
err = p.setGnomeProxy("ftp", "http", "https", "socks")
} else {
err = p.setGnomeProxy("http", "https")
}
if err != nil {
return err
}
err = p.runAsUser("gsettings", "set", "org.gnome.system.proxy", "use-same-proxy", F.ToString(p.supportSOCKS))
if err != nil {
return err
}
err = p.runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "manual")
if err != nil {
return err
}
}
if p.hasKWriteConfig5 {
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "ProxyType", "1")
if err != nil {
return err
}
if p.supportSOCKS {
err = p.setKDEProxy("ftp", "http", "https", "socks")
} else {
err = p.setKDEProxy("http", "https")
}
if err != nil {
return err
}
err = p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "Authmode", "0")
if err != nil {
return err
}
err = p.runAsUser("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
if err != nil {
return err
}
}
p.isEnabled = true
return nil
}
func (p *LinuxSystemProxy) Disable() error {
if p.hasGSettings {
err := p.runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "none")
if err != nil {
return err
}
}
if p.hasKWriteConfig5 {
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "ProxyType", "0")
if err != nil {
return err
}
err = p.runAsUser("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
if err != nil {
return err
}
}
p.isEnabled = false
return nil
}
func (p *LinuxSystemProxy) runAsUser(name string, args ...string) error {
if os.Getuid() != 0 {
return shell.Exec(name, args...).Attach().Run()
} else if sudoUser != "" {
return shell.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
} else if p.sudoUser != "" {
return shell.Exec("su", "-", p.sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
} else {
return E.New("set system proxy: unable to set as root")
}
}
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
if !hasGSettings {
return nil, E.New("unsupported desktop environment")
}
err := runAsUser("gsettings", "set", "org.gnome.system.proxy.http", "enabled", "true")
if err != nil {
return nil, err
}
if isMixed {
err = setGnomeProxy(port, "ftp", "http", "https", "socks")
} else {
err = setGnomeProxy(port, "http", "https")
}
if err != nil {
return nil, err
}
err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "use-same-proxy", F.ToString(isMixed))
if err != nil {
return nil, err
}
err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "manual")
if err != nil {
return nil, err
}
return func() error {
return runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "none")
}, nil
}
func setGnomeProxy(port uint16, proxyTypes ...string) error {
func (p *LinuxSystemProxy) setGnomeProxy(proxyTypes ...string) error {
for _, proxyType := range proxyTypes {
err := runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "host", "127.0.0.1")
err := p.runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "host", p.serverAddr.AddrString())
if err != nil {
return err
}
err = runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "port", F.ToString(port))
err = p.runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "port", F.ToString(p.serverAddr.Port))
if err != nil {
return err
}
}
return nil
}
func (p *LinuxSystemProxy) setKDEProxy(proxyTypes ...string) error {
for _, proxyType := range proxyTypes {
var proxyUrl string
if proxyType == "socks" {
proxyUrl = "socks://" + p.serverAddr.String()
} else {
proxyUrl = "http://" + p.serverAddr.String()
}
err := p.runAsUser(
"kwriteconfig5",
"--file",
"kioslaverc",
"--group",
"Proxy Settings",
"--key", proxyType+"Proxy",
proxyUrl,
)
if err != nil {
return err
}

View File

@@ -3,11 +3,12 @@
package settings
import (
"context"
"os"
"github.com/sagernet/sing-box/adapter"
M "github.com/sagernet/sing/common/metadata"
)
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (SystemProxy, error) {
return nil, os.ErrInvalid
}

View File

@@ -1,17 +1,43 @@
package settings
import (
"github.com/sagernet/sing-box/adapter"
F "github.com/sagernet/sing/common/format"
"context"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/wininet"
)
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
err := wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", port), "")
if err != nil {
return nil, err
}
return func() error {
return wininet.ClearSystemProxy()
type WindowsSystemProxy struct {
serverAddr M.Socksaddr
supportSOCKS bool
isEnabled bool
}
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*WindowsSystemProxy, error) {
return &WindowsSystemProxy{
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
}, nil
}
func (p *WindowsSystemProxy) IsEnabled() bool {
return p.isEnabled
}
func (p *WindowsSystemProxy) Enable() error {
err := wininet.SetSystemProxy("http://"+p.serverAddr.String(), "")
if err != nil {
return err
}
p.isEnabled = true
return nil
}
func (p *WindowsSystemProxy) Disable() error {
err := wininet.ClearSystemProxy()
if err != nil {
return err
}
p.isEnabled = false
return nil
}

View File

@@ -0,0 +1,7 @@
package settings
type SystemProxy interface {
IsEnabled() bool
Enable() error
Disable() error
}

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 ECH config")
}
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.echWatcher = 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.echWatcher.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 ECH key")
}
echKey = content
} else {
return nil, E.New("missing ECH key")
}
block, rest := pem.Decode(echKey)
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
return nil, E.New("invalid ECH keys pem")
}
echKeys, err := 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,54 @@
#### 1.5.0-beta.8
* Fixes and improvements
#### 1.4.2
* Fixes and improvements
#### 1.5.0-beta.6
* Fix compatibility issues with official Hysteria2 server and client
* Fixes and improvements
* Mark [deprecated features](/deprecated)
#### 1.5.0-beta.3
* Fixes and improvements
* Updated Hysteria2 documentation **1**
**1**:
Added notes indicating compatibility issues with the official
Hysteria2 server and client when using `fastOpen=false` or UDP MTU >= 1200.
#### 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 generate ech-keypair [-pq-signature-schemes-enabled]` 生成。
#### pq_signature_schemes_enabled
启用对后量子对等证书签名方案的支持。
建议匹配 `sing-box generate 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 ""

13
docs/deprecated.md Normal file
View File

@@ -0,0 +1,13 @@
# Deprecated Feature List
The following features will be marked deprecated in 1.5.0 and removed entirely in 1.6.0.
#### ShadowsocksR
ShadowsocksR support has never been enabled by default, since the most commonly used proxy sales panel in the
illegal industry stopped using this protocol, it does not make sense to continue to maintain it.
#### Proxy Protocol
Proxy Protocol is added by Pull Request, has problems, is only used by the backend of HTTP multiplexers such as nginx,
is intrusive, and is meaningless for proxy purposes.

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

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

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

15
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,17 +22,17 @@ 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/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.2.10-0.20230925134514-7ce1ab786c10
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 v0.2.10-0.20230912050851-1453c7c8c20d
github.com/sagernet/sing-dns v0.1.9-0.20230911082806-425022bdc92b
github.com/sagernet/sing-mux v0.1.3-0.20230908032617-759a1886a400
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-shadowtls v0.1.4
github.com/sagernet/sing-tun v0.1.12-0.20230926093914-0d0ebad6cfa5
github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641
github.com/sagernet/sing-vmess v0.1.8-0.20230907010359-161fb0ac716b
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
@@ -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

26
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,8 +100,8 @@ 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=
@@ -110,20 +112,20 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL
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.20230925134514-7ce1ab786c10 h1:kYu1ScB1dtdGTzSNkC5eMxtK05Q8kYHExq2SlRa5M/s=
github.com/sagernet/sing v0.2.10-0.20230925134514-7ce1ab786c10/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 v0.2.10-0.20230912050851-1453c7c8c20d h1:dWUNsHDX8EMeGj1XCnrVtydy5C+5D+Orc5JjZP7myHg=
github.com/sagernet/sing v0.2.10-0.20230912050851-1453c7c8c20d/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
github.com/sagernet/sing-dns v0.1.9-0.20230911082806-425022bdc92b h1:m/UWg2voyb94YCWAMInMXIQ91qQGZ6utPAwKCYrlciI=
github.com/sagernet/sing-dns v0.1.9-0.20230911082806-425022bdc92b/go.mod h1:Kg98PBJEg/08jsNFtmZWmPomhskn9Ausn50ecNm4M+8=
github.com/sagernet/sing-mux v0.1.3-0.20230908032617-759a1886a400 h1:LtpYd5c5AJtUSxjyH4KjUS8HT+2XgilyozjbCq/x3EM=
github.com/sagernet/sing-mux v0.1.3-0.20230908032617-759a1886a400/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-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.20230926093914-0d0ebad6cfa5 h1:2KpqI83FequwVysUX6SkUCFvR7pmoGzxei/QD0p682M=
github.com/sagernet/sing-tun v0.1.12-0.20230926093914-0d0ebad6cfa5/go.mod h1:+YImslQMLgMQcVgZZ9IK4ue1o/605VSU90amHUcp4hA=
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.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/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
@@ -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

@@ -33,8 +33,8 @@ type myInboundAdapter struct {
// http mixed
setSystemProxy bool
clearSystemProxy func() error
setSystemProxy bool
systemProxy settings.SystemProxy
// internal
@@ -91,7 +91,19 @@ func (a *myInboundAdapter) Start() error {
}
}
if a.setSystemProxy {
a.clearSystemProxy, err = settings.SetSystemProxy(a.router, M.SocksaddrFromNet(a.tcpListener.Addr()).Port, a.protocol == C.TypeMixed)
listenPort := M.SocksaddrFromNet(a.tcpListener.Addr()).Port
var listenAddrString string
listenAddr := a.listenOptions.Listen.Build()
if listenAddr.IsUnspecified() {
listenAddrString = "127.0.0.1"
} else {
listenAddrString = listenAddr.String()
}
a.systemProxy, err = settings.NewSystemProxy(a.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), a.protocol == C.TypeMixed)
if err != nil {
return E.Cause(err, "initialize system proxy")
}
err = a.systemProxy.Enable()
if err != nil {
return E.Cause(err, "set system proxy")
}
@@ -102,8 +114,8 @@ func (a *myInboundAdapter) Start() error {
func (a *myInboundAdapter) Close() error {
a.inShutdown.Store(true)
var err error
if a.clearSystemProxy != nil {
err = a.clearSystemProxy()
if a.systemProxy != nil && a.systemProxy.IsEnabled() {
err = a.systemProxy.Disable()
}
return E.Errors(err, common.Close(
a.tcpListener,

View File

@@ -35,7 +35,7 @@ func (a *myInboundAdapter) ListenTCP() (net.Listener, error) {
a.logger.Info("tcp server started at ", tcpListener.Addr())
}
if a.listenOptions.ProxyProtocol {
a.logger.Debug("proxy protocol enabled")
a.logger.Warn("Proxy Protocol is deprecated, see https://sing-box.sagernet.org/deprecated")
tcpListener = &proxyproto.Listener{Listener: tcpListener, AcceptNoHeader: a.listenOptions.ProxyProtocolAcceptNoHeader}
}
a.tcpListener = tcpListener

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

@@ -30,9 +30,10 @@ theme:
- navigation.sections
- header.autohide
nav:
- Getting Started:
- Home:
- index.md
- Features: features.md
- Deprecated: deprecated.md
- Support: support.md
- Change Log: changelog.md
- Installation:
@@ -87,6 +88,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 +107,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

@@ -65,7 +65,11 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
}
if err != nil {
return N.HandshakeFailure(conn, err)
return N.ReportHandshakeFailure(conn, err)
}
err = N.ReportHandshakeSuccess(conn)
if err != nil {
return err
}
return CopyEarlyConn(ctx, conn, outConn)
}
@@ -80,14 +84,18 @@ func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dial
var destinationAddresses []netip.Addr
destinationAddresses, err = router.LookupDefault(ctx, metadata.Destination.Fqdn)
if err != nil {
return N.HandshakeFailure(conn, err)
return N.ReportHandshakeFailure(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 N.ReportHandshakeFailure(conn, err)
}
err = N.ReportHandshakeSuccess(conn)
if err != nil {
return err
}
return CopyEarlyConn(ctx, conn, outConn)
}
@@ -103,7 +111,11 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
outConn, err = this.ListenPacket(ctx, metadata.Destination)
}
if err != nil {
return N.HandshakeFailure(conn, err)
return N.ReportHandshakeFailure(conn, err)
}
err = N.ReportHandshakeSuccess(conn)
if err != nil {
return err
}
if destinationAddress.IsValid() {
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
@@ -132,14 +144,18 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this
var destinationAddresses []netip.Addr
destinationAddresses, err = router.LookupDefault(ctx, metadata.Destination.Fqdn)
if err != nil {
return N.HandshakeFailure(conn, err)
return N.ReportHandshakeFailure(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)
return N.ReportHandshakeFailure(conn, err)
}
err = N.ReportHandshakeSuccess(conn)
if err != nil {
return err
}
if destinationAddress.IsValid() {
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
@@ -162,7 +178,6 @@ func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) erro
payload := cachedReader.ReadCached()
if payload != nil && !payload.IsEmpty() {
_, err := serverConn.Write(payload.Bytes())
payload.Release()
if err != nil {
return err
}
@@ -174,12 +189,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 +202,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)
return N.ReportHandshakeFailure(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

@@ -37,6 +37,7 @@ type ShadowsocksR struct {
}
func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (*ShadowsocksR, error) {
logger.Warn("ShadowsocksR is deprecated, see https://sing-box.sagernet.org/deprecated")
outboundDialer, err := dialer.New(router, options.DialerOptions)
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

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

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