Compare commits

..

10 Commits

Author SHA1 Message Date
世界
fcbb8aef53 documentation: Bump version 2023-10-08 12:47:45 +08:00
世界
8670ab241a Add brutal debug option for Hysteria2 2023-10-08 12:45:15 +08:00
世界
e453343a56 Update BBR and Hysteria congestion control 2023-10-08 12:18:44 +08:00
septs
6881f35805 Improve naive auth logical 2023-10-08 12:07:56 +08:00
septs
8214da9697 Improve listable parsing 2023-10-08 11:46:16 +08:00
世界
dfb8117364 documentation: Bump version 2023-10-07 10:05:00 +08:00
世界
3fac02de5c Update quic-go and BBR congestion control 2023-10-07 10:04:47 +08:00
世界
ca4639eb00 Update gVisor to 20230814.0 2023-10-07 10:04:47 +08:00
世界
aa9bfbf5b4 Improve linux bind interface 2023-10-07 10:04:47 +08:00
世界
048293075c Remove deprecated features 2023-10-07 10:04:47 +08:00
58 changed files with 1747 additions and 1140 deletions

View File

@@ -3,7 +3,6 @@ name: Debug build
on:
push:
branches:
- stable-next
- main-next
- dev-next
paths-ignore:
@@ -12,7 +11,6 @@ on:
- '!.github/workflows/debug.yml'
pull_request:
branches:
- stable-next
- main-next
- dev-next

View File

@@ -3,7 +3,6 @@ name: Lint
on:
push:
branches:
- stable-next
- main-next
- dev-next
paths-ignore:
@@ -12,7 +11,6 @@ on:
- '!.github/workflows/lint.yml'
pull_request:
branches:
- stable-next
- main-next
- dev-next
@@ -36,6 +34,4 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
args: --timeout=30m
install-mode: binary
version: latest

View File

@@ -36,35 +36,6 @@ builds:
- darwin_amd64_v3
- darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}'
- id: legacy
main: ./cmd/sing-box
flags:
- -v
- -trimpath
asmflags:
- all=-trimpath={{.Env.GOPATH}}
gcflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_ech
- with_utls
- with_reality_server
- with_clash_api
env:
- CGO_ENABLED=0
- GOROOT=/nix/store/5h8gjl89zx8qxgc572wa3k81zplv8v4z-go-1.20.10/share/go
gobinary: /nix/store/5h8gjl89zx8qxgc572wa3k81zplv8v4z-go-1.20.10/bin/go
targets:
- windows_amd64_v1
- windows_386
- darwin_amd64_v1
mod_timestamp: '{{ .CommitTimestamp }}'
- id: android
main: ./cmd/sing-box
flags:
@@ -119,9 +90,6 @@ snapshot:
name_template: "{{ .Version }}.{{ .ShortCommit }}"
archives:
- id: archive
builds:
- main
- android
format: tar.gz
format_overrides:
- goos: windows
@@ -130,17 +98,6 @@ archives:
files:
- LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
- id: archive-legacy
builds:
- legacy
format: tar.gz
format_overrides:
- goos: windows
format: zip
wrap_in_directory: true
files:
- LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
nfpms:
- id: package
package_name: sing-box

View File

@@ -63,7 +63,7 @@ release:
mkdir dist/release
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
rm -r dist/release
rm -r dist
release_install:
go install -v github.com/goreleaser/goreleaser@latest
@@ -93,7 +93,7 @@ build_ios:
upload_ios_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist
release_ios: build_ios upload_ios_app_store
@@ -104,7 +104,7 @@ build_macos:
upload_macos_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist
release_macos: build_macos upload_macos_app_store
@@ -115,7 +115,7 @@ build_macos_independent:
notarize_macos_independent:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist -allowProvisioningUpdates
xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist
wait_notarize_macos_independent:
sleep 60
@@ -141,7 +141,7 @@ build_tvos:
upload_tvos_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist
release_tvos: build_tvos upload_tvos_app_store

View File

@@ -75,11 +75,3 @@ func AppendContext(ctx context.Context) (context.Context, *InboundContext) {
metadata = new(InboundContext)
return WithContext(ctx, metadata), metadata
}
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
var newMetadata InboundContext
if metadata := ContextFrom(ctx); metadata != nil {
newMetadata = *metadata
}
return WithContext(ctx, &newMetadata), &newMetadata
}

View File

@@ -69,7 +69,7 @@ func (s *Box) startOutbounds() error {
}
problemOutbound := outbounds[problemOutboundTag]
if problemOutbound == nil {
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", outboundTags[oCurrent], "]")
return E.New("dependency[", problemOutbound, "] not found for outbound[", outboundTags[oCurrent], "]")
}
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/gofrs/uuid/v5"
"github.com/spf13/cobra"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
var commandGenerate = &cobra.Command{
@@ -21,7 +22,8 @@ var commandGenerate = &cobra.Command{
func init() {
commandGenerate.AddCommand(commandGenerateUUID)
commandGenerate.AddCommand(commandGenerateRandom)
commandGenerate.AddCommand(commandGenerateWireGuardKeyPair)
commandGenerate.AddCommand(commandGenerateRealityKeyPair)
mainCommand.AddCommand(commandGenerate)
}
@@ -90,3 +92,48 @@ func generateUUID() error {
_, err = os.Stdout.WriteString(newUUID.String() + "\n")
return err
}
var commandGenerateWireGuardKeyPair = &cobra.Command{
Use: "wg-keypair",
Short: "Generate WireGuard key pair",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateWireGuardKey()
if err != nil {
log.Fatal(err)
}
},
}
func generateWireGuardKey() error {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return err
}
os.Stdout.WriteString("PrivateKey: " + privateKey.String() + "\n")
os.Stdout.WriteString("PublicKey: " + privateKey.PublicKey().String() + "\n")
return nil
}
var commandGenerateRealityKeyPair = &cobra.Command{
Use: "reality-keypair",
Short: "Generate reality key pair",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateRealityKey()
if err != nil {
log.Fatal(err)
}
},
}
func generateRealityKey() error {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return err
}
publicKey := privateKey.PublicKey()
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]) + "\n")
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]) + "\n")
return nil
}

View File

@@ -1,40 +0,0 @@
package main
import (
"os"
"time"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
)
var flagGenerateTLSKeyPairMonths int
var commandGenerateTLSKeyPair = &cobra.Command{
Use: "tls-keypair <server_name>",
Short: "Generate TLS self sign key pair",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := generateTLSKeyPair(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGenerateTLSKeyPair.Flags().IntVarP(&flagGenerateTLSKeyPairMonths, "months", "m", 1, "Valid months")
commandGenerate.AddCommand(commandGenerateTLSKeyPair)
}
func generateTLSKeyPair(serverName string) error {
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
if err != nil {
return err
}
os.Stdout.WriteString(string(privateKeyPem) + "\n")
os.Stdout.WriteString(string(publicKeyPem) + "\n")
return nil
}

View File

@@ -1,40 +0,0 @@
//go:build go1.20
package main
import (
"crypto/ecdh"
"crypto/rand"
"encoding/base64"
"os"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
)
var commandGenerateVAPIDKeyPair = &cobra.Command{
Use: "vapid-keypair",
Short: "Generate VAPID key pair",
Run: func(cmd *cobra.Command, args []string) {
err := generateVAPIDKeyPair()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGenerate.AddCommand(commandGenerateVAPIDKeyPair)
}
func generateVAPIDKeyPair() error {
privateKey, err := ecdh.P256().GenerateKey(rand.Reader)
if err != nil {
return err
}
publicKey := privateKey.PublicKey()
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey.Bytes()) + "\n")
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey.Bytes()) + "\n")
return nil
}

View File

@@ -1,61 +0,0 @@
package main
import (
"encoding/base64"
"os"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
func init() {
commandGenerate.AddCommand(commandGenerateWireGuardKeyPair)
commandGenerate.AddCommand(commandGenerateRealityKeyPair)
}
var commandGenerateWireGuardKeyPair = &cobra.Command{
Use: "wg-keypair",
Short: "Generate WireGuard key pair",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateWireGuardKey()
if err != nil {
log.Fatal(err)
}
},
}
func generateWireGuardKey() error {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return err
}
os.Stdout.WriteString("PrivateKey: " + privateKey.String() + "\n")
os.Stdout.WriteString("PublicKey: " + privateKey.PublicKey().String() + "\n")
return nil
}
var commandGenerateRealityKeyPair = &cobra.Command{
Use: "reality-keypair",
Short: "Generate reality key pair",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateRealityKey()
if err != nil {
log.Fatal(err)
}
},
}
func generateRealityKey() error {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return err
}
publicKey := privateKey.PublicKey()
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]) + "\n")
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]) + "\n")
return nil
}

View File

@@ -1,7 +1,6 @@
package main
import (
"github.com/getsentry/sentry-go"
"os"
"time"
@@ -16,7 +15,6 @@ var (
configDirectories []string
workingDir string
disableColor bool
enableDebug bool
)
var mainCommand = &cobra.Command{
@@ -29,25 +27,12 @@ func init() {
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
mainCommand.PersistentFlags().BoolVarP(&enableDebug, "debug", "", false, "enable sentry debug mode")
}
func main() {
if err := mainCommand.Execute(); err != nil {
log.Fatal(err)
}
if enableDebug {
err := sentry.Init(sentry.ClientOptions{
Dsn: "",
})
if err != nil {
log.Fatal("sentry.Init: %s", err)
}
defer sentry.Flush(2 * time.Second)
}
}
func preRun(cmd *cobra.Command, args []string) {

View File

@@ -36,7 +36,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
if !destination.IsFqdn() {
return d.dialer.DialContext(ctx, network, destination)
}
ctx, metadata := adapter.ExtendContext(ctx)
ctx, metadata := adapter.AppendContext(ctx)
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
metadata.Destination = destination
metadata.Domain = ""
@@ -61,7 +61,7 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
if !destination.IsFqdn() {
return d.dialer.ListenPacket(ctx, destination)
}
ctx, metadata := adapter.ExtendContext(ctx)
ctx, metadata := adapter.AppendContext(ctx)
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
metadata.Destination = destination
metadata.Domain = ""

View File

@@ -11,34 +11,22 @@ import (
"time"
)
func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour))
if err != nil {
return nil, err
}
certificate, err := tls.X509KeyPair(publicKeyPem, privateKeyPem)
if err != nil {
return nil, err
}
return &certificate, err
}
func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
if timeFunc == nil {
timeFunc = time.Now
}
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return
return nil, err
}
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return
return nil, err
}
template := &x509.Certificate{
SerialNumber: serialNumber,
NotBefore: timeFunc().Add(time.Hour * -1),
NotAfter: expire,
NotAfter: timeFunc().Add(time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
@@ -49,13 +37,17 @@ func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.T
}
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
if err != nil {
return
return nil, err
}
privateDer, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return
return nil, err
}
publicKeyPem = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer})
privateKeyPem = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDer})
return
publicPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer})
privPem := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDer})
keyPair, err := tls.X509KeyPair(publicPem, privPem)
if err != nil {
return nil, err
}
return &keyPair, err
}

View File

@@ -233,7 +233,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
}
if certificate == nil && key == nil && options.Insecure {
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
return GenerateKeyPair(ntp.TimeFuncFromContext(ctx), info.ServerName)
}
} else {
if certificate == nil {

View File

@@ -1,109 +1,3 @@
#### 1.6.0
* Fixes and improvements
Important changes since 1.5:
* Our [Apple tvOS client](/installation/clients/sft) is now available in the App Store 🍎
* Update BBR congestion control for TUIC and Hysteria2 **1**
* Update brutal congestion control for Hysteria2
* Add `brutal_debug` option for Hysteria2
* Update legacy Hysteria protocol **2**
* Add TLS self sign key pair generate command
* Remove [Deprecated Features](/deprecated) by agreement
**1**:
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested.
This update is intended to address the multi-send defects of the old implementation and may introduce new issues.
**2**
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of
the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
#### 1.5.5
* Fix IPv6 `auto_route` for Linux **1**
* Add legacy builds for old Windows and macOS systems **2**
* Fixes and improvements
**1**:
When `auto_route` is enabled and `strict_route` is disabled, the device can now be reached from external IPv6 addresses.
**2**:
Built using Go 1.20, the last version that will run on Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave.
#### 1.6.0-rc.4
* Fixes and improvements
#### 1.6.0-rc.1
* Add legacy builds for old Windows and macOS systems **1**
* Fixes and improvements
**1**:
Built using Go 1.20, the last version that will run on Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave.
#### 1.6.0-beta.4
* Fix IPv6 `auto_route` for Linux **1**
* Fixes and improvements
**1**:
When `auto_route` is enabled and `strict_route` is disabled, the device can now be reached from external IPv6 addresses.
#### 1.5.4
* Fix Clash cache crash on arm32 devices
* Fixes and improvements
#### 1.6.0-beta.3
* Update the legacy Hysteria protocol **1**
* Fixes and improvements
**1**
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of
the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
#### 1.6.0-beta.2
* Add TLS self sign key pair generate command
* Update brutal congestion control for Hysteria2
* Fix Clash cache crash on arm32 devices
* Update golang.org/x/net to v0.17.0
* Fixes and improvements
#### 1.5.3
* Fix compatibility with Android 14
* Fixes and improvements
#### 1.6.0-beta.1
* Fixes and improvements
#### 1.6.0-alpha.5
* Fix compatibility with Android 14
* Update BBR congestion control for TUIC and Hysteria2 **1**
* Fixes and improvements
**1**:
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested.
This update is intended to fix a memory leak flaw in the new implementation introduced in 1.6.0-alpha.1 and may
introduce new issues.
#### 1.6.0-alpha.4
* Add `brutal_debug` option for Hysteria2

View File

@@ -82,3 +82,49 @@ Both if empty.
| none | / |
| 2022 methods | `sing-box generate rand --base64 <Key Length>` |
| other methods | any string |
### Listen Fields
#### listen
==Required==
Listen address.
#### listen_port
==Required==
Listen port.
#### tcp_fast_open
Enable tcp fast open for listener.
#### sniff
Enable sniffing.
See [Protocol Sniff](/configuration/route/sniff/) for details.
#### sniff_override_destination
Override the connection destination address with the sniffed domain.
If the domain name is invalid (like tor), this will not work.
#### domain_strategy
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
If set, the requested domain name will be resolved to IP before routing.
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
#### udp_timeout
UDP NAT expiration time in seconds, default is 300 (5 minutes).
#### proxy_protocol
Parse [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.

View File

@@ -1,6 +1,8 @@
# Deprecated Feature List
The following features will be marked deprecated in 1.5.0 and removed entirely in 1.6.0.
### 1.6.0
The following features were marked deprecated in 1.5.0 and removed entirely in 1.6.0.
#### ShadowsocksR

View File

@@ -1,18 +1,16 @@
package cachefile
import (
"errors"
"net/netip"
"os"
"strings"
"sync"
"time"
"github.com/sagernet/bbolt"
bboltErrors "github.com/sagernet/bbolt/errors"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"go.etcd.io/bbolt"
)
var (
@@ -44,25 +42,13 @@ type CacheFile struct {
func Open(path string, cacheID string) (*CacheFile, error) {
const fileMode = 0o666
options := bbolt.Options{Timeout: time.Second}
var (
db *bbolt.DB
err error
)
for i := 0; i < 10; i++ {
db, err = bbolt.Open(path, fileMode, &options)
if err == nil {
db, err := bbolt.Open(path, fileMode, &options)
switch err {
case bbolt.ErrInvalid, bbolt.ErrChecksum, bbolt.ErrVersionMismatch:
if err = os.Remove(path); err != nil {
break
}
if errors.Is(err, bboltErrors.ErrTimeout) {
continue
}
if E.IsMulti(err, bboltErrors.ErrInvalid, bboltErrors.ErrChecksum, bboltErrors.ErrVersionMismatch) {
rmErr := os.Remove(path)
if rmErr != nil {
return nil, err
}
}
time.Sleep(100 * time.Millisecond)
db, err = bbolt.Open(path, 0o666, &options)
}
if err != nil {
return nil, err

View File

@@ -5,10 +5,11 @@ import (
"os"
"time"
"github.com/sagernet/bbolt"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
"go.etcd.io/bbolt"
)
const fakeipBucketPrefix = "fakeip_"

View File

@@ -1,234 +0,0 @@
//go:build android
package libbox
import (
"archive/zip"
"bytes"
"debug/buildinfo"
"io"
"runtime/debug"
"strings"
"github.com/sagernet/sing/common"
)
const (
androidVPNCoreTypeOpenVPN = "OpenVPN"
androidVPNCoreTypeShadowsocks = "Shadowsocks"
androidVPNCoreTypeClash = "Clash"
androidVPNCoreTypeV2Ray = "V2Ray"
androidVPNCoreTypeWireGuard = "WireGuard"
androidVPNCoreTypeSingBox = "sing-box"
androidVPNCoreTypeUnknown = "Unknown"
)
type AndroidVPNType struct {
CoreType string
CorePath string
GoVersion string
}
func ReadAndroidVPNType(publicSourceDirList StringIterator) (*AndroidVPNType, error) {
apkPathList := iteratorToArray[string](publicSourceDirList)
var lastError error
for _, apkPath := range apkPathList {
androidVPNType, err := readAndroidVPNType(apkPath)
if androidVPNType == nil {
if err != nil {
lastError = err
}
continue
}
return androidVPNType, nil
}
return nil, lastError
}
func readAndroidVPNType(publicSourceDir string) (*AndroidVPNType, error) {
reader, err := zip.OpenReader(publicSourceDir)
if err != nil {
return nil, err
}
defer reader.Close()
var lastError error
for _, file := range reader.File {
if !strings.HasPrefix(file.Name, "lib/") {
continue
}
vpnType, err := readAndroidVPNTypeEntry(file)
if err != nil {
lastError = err
continue
}
return vpnType, nil
}
for _, file := range reader.File {
if !strings.HasPrefix(file.Name, "lib/") {
continue
}
if strings.Contains(file.Name, androidVPNCoreTypeOpenVPN) || strings.Contains(file.Name, "ovpn") {
return &AndroidVPNType{CoreType: androidVPNCoreTypeOpenVPN}, nil
}
if strings.Contains(file.Name, androidVPNCoreTypeShadowsocks) {
return &AndroidVPNType{CoreType: androidVPNCoreTypeShadowsocks}, nil
}
}
return nil, lastError
}
func readAndroidVPNTypeEntry(zipFile *zip.File) (*AndroidVPNType, error) {
readCloser, err := zipFile.Open()
if err != nil {
return nil, err
}
libContent := make([]byte, zipFile.UncompressedSize64)
_, err = io.ReadFull(readCloser, libContent)
readCloser.Close()
if err != nil {
return nil, err
}
buildInfo, err := buildinfo.Read(bytes.NewReader(libContent))
if err != nil {
return nil, err
}
var vpnType AndroidVPNType
vpnType.GoVersion = buildInfo.GoVersion
if !strings.HasPrefix(vpnType.GoVersion, "go") {
vpnType.GoVersion = "obfuscated"
} else {
vpnType.GoVersion = vpnType.GoVersion[2:]
}
vpnType.CoreType = androidVPNCoreTypeUnknown
if len(buildInfo.Deps) == 0 {
vpnType.CoreType = "obfuscated"
return &vpnType, nil
}
dependencies := make(map[string]bool)
dependencies[buildInfo.Path] = true
for _, module := range buildInfo.Deps {
dependencies[module.Path] = true
if module.Replace != nil {
dependencies[module.Replace.Path] = true
}
}
for dependency := range dependencies {
pkgType, loaded := determinePkgType(dependency)
if loaded {
vpnType.CoreType = pkgType
}
}
if vpnType.CoreType == androidVPNCoreTypeUnknown {
for dependency := range dependencies {
pkgType, loaded := determinePkgTypeSecondary(dependency)
if loaded {
vpnType.CoreType = pkgType
return &vpnType, nil
}
}
}
if vpnType.CoreType != androidVPNCoreTypeUnknown {
vpnType.CorePath, _ = determineCorePath(buildInfo, vpnType.CoreType)
return &vpnType, nil
}
if dependencies["github.com/golang/protobuf"] && dependencies["github.com/v2fly/ss-bloomring"] {
vpnType.CoreType = androidVPNCoreTypeV2Ray
return &vpnType, nil
}
return &vpnType, nil
}
func determinePkgType(pkgName string) (string, bool) {
pkgNameLower := strings.ToLower(pkgName)
if strings.Contains(pkgNameLower, "clash") {
return androidVPNCoreTypeClash, true
}
if strings.Contains(pkgNameLower, "v2ray") || strings.Contains(pkgNameLower, "xray") {
return androidVPNCoreTypeV2Ray, true
}
if strings.Contains(pkgNameLower, "sing-box") {
return androidVPNCoreTypeSingBox, true
}
return "", false
}
func determinePkgTypeSecondary(pkgName string) (string, bool) {
pkgNameLower := strings.ToLower(pkgName)
if strings.Contains(pkgNameLower, "wireguard") {
return androidVPNCoreTypeWireGuard, true
}
return "", false
}
func determineCorePath(pkgInfo *buildinfo.BuildInfo, pkgType string) (string, bool) {
switch pkgType {
case androidVPNCoreTypeClash:
return determineCorePathForPkgs(pkgInfo, []string{"github.com/Dreamacro/clash"}, []string{"clash"})
case androidVPNCoreTypeV2Ray:
if v2rayVersion, loaded := determineCorePathForPkgs(pkgInfo, []string{
"github.com/v2fly/v2ray-core",
"github.com/v2fly/v2ray-core/v4",
"github.com/v2fly/v2ray-core/v5",
}, []string{
"v2ray",
}); loaded {
return v2rayVersion, true
}
if xrayVersion, loaded := determineCorePathForPkgs(pkgInfo, []string{
"github.com/xtls/xray-core",
}, []string{
"xray",
}); loaded {
return xrayVersion, true
}
return "", false
case androidVPNCoreTypeSingBox:
return determineCorePathForPkgs(pkgInfo, []string{"github.com/sagernet/sing-box"}, []string{"sing-box"})
case androidVPNCoreTypeWireGuard:
return determineCorePathForPkgs(pkgInfo, []string{"golang.zx2c4.com/wireguard"}, []string{"wireguard"})
default:
return "", false
}
}
func determineCorePathForPkgs(pkgInfo *buildinfo.BuildInfo, pkgs []string, names []string) (string, bool) {
for _, pkg := range pkgs {
if pkgInfo.Path == pkg {
return pkg, true
}
strictDependency := common.Find(pkgInfo.Deps, func(module *debug.Module) bool {
return module.Path == pkg
})
if strictDependency != nil {
if isValidVersion(strictDependency.Version) {
return strictDependency.Path + " " + strictDependency.Version, true
} else {
return strictDependency.Path, true
}
}
}
for _, name := range names {
if strings.Contains(pkgInfo.Path, name) {
return pkgInfo.Path, true
}
looseDependency := common.Find(pkgInfo.Deps, func(module *debug.Module) bool {
return strings.Contains(module.Path, name) || (module.Replace != nil && strings.Contains(module.Replace.Path, name))
})
if looseDependency != nil {
return looseDependency.Path, true
}
}
return "", false
}
func isValidVersion(version string) bool {
if version == "(devel)" {
return false
}
if strings.Contains(version, "v0.0.0") {
return false
}
return true
}

View File

@@ -80,7 +80,6 @@ func (s *BoxService) Sleep() {
func (s *BoxService) Wake() {
s.pauseManager.DeviceWake()
_ = s.instance.Router().ResetNetwork()
}
var _ platform.Interface = (*platformInterfaceWrapper)(nil)

29
go.mod
View File

@@ -5,14 +5,14 @@ go 1.20
require (
berty.tech/go-libtor v1.0.385
github.com/caddyserver/certmagic v0.19.2
github.com/cloudflare/circl v1.3.5
github.com/cloudflare/circl v1.3.3
github.com/cretz/bine v0.2.0
github.com/fsnotify/fsnotify v1.7.0
github.com/fsnotify/fsnotify v1.6.0
github.com/go-chi/chi/v5 v5.0.10
github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.3
github.com/gofrs/uuid/v5 v5.0.0
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a
github.com/libdns/alidns v1.0.3
github.com/libdns/cloudflare v0.1.0
github.com/logrusorgru/aurora v2.0.3+incompatible
@@ -20,20 +20,19 @@ require (
github.com/miekg/dns v1.1.56
github.com/ooni/go-libtor v1.1.8
github.com/oschwald/maxminddb-golang v1.12.0
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a
github.com/sagernet/gomobile v0.0.0-20230915142329-c6740b6d2950
github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028
github.com/sagernet/sing v0.2.14-0.20231008040725-e690cb9a7ad2
github.com/sagernet/sing-dns v0.1.10
github.com/sagernet/sing-mux v0.1.3
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6
github.com/sagernet/sing-quic v0.1.3-0.20231008043106-a107947d1ed5
github.com/sagernet/sing-shadowsocks v0.2.5
github.com/sagernet/sing-shadowsocks2 v0.1.4
github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6
github.com/sagernet/sing-tun v0.1.16-0.20231006112722-19cc8b9e81aa
github.com/sagernet/sing-vmess v0.1.8
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
@@ -42,13 +41,15 @@ require (
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.4
go.etcd.io/bbolt v1.3.7
go.uber.org/zap v1.26.0
go4.org/netipx v0.0.0-20230824141953-6213f710f925
golang.org/x/crypto v0.14.0
golang.org/x/net v0.17.0
golang.org/x/exp v0.0.0-20231005195138-3e424a577f31
golang.org/x/net v0.16.0
golang.org/x/sys v0.13.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/grpc v1.59.0
google.golang.org/grpc v1.58.2
google.golang.org/protobuf v1.31.0
howett.net/plist v1.0.0
)
@@ -59,7 +60,6 @@ require (
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/getsentry/sentry-go v0.25.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.3 // indirect
@@ -68,7 +68,7 @@ require (
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.16.0 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/libdns/libdns v0.2.1 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
@@ -85,12 +85,11 @@ require (
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.2.1 // indirect

58
go.sum
View File

@@ -9,8 +9,8 @@ github.com/caddyserver/certmagic v0.19.2/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.3.5 h1:g+wWynZqVALYAlpSQFAa7TscDnUK8mKYtrxMpw6AUKo=
github.com/cloudflare/circl v1.3.5/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
@@ -18,10 +18,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/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI=
github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
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=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
@@ -49,15 +47,14 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c h1:PgxFEySCI41sH0mB7/2XswdXbUykQsRUGod8Rn+NubM=
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ=
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@@ -95,8 +92,6 @@ github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1
github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a h1:wZHruBxZCsQLXHAozWpnJBL3wJ/XufDpz0qKtgpSnA4=
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=
@@ -113,22 +108,22 @@ 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.16-0.20231021090846-8002db54c028 h1:6GbQt7SC9y5Imrq5jDMbXDSaNiMhJ8KBjhjtQRuqQvE=
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028/go.mod h1:AhNEHu0GXrpqkuzvTwvC8+j2cQUU/dh+zLEmq4C99pg=
github.com/sagernet/sing v0.2.14-0.20231008040725-e690cb9a7ad2 h1:fbrU4QJwN6XLLPDwSSLgyUru2niwJRvkpNuHsH51vhs=
github.com/sagernet/sing v0.2.14-0.20231008040725-e690cb9a7ad2/go.mod h1:AhNEHu0GXrpqkuzvTwvC8+j2cQUU/dh+zLEmq4C99pg=
github.com/sagernet/sing-dns v0.1.10 h1:iIU7nRBlUYj+fF2TaktGIvRiTFFrHwSMedLQsvlTZCI=
github.com/sagernet/sing-dns v0.1.10/go.mod h1:vtUimtf7Nq9EdvD5WTpfCr69KL1M7bcgOVKiYBiAY/c=
github.com/sagernet/sing-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6 h1:w+TUbIZKZFSdf/AUa/y33kY9xaLeNGz/tBNcNhqpqfg=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6/go.mod h1:1M7xP4802K9Kz6BQ7LlA7UeCapWvWlH1Htmk2bAqkWc=
github.com/sagernet/sing-quic v0.1.3-0.20231008043106-a107947d1ed5 h1:ENdA0N/fjXoK27FNIgJyFu4EkiaFAAkO5QNRCCq1LmE=
github.com/sagernet/sing-quic v0.1.3-0.20231008043106-a107947d1ed5/go.mod h1:1M7xP4802K9Kz6BQ7LlA7UeCapWvWlH1Htmk2bAqkWc=
github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY=
github.com/sagernet/sing-shadowsocks v0.2.5/go.mod h1:MGWGkcU2xW2G2mfArT9/QqpVLOGU+dBaahZCtPHdt7A=
github.com/sagernet/sing-shadowsocks2 v0.1.4 h1:vht2M8t3m5DTgXR2j24KbYOygG5aOp+MUhpQnAux728=
github.com/sagernet/sing-shadowsocks2 v0.1.4/go.mod h1:Mgdee99NxxNd5Zld3ixIs18yVs4x2dI2VTDDE1N14Wc=
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.17-0.20231026060825-efd9884154a6 h1:4yEXBqQoUgXj7qPSLD6lr+z9/KfsvixO9JUA2i5xnM8=
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6/go.mod h1:w2+S+uWE94E/pQWSDdDdMIjwAEb645kuGPunr6ZllUg=
github.com/sagernet/sing-tun v0.1.16-0.20231006112722-19cc8b9e81aa h1:lQVFeYJ916CXunKB3JrsOQqCtT16OAitMXw8Z0lRNvY=
github.com/sagernet/sing-tun v0.1.16-0.20231006112722-19cc8b9e81aa/go.mod h1:d5kKDDW3I8Tr1OSWNaHXzrnsg1LbOx9sYVx83cHPhm8=
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
@@ -163,6 +158,8 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
@@ -174,15 +171,15 @@ golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaE
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/exp v0.0.0-20231005195138-3e424a577f31 h1:9k5exFQKQglLo+RoP+4zMjOFE14P6+vyR0baDAi0Rcs=
golang.org/x/exp v0.0.0-20231005195138-3e424a577f31/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -190,6 +187,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
@@ -203,15 +201,15 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I=
google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=

View File

@@ -4,38 +4,104 @@ package inbound
import (
"context"
"net"
"sync"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/humanize"
"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-quic/hysteria"
"github.com/sagernet/sing-box/transport/hysteria"
"github.com/sagernet/sing-quic"
hyCC "github.com/sagernet/sing-quic/hysteria2/congestion"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"golang.org/x/exp/slices"
)
var _ adapter.Inbound = (*Hysteria)(nil)
type Hysteria struct {
myInboundAdapter
quicConfig *quic.Config
tlsConfig tls.ServerConfig
service *hysteria.Service[int]
userNameList []string
authKey []string
authUser []string
xplusKey []byte
sendBPS uint64
recvBPS uint64
listener qtls.Listener
udpAccess sync.RWMutex
udpSessionId uint32
udpSessions map[uint32]chan *hysteria.UDPMessage
udpDefragger hysteria.Defragger
}
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (*Hysteria, error) {
options.UDPFragmentDefault = true
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
quicConfig := &quic.Config{
InitialStreamReceiveWindow: options.ReceiveWindowConn,
MaxStreamReceiveWindow: options.ReceiveWindowConn,
InitialConnectionReceiveWindow: options.ReceiveWindowClient,
MaxConnectionReceiveWindow: options.ReceiveWindowClient,
MaxIncomingStreams: int64(options.MaxConnClient),
KeepAlivePeriod: hysteria.KeepAlivePeriod,
DisablePathMTUDiscovery: options.DisableMTUDiscovery || !(C.IsLinux || C.IsWindows),
EnableDatagrams: true,
}
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
if options.ReceiveWindowConn == 0 {
quicConfig.InitialStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
quicConfig.MaxStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
}
if options.ReceiveWindowClient == 0 {
quicConfig.InitialConnectionReceiveWindow = hysteria.DefaultConnectionReceiveWindow
quicConfig.MaxConnectionReceiveWindow = hysteria.DefaultConnectionReceiveWindow
}
if quicConfig.MaxIncomingStreams == 0 {
quicConfig.MaxIncomingStreams = hysteria.DefaultMaxIncomingStreams
}
authKey := common.Map(options.Users, func(it option.HysteriaUser) string {
if len(it.Auth) > 0 {
return string(it.Auth)
} else {
return it.AuthString
}
})
authUser := common.Map(options.Users, func(it option.HysteriaUser) string {
return it.Name
})
var xplus []byte
if options.Obfs != "" {
xplus = []byte(options.Obfs)
}
var up, down uint64
if len(options.Up) > 0 {
up = hysteria.StringToBps(options.Up)
if up == 0 {
return nil, E.New("invalid up speed format: ", options.Up)
}
} else {
up = uint64(options.UpMbps) * hysteria.MbpsToBps
}
if len(options.Down) > 0 {
down = hysteria.StringToBps(options.Down)
if down == 0 {
return nil, E.New("invalid down speed format: ", options.Down)
}
} else {
down = uint64(options.DownMbps) * hysteria.MbpsToBps
}
if up < hysteria.MinSpeedBPS {
return nil, E.New("invalid up speed")
}
if down < hysteria.MinSpeedBPS {
return nil, E.New("invalid down speed")
}
inbound := &Hysteria{
myInboundAdapter: myInboundAdapter{
@@ -47,108 +113,224 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
tag: tag,
listenOptions: options.ListenOptions,
},
tlsConfig: tlsConfig,
quicConfig: quicConfig,
authKey: authKey,
authUser: authUser,
xplusKey: xplus,
sendBPS: up,
recvBPS: down,
udpSessions: make(map[uint32]chan *hysteria.UDPMessage),
}
var sendBps, receiveBps uint64
if len(options.Up) > 0 {
sendBps, err = humanize.ParseBytes(options.Up)
if err != nil {
return nil, E.Cause(err, "invalid up speed format: ", options.Up)
}
} else {
sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}
if len(options.Down) > 0 {
receiveBps, err = humanize.ParseBytes(options.Down)
if receiveBps == 0 {
return nil, E.New("invalid down speed format: ", options.Down)
}
} else {
receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps
if len(options.TLS.ALPN) == 0 {
options.TLS.ALPN = []string{hysteria.DefaultALPN}
}
service, err := hysteria.NewService[int](hysteria.ServiceOptions{
Context: ctx,
Logger: logger,
SendBPS: sendBps,
ReceiveBPS: receiveBps,
XPlusPassword: options.Obfs,
TLSConfig: tlsConfig,
Handler: adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, nil),
// Legacy options
ConnReceiveWindow: options.ReceiveWindowConn,
StreamReceiveWindow: options.ReceiveWindowClient,
MaxIncomingStreams: int64(options.MaxConnClient),
DisableMTUDiscovery: options.DisableMTUDiscovery,
})
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
userList := make([]int, 0, len(options.Users))
userNameList := make([]string, 0, len(options.Users))
userPasswordList := make([]string, 0, len(options.Users))
for index, user := range options.Users {
userList = append(userList, index)
userNameList = append(userNameList, user.Name)
var password string
if user.AuthString != "" {
password = user.AuthString
} else {
password = string(user.Auth)
}
userPasswordList = append(userPasswordList, password)
}
service.UpdateUsers(userList, userPasswordList)
inbound.service = service
inbound.userNameList = userNameList
inbound.tlsConfig = tlsConfig
return inbound, nil
}
func (h *Hysteria) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
ctx = log.ContextWithNewID(ctx)
metadata = h.createMetadata(conn, metadata)
userID, _ := auth.UserFromContext[int](ctx)
if userName := h.userNameList[userID]; 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)
}
func (h *Hysteria) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
ctx = log.ContextWithNewID(ctx)
metadata = h.createPacketMetadata(conn, metadata)
userID, _ := auth.UserFromContext[int](ctx)
if userName := h.userNameList[userID]; userName != "" {
metadata.User = userName
h.logger.InfoContext(ctx, "[", userName, "] inbound packet connection to ", metadata.Destination)
} else {
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
}
return h.router.RoutePacketConnection(ctx, conn, metadata)
}
func (h *Hysteria) 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.service.Start(packetConn)
if len(h.xplusKey) > 0 {
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
}
err = h.tlsConfig.Start()
if err != nil {
return err
}
listener, err := qtls.Listen(packetConn, h.tlsConfig, h.quicConfig)
if err != nil {
return err
}
h.listener = listener
h.logger.Info("udp server started at ", listener.Addr())
go h.acceptLoop()
return nil
}
func (h *Hysteria) acceptLoop() {
for {
ctx := log.ContextWithNewID(h.ctx)
conn, err := h.listener.Accept(ctx)
if err != nil {
return
}
go func() {
hErr := h.accept(ctx, conn)
if hErr != nil {
conn.CloseWithError(0, "")
NewError(h.logger, ctx, E.Cause(hErr, "process connection from ", conn.RemoteAddr()))
}
}()
}
}
func (h *Hysteria) accept(ctx context.Context, conn quic.Connection) error {
controlStream, err := conn.AcceptStream(ctx)
if err != nil {
return err
}
clientHello, err := hysteria.ReadClientHello(controlStream)
if err != nil {
return err
}
if len(h.authKey) > 0 {
userIndex := slices.Index(h.authKey, string(clientHello.Auth))
if userIndex == -1 {
err = hysteria.WriteServerHello(controlStream, hysteria.ServerHello{
Message: "wrong password",
})
return E.Errors(E.New("wrong password: ", string(clientHello.Auth)), err)
}
user := h.authUser[userIndex]
if user == "" {
user = F.ToString(userIndex)
} else {
ctx = auth.ContextWithUser(ctx, user)
}
h.logger.InfoContext(ctx, "[", user, "] inbound connection from ", conn.RemoteAddr())
} else {
h.logger.InfoContext(ctx, "inbound connection from ", conn.RemoteAddr())
}
h.logger.DebugContext(ctx, "peer send speed: ", clientHello.SendBPS/1024/1024, " MBps, peer recv speed: ", clientHello.RecvBPS/1024/1024, " MBps")
if clientHello.SendBPS == 0 || clientHello.RecvBPS == 0 {
return E.New("invalid rate from client")
}
serverSendBPS, serverRecvBPS := clientHello.RecvBPS, clientHello.SendBPS
if h.sendBPS > 0 && serverSendBPS > h.sendBPS {
serverSendBPS = h.sendBPS
}
if h.recvBPS > 0 && serverRecvBPS > h.recvBPS {
serverRecvBPS = h.recvBPS
}
err = hysteria.WriteServerHello(controlStream, hysteria.ServerHello{
OK: true,
SendBPS: serverSendBPS,
RecvBPS: serverRecvBPS,
})
if err != nil {
return err
}
conn.SetCongestionControl(hyCC.NewBrutalSender(serverSendBPS, false, nil))
go h.udpRecvLoop(conn)
for {
var stream quic.Stream
stream, err = conn.AcceptStream(ctx)
if err != nil {
return err
}
go func() {
hErr := h.acceptStream(ctx, conn /*&hysteria.StreamWrapper{Stream: stream}*/, stream)
if hErr != nil {
stream.Close()
NewError(h.logger, ctx, E.Cause(hErr, "process stream from ", conn.RemoteAddr()))
}
}()
}
}
func (h *Hysteria) udpRecvLoop(conn quic.Connection) {
for {
packet, err := conn.ReceiveMessage(h.ctx)
if err != nil {
return
}
message, err := hysteria.ParseUDPMessage(packet)
if err != nil {
h.logger.Error("parse udp message: ", err)
continue
}
dfMsg := h.udpDefragger.Feed(message)
if dfMsg == nil {
continue
}
h.udpAccess.RLock()
ch, ok := h.udpSessions[dfMsg.SessionID]
if ok {
select {
case ch <- dfMsg:
// OK
default:
// Silently drop the message when the channel is full
}
}
h.udpAccess.RUnlock()
}
}
func (h *Hysteria) acceptStream(ctx context.Context, conn quic.Connection, stream quic.Stream) error {
request, err := hysteria.ReadClientRequest(stream)
if err != nil {
return err
}
var metadata adapter.InboundContext
metadata.Inbound = h.tag
metadata.InboundType = C.TypeHysteria
metadata.InboundOptions = h.listenOptions.InboundOptions
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
metadata.Destination = M.ParseSocksaddrHostPort(request.Host, request.Port).Unwrap()
metadata.User, _ = auth.UserFromContext[string](ctx)
if !request.UDP {
err = hysteria.WriteServerResponse(stream, hysteria.ServerResponse{
OK: true,
})
if err != nil {
return err
}
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
return h.router.RouteConnection(ctx, hysteria.NewConn(stream, metadata.Destination, false), metadata)
} else {
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
var id uint32
h.udpAccess.Lock()
id = h.udpSessionId
nCh := make(chan *hysteria.UDPMessage, 1024)
h.udpSessions[id] = nCh
h.udpSessionId += 1
h.udpAccess.Unlock()
err = hysteria.WriteServerResponse(stream, hysteria.ServerResponse{
OK: true,
UDPSessionID: id,
})
if err != nil {
return err
}
packetConn := hysteria.NewPacketConn(conn, stream, id, metadata.Destination, nCh, common.Closer(func() error {
h.udpAccess.Lock()
if ch, ok := h.udpSessions[id]; ok {
close(ch)
delete(h.udpSessions, id)
}
h.udpAccess.Unlock()
return nil
}))
go packetConn.Hold()
return h.router.RoutePacketConnection(ctx, packetConn, metadata)
}
}
func (h *Hysteria) Close() error {
h.udpAccess.Lock()
for _, session := range h.udpSessions {
close(session)
}
h.udpSessions = make(map[uint32]chan *hysteria.UDPMessage)
h.udpAccess.Unlock()
return common.Close(
&h.myInboundAdapter,
h.listener,
h.tlsConfig,
common.PtrOrNil(h.service),
)
}

View File

@@ -14,7 +14,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-quic/hysteria"
"github.com/sagernet/sing-box/transport/hysteria"
"github.com/sagernet/sing-quic/hysteria2"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
@@ -32,7 +32,6 @@ type Hysteria2 struct {
}
func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (*Hysteria2, error) {
options.UDPFragmentDefault = true
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}

View File

@@ -49,7 +49,6 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
tag: tag,
listenOptions: options.ListenOptions,
},
tlsConfig: tlsConfig,
}
service, err := tuic.NewService[int](tuic.ServiceOptions{
Context: ctx,

View File

@@ -73,14 +73,14 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
tunOptions: tun.Options{
Name: options.InterfaceName,
MTU: tunMTU,
Inet4Address: options.Inet4Address,
Inet6Address: options.Inet6Address,
Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
AutoRoute: options.AutoRoute,
StrictRoute: options.StrictRoute,
IncludeInterface: options.IncludeInterface,
ExcludeInterface: options.ExcludeInterface,
Inet4RouteAddress: options.Inet4RouteAddress,
Inet6RouteAddress: options.Inet6RouteAddress,
Inet4RouteAddress: common.Map(options.Inet4RouteAddress, option.ListenPrefix.Build),
Inet6RouteAddress: common.Map(options.Inet6RouteAddress, option.ListenPrefix.Build),
IncludeUID: includeUID,
ExcludeUID: excludeUID,
IncludeAndroidUser: options.IncludeAndroidUser,

View File

@@ -1,7 +1,5 @@
package option
import "net/netip"
type DNSOptions struct {
Servers []DNSServerOptions `json:"servers,omitempty"`
Rules []DNSRule `json:"rules,omitempty"`
@@ -30,6 +28,6 @@ type DNSClientOptions struct {
type DNSFakeIPOptions struct {
Enabled bool `json:"enabled,omitempty"`
Inet4Range *netip.Prefix `json:"inet4_range,omitempty"`
Inet6Range *netip.Prefix `json:"inet6_range,omitempty"`
Inet4Range *ListenPrefix `json:"inet4_range,omitempty"`
Inet6Range *ListenPrefix `json:"inet6_range,omitempty"`
}

View File

@@ -27,9 +27,9 @@ type SocksOutboundOptions struct {
type HTTPOutboundOptions struct {
DialerOptions
ServerOptions
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
TLS *OutboundTLSOptions `json:"tls,omitempty"`
Path string `json:"path,omitempty"`
Headers HTTPHeader `json:"headers,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
TLS *OutboundTLSOptions `json:"tls,omitempty"`
Path string `json:"path,omitempty"`
Headers map[string]Listable[string] `json:"headers,omitempty"`
}

View File

@@ -1,16 +1,14 @@
package option
import "net/netip"
type TunInboundOptions struct {
InterfaceName string `json:"interface_name,omitempty"`
MTU uint32 `json:"mtu,omitempty"`
Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"`
Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"`
Inet4Address Listable[ListenPrefix] `json:"inet4_address,omitempty"`
Inet6Address Listable[ListenPrefix] `json:"inet6_address,omitempty"`
AutoRoute bool `json:"auto_route,omitempty"`
StrictRoute bool `json:"strict_route,omitempty"`
Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"`
Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"`
Inet4RouteAddress Listable[ListenPrefix] `json:"inet4_route_address,omitempty"`
Inet6RouteAddress Listable[ListenPrefix] `json:"inet6_route_address,omitempty"`
IncludeInterface Listable[string] `json:"include_interface,omitempty"`
ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"`
IncludeUID Listable[uint32] `json:"include_uid,omitempty"`

View File

@@ -1,9 +1,7 @@
package option
import (
"net/http"
"net/netip"
"strings"
"time"
"github.com/sagernet/sing-box/common/json"
@@ -51,60 +49,60 @@ func (a *ListenAddress) Build() netip.Addr {
return (netip.Addr)(*a)
}
type NetworkList string
type NetworkList []string
func (v *NetworkList) UnmarshalJSON(content []byte) error {
var networkList []string
err := json.Unmarshal(content, &networkList)
var (
networkList []string
err error
)
if len(content) > 0 && content[0] != '[' {
networkList = make([]string, 1)
err = json.Unmarshal(content, &networkList[0])
} else {
err = json.Unmarshal(content, &networkList)
}
if err != nil {
var networkItem string
err = json.Unmarshal(content, &networkItem)
if err != nil {
return err
}
networkList = []string{networkItem}
return err
}
for _, networkName := range networkList {
switch networkName {
case N.NetworkTCP, N.NetworkUDP:
break
default:
return E.New("unknown network: " + networkName)
return E.Extend(N.ErrUnknownNetwork, networkName)
}
}
*v = NetworkList(strings.Join(networkList, "\n"))
*v = networkList
return nil
}
func (v NetworkList) Build() []string {
if v == "" {
if len(v) == 0 {
return []string{N.NetworkTCP, N.NetworkUDP}
}
return strings.Split(string(v), "\n")
return v
}
type Listable[T comparable] []T
func (l Listable[T]) MarshalJSON() ([]byte, error) {
arrayList := []T(l)
if len(arrayList) == 1 {
return json.Marshal(arrayList[0])
if len(l) == 1 {
return json.Marshal(l[0])
}
return json.Marshal(arrayList)
return json.Marshal([]T(l))
}
func (l *Listable[T]) UnmarshalJSON(content []byte) error {
err := json.Unmarshal(content, (*[]T)(l))
if err == nil {
return nil
if len(content) > 0 && content[0] != '[' {
var element T
err := json.Unmarshal(content, &element)
if err == nil {
*l = []T{element}
return nil
}
}
var singleItem T
newError := json.Unmarshal(content, &singleItem)
if newError != nil {
return E.Errors(err, newError)
}
*l = []T{singleItem}
return nil
return json.Unmarshal(content, (*[]T)(l))
}
type DomainStrategy dns.DomainStrategy
@@ -172,6 +170,34 @@ func (d *Duration) UnmarshalJSON(bytes []byte) error {
return nil
}
type ListenPrefix netip.Prefix
func (p ListenPrefix) MarshalJSON() ([]byte, error) {
prefix := netip.Prefix(p)
if !prefix.IsValid() {
return json.Marshal(nil)
}
return json.Marshal(prefix.String())
}
func (p *ListenPrefix) UnmarshalJSON(bytes []byte) error {
var value string
err := json.Unmarshal(bytes, &value)
if err != nil {
return err
}
prefix, err := netip.ParsePrefix(value)
if err != nil {
return err
}
*p = ListenPrefix(prefix)
return nil
}
func (p ListenPrefix) Build() netip.Prefix {
return netip.Prefix(p)
}
type DNSQueryType uint16
func (t DNSQueryType) MarshalJSON() ([]byte, error) {
@@ -208,15 +234,3 @@ func DNSQueryTypeToString(queryType uint16) string {
}
return F.ToString(queryType)
}
type HTTPHeader map[string]Listable[string]
func (h HTTPHeader) Build() http.Header {
header := make(http.Header)
for name, values := range h {
for _, value := range values {
header.Add(name, value)
}
}
return header
}

View File

@@ -61,19 +61,19 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error {
}
type V2RayHTTPOptions struct {
Host Listable[string] `json:"host,omitempty"`
Path string `json:"path,omitempty"`
Method string `json:"method,omitempty"`
Headers HTTPHeader `json:"headers,omitempty"`
IdleTimeout Duration `json:"idle_timeout,omitempty"`
PingTimeout Duration `json:"ping_timeout,omitempty"`
Host Listable[string] `json:"host,omitempty"`
Path string `json:"path,omitempty"`
Method string `json:"method,omitempty"`
Headers map[string]Listable[string] `json:"headers,omitempty"`
IdleTimeout Duration `json:"idle_timeout,omitempty"`
PingTimeout Duration `json:"ping_timeout,omitempty"`
}
type V2RayWebsocketOptions struct {
Path string `json:"path,omitempty"`
Headers HTTPHeader `json:"headers,omitempty"`
MaxEarlyData uint32 `json:"max_early_data,omitempty"`
EarlyDataHeaderName string `json:"early_data_header_name,omitempty"`
Path string `json:"path,omitempty"`
Headers map[string]Listable[string] `json:"headers,omitempty"`
MaxEarlyData uint32 `json:"max_early_data,omitempty"`
EarlyDataHeaderName string `json:"early_data_header_name,omitempty"`
}
type V2RayQUICOptions struct{}

View File

@@ -1,12 +1,10 @@
package option
import "net/netip"
type WireGuardOutboundOptions struct {
DialerOptions
SystemInterface bool `json:"system_interface,omitempty"`
InterfaceName string `json:"interface_name,omitempty"`
LocalAddress Listable[netip.Prefix] `json:"local_address"`
LocalAddress Listable[ListenPrefix] `json:"local_address"`
PrivateKey string `json:"private_key"`
Peers []WireGuardPeer `json:"peers,omitempty"`
ServerOptions

View File

@@ -11,13 +11,11 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/canceler"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
@@ -76,7 +74,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
return CopyEarlyConn(ctx, conn, outConn)
}
func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error {
func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
ctx = adapter.WithContext(ctx, &metadata)
var outConn net.Conn
var err error
@@ -84,7 +82,7 @@ func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dial
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
} else if metadata.Destination.IsFqdn() {
var destinationAddresses []netip.Addr
destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy)
destinationAddresses, err = router.LookupDefault(ctx, metadata.Destination.Fqdn)
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
@@ -120,10 +118,7 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
return err
}
if destinationAddress.IsValid() {
if metadata.Destination.IsFqdn() {
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
}
if natConn, loaded := common.Cast[*bufio.NATPacketConn](conn); loaded {
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
natConn.UpdateDestination(destinationAddress)
}
}
@@ -138,7 +133,7 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn))
}
func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error {
func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error {
ctx = adapter.WithContext(ctx, &metadata)
var outConn net.PacketConn
var destinationAddress netip.Addr
@@ -147,7 +142,7 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this
outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
} else if metadata.Destination.IsFqdn() {
var destinationAddresses []netip.Addr
destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy)
destinationAddresses, err = router.LookupDefault(ctx, metadata.Destination.Fqdn)
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
@@ -163,10 +158,7 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this
return err
}
if destinationAddress.IsValid() {
if metadata.Destination.IsFqdn() {
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
}
if natConn, loaded := common.Cast[*bufio.NATPacketConn](conn); loaded {
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
natConn.UpdateDestination(destinationAddress)
}
}

View File

@@ -120,7 +120,7 @@ func (h *Direct) DialParallel(ctx context.Context, network string, destination M
}
func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
ctx, metadata := adapter.ExtendContext(ctx)
ctx, metadata := adapter.AppendContext(ctx)
metadata.Outbound = h.tag
metadata.Destination = destination
switch h.overrideOption {

View File

@@ -3,6 +3,7 @@ package outbound
import (
"context"
"net"
"net/http"
"os"
"github.com/sagernet/sing-box/adapter"
@@ -33,6 +34,13 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge
if err != nil {
return nil, err
}
var headers http.Header
if options.Headers != nil {
headers = make(http.Header)
for key, values := range options.Headers {
headers[key] = values
}
}
return &HTTP{
myOutboundAdapter{
protocol: C.TypeHTTP,
@@ -48,7 +56,7 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge
Username: options.Username,
Password: options.Password,
Path: options.Path,
Headers: options.Headers.Build(),
Headers: headers,
}),
}, nil
}

View File

@@ -5,16 +5,18 @@ package outbound
import (
"context"
"net"
"os"
"sync"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/humanize"
"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-quic/hysteria"
"github.com/sagernet/sing-box/transport/hysteria"
"github.com/sagernet/sing-quic"
hyCC "github.com/sagernet/sing-quic/hysteria2/congestion"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
@@ -23,13 +25,27 @@ import (
)
var (
_ adapter.Outbound = (*TUIC)(nil)
_ adapter.InterfaceUpdateListener = (*TUIC)(nil)
_ adapter.Outbound = (*Hysteria)(nil)
_ adapter.InterfaceUpdateListener = (*Hysteria)(nil)
)
type Hysteria struct {
myOutboundAdapter
client *hysteria.Client
ctx context.Context
dialer N.Dialer
serverAddr M.Socksaddr
tlsConfig tls.Config
quicConfig *quic.Config
authKey []byte
xplusKey []byte
sendBPS uint64
recvBPS uint64
connAccess sync.Mutex
conn quic.Connection
rawConn net.Conn
udpAccess sync.RWMutex
udpSessions map[uint32]chan *hysteria.UDPMessage
udpDefragger hysteria.Defragger
}
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (*Hysteria, error) {
@@ -41,77 +57,252 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil {
return nil, err
}
outboundDialer, err := dialer.New(router, options.DialerOptions)
if err != nil {
return nil, err
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{hysteria.DefaultALPN})
}
networkList := options.Network.Build()
var password string
if options.AuthString != "" {
password = options.AuthString
quicConfig := &quic.Config{
InitialStreamReceiveWindow: options.ReceiveWindowConn,
MaxStreamReceiveWindow: options.ReceiveWindowConn,
InitialConnectionReceiveWindow: options.ReceiveWindow,
MaxConnectionReceiveWindow: options.ReceiveWindow,
KeepAlivePeriod: hysteria.KeepAlivePeriod,
DisablePathMTUDiscovery: options.DisableMTUDiscovery,
EnableDatagrams: true,
}
if options.ReceiveWindowConn == 0 {
quicConfig.InitialStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
quicConfig.MaxStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
}
if options.ReceiveWindow == 0 {
quicConfig.InitialConnectionReceiveWindow = hysteria.DefaultConnectionReceiveWindow
quicConfig.MaxConnectionReceiveWindow = hysteria.DefaultConnectionReceiveWindow
}
if quicConfig.MaxIncomingStreams == 0 {
quicConfig.MaxIncomingStreams = hysteria.DefaultMaxIncomingStreams
}
var auth []byte
if len(options.Auth) > 0 {
auth = options.Auth
} else {
password = string(options.Auth)
auth = []byte(options.AuthString)
}
var sendBps, receiveBps uint64
var xplus []byte
if options.Obfs != "" {
xplus = []byte(options.Obfs)
}
var up, down uint64
if len(options.Up) > 0 {
sendBps, err = humanize.ParseBytes(options.Up)
if err != nil {
return nil, E.Cause(err, "invalid up speed format: ", options.Up)
up = hysteria.StringToBps(options.Up)
if up == 0 {
return nil, E.New("invalid up speed format: ", options.Up)
}
} else {
sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps
up = uint64(options.UpMbps) * hysteria.MbpsToBps
}
if len(options.Down) > 0 {
receiveBps, err = humanize.ParseBytes(options.Down)
if receiveBps == 0 {
down = hysteria.StringToBps(options.Down)
if down == 0 {
return nil, E.New("invalid down speed format: ", options.Down)
}
} else {
receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps
down = uint64(options.DownMbps) * hysteria.MbpsToBps
}
client, err := hysteria.NewClient(hysteria.ClientOptions{
Context: ctx,
Dialer: outboundDialer,
Logger: logger,
ServerAddress: options.ServerOptions.Build(),
SendBPS: sendBps,
ReceiveBPS: receiveBps,
XPlusPassword: options.Obfs,
Password: password,
TLSConfig: tlsConfig,
UDPDisabled: !common.Contains(networkList, N.NetworkUDP),
ConnReceiveWindow: options.ReceiveWindowConn,
StreamReceiveWindow: options.ReceiveWindow,
DisableMTUDiscovery: options.DisableMTUDiscovery,
})
if up < hysteria.MinSpeedBPS {
return nil, E.New("invalid up speed")
}
if down < hysteria.MinSpeedBPS {
return nil, E.New("invalid down speed")
}
outboundDialer, err := dialer.New(router, options.DialerOptions)
if err != nil {
return nil, err
}
return &Hysteria{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeHysteria,
network: networkList,
network: options.Network.Build(),
router: router,
logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
client: client,
ctx: ctx,
dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(),
tlsConfig: tlsConfig,
quicConfig: quicConfig,
authKey: auth,
xplusKey: xplus,
sendBPS: up,
recvBPS: down,
}, nil
}
func (h *Hysteria) offer(ctx context.Context) (quic.Connection, error) {
conn := h.conn
if conn != nil && !common.Done(conn.Context()) {
return conn, nil
}
h.connAccess.Lock()
defer h.connAccess.Unlock()
h.udpAccess.Lock()
defer h.udpAccess.Unlock()
conn = h.conn
if conn != nil && !common.Done(conn.Context()) {
return conn, nil
}
common.Close(h.rawConn)
conn, err := h.offerNew(ctx)
if err != nil {
return nil, err
}
if common.Contains(h.network, N.NetworkUDP) {
for _, session := range h.udpSessions {
close(session)
}
h.udpSessions = make(map[uint32]chan *hysteria.UDPMessage)
h.udpDefragger = hysteria.Defragger{}
go h.udpRecvLoop(conn)
}
return conn, nil
}
func (h *Hysteria) offerNew(ctx context.Context) (quic.Connection, error) {
udpConn, err := h.dialer.DialContext(h.ctx, "udp", h.serverAddr)
if err != nil {
return nil, err
}
var packetConn net.PacketConn
packetConn = bufio.NewUnbindPacketConn(udpConn)
if h.xplusKey != nil {
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
}
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
quicConn, err := qtls.Dial(h.ctx, packetConn, udpConn.RemoteAddr(), h.tlsConfig, h.quicConfig)
if err != nil {
packetConn.Close()
return nil, err
}
controlStream, err := quicConn.OpenStreamSync(ctx)
if err != nil {
packetConn.Close()
return nil, err
}
err = hysteria.WriteClientHello(controlStream, hysteria.ClientHello{
SendBPS: h.sendBPS,
RecvBPS: h.recvBPS,
Auth: h.authKey,
})
if err != nil {
packetConn.Close()
return nil, err
}
serverHello, err := hysteria.ReadServerHello(controlStream)
if err != nil {
packetConn.Close()
return nil, err
}
if !serverHello.OK {
packetConn.Close()
return nil, E.New("remote error: ", serverHello.Message)
}
quicConn.SetCongestionControl(hyCC.NewBrutalSender(serverHello.RecvBPS, false, nil))
h.conn = quicConn
h.rawConn = udpConn
return quicConn, nil
}
func (h *Hysteria) udpRecvLoop(conn quic.Connection) {
for {
packet, err := conn.ReceiveMessage(h.ctx)
if err != nil {
return
}
message, err := hysteria.ParseUDPMessage(packet)
if err != nil {
h.logger.Error("parse udp message: ", err)
continue
}
dfMsg := h.udpDefragger.Feed(message)
if dfMsg == nil {
continue
}
h.udpAccess.RLock()
ch, ok := h.udpSessions[dfMsg.SessionID]
if ok {
select {
case ch <- dfMsg:
// OK
default:
// Silently drop the message when the channel is full
}
}
h.udpAccess.RUnlock()
}
}
func (h *Hysteria) InterfaceUpdated() {
h.Close()
return
}
func (h *Hysteria) Close() error {
h.connAccess.Lock()
defer h.connAccess.Unlock()
h.udpAccess.Lock()
defer h.udpAccess.Unlock()
if h.conn != nil {
h.conn.CloseWithError(0, "")
h.rawConn.Close()
}
for _, session := range h.udpSessions {
close(session)
}
h.udpSessions = make(map[uint32]chan *hysteria.UDPMessage)
return nil
}
func (h *Hysteria) open(ctx context.Context, reconnect bool) (quic.Connection, quic.Stream, error) {
conn, err := h.offer(ctx)
if err != nil {
if nErr, ok := err.(net.Error); ok && !nErr.Temporary() && reconnect {
return h.open(ctx, false)
}
return nil, nil, err
}
stream, err := conn.OpenStream()
if err != nil {
if nErr, ok := err.(net.Error); ok && !nErr.Temporary() && reconnect {
return h.open(ctx, false)
}
return nil, nil, err
}
return conn, &hysteria.StreamWrapper{Stream: stream}, nil
}
func (h *Hysteria) 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)
_, stream, err := h.open(ctx, true)
if err != nil {
return nil, err
}
err = hysteria.WriteClientRequest(stream, hysteria.ClientRequest{
Host: destination.AddrString(),
Port: destination.Port,
})
if err != nil {
stream.Close()
return nil, err
}
return hysteria.NewConn(stream, destination, true), nil
case N.NetworkUDP:
conn, err := h.ListenPacket(ctx, destination)
if err != nil {
return nil, err
}
return bufio.NewBindPacketConn(conn, destination), nil
return conn.(*hysteria.PacketConn), nil
default:
return nil, E.New("unsupported network: ", network)
}
@@ -119,7 +310,44 @@ func (h *Hysteria) DialContext(ctx context.Context, network string, destination
func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
return h.client.ListenPacket(ctx, destination)
conn, stream, err := h.open(ctx, true)
if err != nil {
return nil, err
}
err = hysteria.WriteClientRequest(stream, hysteria.ClientRequest{
UDP: true,
Host: destination.AddrString(),
Port: destination.Port,
})
if err != nil {
stream.Close()
return nil, err
}
var response *hysteria.ServerResponse
response, err = hysteria.ReadServerResponse(stream)
if err != nil {
stream.Close()
return nil, err
}
if !response.OK {
stream.Close()
return nil, E.New("remote error: ", response.Message)
}
h.udpAccess.Lock()
nCh := make(chan *hysteria.UDPMessage, 1024)
h.udpSessions[response.UDPSessionID] = nCh
h.udpAccess.Unlock()
packetConn := hysteria.NewPacketConn(conn, stream, response.UDPSessionID, destination, nCh, common.Closer(func() error {
h.udpAccess.Lock()
if ch, ok := h.udpSessions[response.UDPSessionID]; ok {
close(ch)
delete(h.udpSessions, response.UDPSessionID)
}
h.udpAccess.Unlock()
return nil
}))
go packetConn.Hold()
return packetConn, nil
}
func (h *Hysteria) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
@@ -129,11 +357,3 @@ func (h *Hysteria) NewConnection(ctx context.Context, conn net.Conn, metadata ad
func (h *Hysteria) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return NewPacketConnection(ctx, h, conn, metadata)
}
func (h *Hysteria) InterfaceUpdated() error {
return h.client.CloseWithError(E.New("network changed"))
}
func (h *Hysteria) Close() error {
return h.client.CloseWithError(os.ErrClosed)
}

View File

@@ -13,7 +13,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-quic/hysteria"
"github.com/sagernet/sing-box/transport/hysteria"
"github.com/sagernet/sing-quic/hysteria2"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"

View File

@@ -9,7 +9,6 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
@@ -115,7 +114,7 @@ func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.
func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if h.resolve {
return NewDirectConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4)
return NewDirectConnection(ctx, h.router, h, conn, metadata)
} else {
return NewConnection(ctx, h, conn, metadata)
}
@@ -123,7 +122,7 @@ func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapt
func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
if h.resolve {
return NewDirectPacketConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4)
return NewDirectPacketConnection(ctx, h.router, h, conn, metadata)
} else {
return NewPacketConnection(ctx, h, conn, metadata)
}

View File

@@ -16,8 +16,8 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/wireguard"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
@@ -70,7 +70,8 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
return nil, err
}
outbound.bind = wireguard.NewClientBind(ctx, outbound, outboundDialer, isConnect, connectAddr, reserved)
if len(options.LocalAddress) == 0 {
localPrefixes := common.Map(options.LocalAddress, option.ListenPrefix.Build)
if len(localPrefixes) == 0 {
return nil, E.New("missing local address")
}
var privateKey string
@@ -141,7 +142,7 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
ipcConf += "\npreshared_key=" + preSharedKey
}
var has4, has6 bool
for _, address := range options.LocalAddress {
for _, address := range localPrefixes {
if address.Addr().Is4() {
has4 = true
} else {
@@ -161,9 +162,9 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
}
var wireTunDevice wireguard.Device
if !options.SystemInterface && tun.WithGVisor {
wireTunDevice, err = wireguard.NewStackDevice(options.LocalAddress, mtu)
wireTunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu)
} else {
wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, options.LocalAddress, mtu)
wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
}
if err != nil {
return nil, E.Cause(err, "create WireGuard device")
@@ -227,11 +228,11 @@ func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) (
}
func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS)
return NewDirectConnection(ctx, w.router, w, conn, metadata)
}
func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS)
return NewDirectPacketConnection(ctx, w.router, w, conn, metadata)
}
func (w *WireGuard) Start() error {

View File

@@ -253,10 +253,10 @@ func NewRouter(
var inet4Range netip.Prefix
var inet6Range netip.Prefix
if fakeIPOptions.Inet4Range != nil {
inet4Range = *fakeIPOptions.Inet4Range
inet4Range = fakeIPOptions.Inet4Range.Build()
}
if fakeIPOptions.Inet6Range != nil {
inet6Range = *fakeIPOptions.Inet6Range
inet6Range = fakeIPOptions.Inet6Range.Build()
}
router.fakeIPStore = fakeip.NewStore(router, router.logger, inet4Range, inet6Range)
}
@@ -835,7 +835,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
}
}
if metadata.FakeIP {
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination)
conn = fakeip.NewNATPacketConn(conn, metadata.OriginDestination, metadata.Destination)
}
return detour.NewPacketConnection(ctx, conn, metadata)
}

View File

@@ -2,15 +2,10 @@ package main
import (
"context"
"crypto/tls"
"io"
"net"
"net/http"
"testing"
"time"
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/http3"
"github.com/sagernet/sing-box"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
@@ -79,28 +74,6 @@ func testSuit(t *testing.T, clientPort uint16, testPort uint16) {
// require.NoError(t, testPacketConnTimeout(t, dialUDP))
}
func testQUIC(t *testing.T, clientPort uint16) {
dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "")
client := &http.Client{
Transport: &http3.RoundTripper{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
destination := M.ParseSocksaddr(addr)
udpConn, err := dialer.DialContext(ctx, N.NetworkUDP, destination)
if err != nil {
return nil, err
}
return quic.DialEarly(ctx, udpConn.(net.PacketConn), destination, tlsCfg, cfg)
},
},
}
response, err := client.Get("https://cloudflare.com/cdn-cgi/trace")
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.StatusCode)
content, err := io.ReadAll(response.Body)
require.NoError(t, err)
println(string(content))
}
func testSuitLargeUDP(t *testing.T, clientPort uint16, testPort uint16) {
dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "")
dialTCP := func() (net.Conn, error) {

View File

@@ -1,83 +0,0 @@
package main
import (
"net/netip"
"testing"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
dns "github.com/sagernet/sing-dns"
"github.com/gofrs/uuid/v5"
)
func TestTUICDomainUDP(t *testing.T) {
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
startInstance(t, option.Options{
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
Tag: "mixed-in",
MixedOptions: option.HTTPMixedInboundOptions{
ListenOptions: option.ListenOptions{
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
ListenPort: clientPort,
},
},
},
{
Type: C.TypeTUIC,
TUICOptions: option.TUICInboundOptions{
ListenOptions: option.ListenOptions{
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
ListenPort: serverPort,
InboundOptions: option.InboundOptions{
DomainStrategy: option.DomainStrategy(dns.DomainStrategyUseIPv6),
},
},
Users: []option.TUICUser{{
UUID: uuid.Nil.String(),
}},
TLS: &option.InboundTLSOptions{
Enabled: true,
ServerName: "example.org",
CertificatePath: certPem,
KeyPath: keyPem,
},
},
},
},
Outbounds: []option.Outbound{
{
Type: C.TypeDirect,
},
{
Type: C.TypeTUIC,
Tag: "tuic-out",
TUICOptions: option.TUICOutboundOptions{
ServerOptions: option.ServerOptions{
Server: "127.0.0.1",
ServerPort: serverPort,
},
UUID: uuid.Nil.String(),
TLS: &option.OutboundTLSOptions{
Enabled: true,
ServerName: "example.org",
CertificatePath: certPem,
},
},
},
},
Route: &option.RouteOptions{
Rules: []option.Rule{
{
DefaultOptions: option.DefaultRule{
Inbound: []string{"mixed-in"},
Outbound: "tuic-out",
},
},
},
},
})
testQUIC(t, clientPort)
}

View File

@@ -6,35 +6,34 @@ require github.com/sagernet/sing-box v0.0.0
replace github.com/sagernet/sing-box => ../
replace github.com/sagernet/sing-quic => ../../sing-quic
require (
github.com/docker/docker v24.0.6+incompatible
github.com/docker/go-connections v0.4.0
github.com/gofrs/uuid/v5 v5.0.0
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028
github.com/sagernet/sing-dns v0.1.10
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6
github.com/sagernet/sing v0.2.13-0.20231001063649-e0ec961fb1ab
github.com/sagernet/sing-quic v0.1.1
github.com/sagernet/sing-shadowsocks v0.2.5
github.com/sagernet/sing-shadowsocks2 v0.1.4
github.com/spyzhov/ajson v0.9.0
github.com/stretchr/testify v1.8.4
go.uber.org/goleak v1.3.0
golang.org/x/net v0.17.0
go.uber.org/goleak v1.2.1
golang.org/x/net v0.15.0
)
require (
berty.tech/go-libtor v1.0.385 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/caddyserver/certmagic v0.19.2 // indirect
github.com/cloudflare/circl v1.3.5 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/cretz/bine v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-chi/cors v1.2.1 // indirect
github.com/go-chi/render v1.0.3 // indirect
@@ -45,9 +44,9 @@ require (
github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c // indirect
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.16.0 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/libdns/alidns v1.0.3 // indirect
github.com/libdns/cloudflare v0.1.0 // indirect
@@ -55,7 +54,7 @@ require (
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
github.com/mholt/acmez v1.2.0 // indirect
github.com/miekg/dns v1.1.56 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/ooni/go-libtor v1.1.8 // indirect
@@ -67,15 +66,16 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a // indirect
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab // indirect
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
github.com/sagernet/quic-go v0.0.0-20231001051131-0fc736a289bb // indirect
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/sing-dns v0.1.10 // indirect
github.com/sagernet/sing-mux v0.1.3 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6 // indirect
github.com/sagernet/sing-tun v0.1.14 // indirect
github.com/sagernet/sing-vmess v0.1.8 // indirect
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect
@@ -86,20 +86,21 @@ require (
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/grpc v1.59.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/grpc v1.58.2 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.1 // indirect
gotest.tools/v3 v3.4.0 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
)

View File

@@ -1,8 +1,8 @@
berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=
berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
@@ -12,26 +12,24 @@ github.com/caddyserver/certmagic v0.19.2/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.3.5 h1:g+wWynZqVALYAlpSQFAa7TscDnUK8mKYtrxMpw6AUKo=
github.com/cloudflare/circl v1.3.5/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE=
github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
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=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
@@ -59,8 +57,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c h1:PgxFEySCI41sH0mB7/2XswdXbUykQsRUGod8Rn+NubM=
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ=
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -68,7 +66,6 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@@ -86,8 +83,8 @@ github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
@@ -112,38 +109,34 @@ github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a h1:wZHruBxZCsQLXHAozWpnJBL3wJ/XufDpz0qKtgpSnA4=
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/gvisor v0.0.0-20230930141345-5fef6f2e17ab h1:u+xQoi/Yc6bNUvTfrDD6HhGRybn2lzrhf5vmS+wb4Ho=
github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab/go.mod h1:3akUhSHSVtLuJaYcW5JPepUraBOW06Ibz2HKwaK5rOk=
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTSWt6hdPrARORfoYvuUczynvRLrueo=
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460 h1:dAe4OIJAtE0nHOzTHhAReQteh3+sa63rvXbuIpbeOTY=
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460/go.mod h1:uJGpmJCOcMQqMlHKc3P1Vz6uygmpz4bPeVIoOhdVQnM=
github.com/sagernet/quic-go v0.0.0-20231001051131-0fc736a289bb h1:jlrVCepGBoob4QsPChIbe1j0d/lZSJkyVj2ukX3D4PE=
github.com/sagernet/quic-go v0.0.0-20231001051131-0fc736a289bb/go.mod h1:uJGpmJCOcMQqMlHKc3P1Vz6uygmpz4bPeVIoOhdVQnM=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028 h1:6GbQt7SC9y5Imrq5jDMbXDSaNiMhJ8KBjhjtQRuqQvE=
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028/go.mod h1:AhNEHu0GXrpqkuzvTwvC8+j2cQUU/dh+zLEmq4C99pg=
github.com/sagernet/sing v0.2.13-0.20231001063649-e0ec961fb1ab h1:ccXgrc2HGcPlFFtNISITE95YeUiChU+7GCQaeWriylk=
github.com/sagernet/sing v0.2.13-0.20231001063649-e0ec961fb1ab/go.mod h1:GQ673iPfUnkbK/dIPkfd1Xh1MjOGo36gkl/mkiHY7Jg=
github.com/sagernet/sing-dns v0.1.10 h1:iIU7nRBlUYj+fF2TaktGIvRiTFFrHwSMedLQsvlTZCI=
github.com/sagernet/sing-dns v0.1.10/go.mod h1:vtUimtf7Nq9EdvD5WTpfCr69KL1M7bcgOVKiYBiAY/c=
github.com/sagernet/sing-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6 h1:w+TUbIZKZFSdf/AUa/y33kY9xaLeNGz/tBNcNhqpqfg=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6/go.mod h1:1M7xP4802K9Kz6BQ7LlA7UeCapWvWlH1Htmk2bAqkWc=
github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY=
github.com/sagernet/sing-shadowsocks v0.2.5/go.mod h1:MGWGkcU2xW2G2mfArT9/QqpVLOGU+dBaahZCtPHdt7A=
github.com/sagernet/sing-shadowsocks2 v0.1.4 h1:vht2M8t3m5DTgXR2j24KbYOygG5aOp+MUhpQnAux728=
github.com/sagernet/sing-shadowsocks2 v0.1.4/go.mod h1:Mgdee99NxxNd5Zld3ixIs18yVs4x2dI2VTDDE1N14Wc=
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.17-0.20231026060825-efd9884154a6 h1:4yEXBqQoUgXj7qPSLD6lr+z9/KfsvixO9JUA2i5xnM8=
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6/go.mod h1:w2+S+uWE94E/pQWSDdDdMIjwAEb645kuGPunr6ZllUg=
github.com/sagernet/sing-tun v0.1.14 h1:Vsval4r78kngCsZsz0FExT6p6akiUeRuiVXjfgnN3Ok=
github.com/sagernet/sing-tun v0.1.14/go.mod h1:Z2WibDUoQh/3wwFCfkIzIG0n/NlAlPuEbLTq3rD1aQY=
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
@@ -178,8 +171,10 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
@@ -191,26 +186,26 @@ golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaE
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -218,15 +213,17 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -238,16 +235,17 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I=
google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
@@ -257,7 +255,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8X
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=

View File

@@ -97,7 +97,7 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) {
},
},
})
testSuitLargeUDP(t, clientPort, testPort)
testSuit(t, clientPort, testPort)
}
func TestHysteria2Inbound(t *testing.T) {

View File

@@ -79,7 +79,7 @@ func TestHysteriaSelf(t *testing.T) {
},
},
})
testSuit(t, clientPort, testPort)
testSuitSimple1(t, clientPort, testPort)
}
func TestHysteriaInbound(t *testing.T) {
@@ -118,7 +118,7 @@ func TestHysteriaInbound(t *testing.T) {
caPem: "/etc/hysteria/ca.pem",
},
})
testSuit(t, clientPort, testPort)
testSuitSimple1(t, clientPort, testPort)
}
func TestHysteriaOutbound(t *testing.T) {

View File

@@ -40,7 +40,7 @@ func _TestWireGuard(t *testing.T) {
Server: "127.0.0.1",
ServerPort: serverPort,
},
LocalAddress: []netip.Prefix{netip.MustParsePrefix("10.0.0.2/32")},
LocalAddress: []option.ListenPrefix{option.ListenPrefix(netip.MustParsePrefix("10.0.0.2/32"))},
PrivateKey: "qGnwlkZljMxeECW8fbwAWdvgntnbK7B8UmMFl3zM0mk=",
PeerPublicKey: "QsdcBm+oJw2oNv0cIFXLIq1E850lgTBonup4qnKEQBg=",
},

View File

@@ -0,0 +1,65 @@
package hysteria
func FragUDPMessage(m UDPMessage, maxSize int) []UDPMessage {
if m.Size() <= maxSize {
return []UDPMessage{m}
}
fullPayload := m.Data
maxPayloadSize := maxSize - m.HeaderSize()
off := 0
fragID := uint8(0)
fragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up
var frags []UDPMessage
for off < len(fullPayload) {
payloadSize := len(fullPayload) - off
if payloadSize > maxPayloadSize {
payloadSize = maxPayloadSize
}
frag := m
frag.FragID = fragID
frag.FragCount = fragCount
frag.Data = fullPayload[off : off+payloadSize]
frags = append(frags, frag)
off += payloadSize
fragID++
}
return frags
}
type Defragger struct {
msgID uint16
frags []*UDPMessage
count uint8
}
func (d *Defragger) Feed(m UDPMessage) *UDPMessage {
if m.FragCount <= 1 {
return &m
}
if m.FragID >= m.FragCount {
// wtf is this?
return nil
}
if m.MsgID != d.msgID {
// new message, clear previous state
d.msgID = m.MsgID
d.frags = make([]*UDPMessage, m.FragCount)
d.count = 1
d.frags[m.FragID] = &m
} else if d.frags[m.FragID] == nil {
d.frags[m.FragID] = &m
d.count++
if int(d.count) == len(d.frags) {
// all fragments received, assemble
var data []byte
for _, frag := range d.frags {
data = append(data, frag.Data...)
}
m.Data = data
m.FragID = 0
m.FragCount = 1
return &m
}
}
return nil
}

View File

@@ -0,0 +1,539 @@
package hysteria
import (
"bytes"
"encoding/binary"
"io"
"math/rand"
"net"
"os"
"time"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
)
const (
MbpsToBps = 125000
MinSpeedBPS = 16384
DefaultStreamReceiveWindow = 15728640 // 15 MB/s
DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
DefaultMaxIncomingStreams = 1024
DefaultALPN = "hysteria"
KeepAlivePeriod = 10 * time.Second
)
const Version = 3
type ClientHello struct {
SendBPS uint64
RecvBPS uint64
Auth []byte
}
func WriteClientHello(stream io.Writer, hello ClientHello) error {
var requestLen int
requestLen += 1 // version
requestLen += 8 // sendBPS
requestLen += 8 // recvBPS
requestLen += 2 // auth len
requestLen += len(hello.Auth)
request := buf.NewSize(requestLen)
defer request.Release()
common.Must(
request.WriteByte(Version),
binary.Write(request, binary.BigEndian, hello.SendBPS),
binary.Write(request, binary.BigEndian, hello.RecvBPS),
binary.Write(request, binary.BigEndian, uint16(len(hello.Auth))),
common.Error(request.Write(hello.Auth)),
)
return common.Error(stream.Write(request.Bytes()))
}
func ReadClientHello(reader io.Reader) (*ClientHello, error) {
var version uint8
err := binary.Read(reader, binary.BigEndian, &version)
if err != nil {
return nil, err
}
if version != Version {
return nil, E.New("unsupported client version: ", version)
}
var clientHello ClientHello
err = binary.Read(reader, binary.BigEndian, &clientHello.SendBPS)
if err != nil {
return nil, err
}
err = binary.Read(reader, binary.BigEndian, &clientHello.RecvBPS)
if err != nil {
return nil, err
}
var authLen uint16
err = binary.Read(reader, binary.BigEndian, &authLen)
if err != nil {
return nil, err
}
clientHello.Auth = make([]byte, authLen)
_, err = io.ReadFull(reader, clientHello.Auth)
if err != nil {
return nil, err
}
return &clientHello, nil
}
type ServerHello struct {
OK bool
SendBPS uint64
RecvBPS uint64
Message string
}
func ReadServerHello(stream io.Reader) (*ServerHello, error) {
var responseLen int
responseLen += 1 // ok
responseLen += 8 // sendBPS
responseLen += 8 // recvBPS
responseLen += 2 // message len
response := buf.NewSize(responseLen)
defer response.Release()
_, err := response.ReadFullFrom(stream, responseLen)
if err != nil {
return nil, err
}
var serverHello ServerHello
serverHello.OK = response.Byte(0) == 1
serverHello.SendBPS = binary.BigEndian.Uint64(response.Range(1, 9))
serverHello.RecvBPS = binary.BigEndian.Uint64(response.Range(9, 17))
messageLen := binary.BigEndian.Uint16(response.Range(17, 19))
if messageLen == 0 {
return &serverHello, nil
}
message := make([]byte, messageLen)
_, err = io.ReadFull(stream, message)
if err != nil {
return nil, err
}
serverHello.Message = string(message)
return &serverHello, nil
}
func WriteServerHello(stream io.Writer, hello ServerHello) error {
var responseLen int
responseLen += 1 // ok
responseLen += 8 // sendBPS
responseLen += 8 // recvBPS
responseLen += 2 // message len
responseLen += len(hello.Message)
response := buf.NewSize(responseLen)
defer response.Release()
if hello.OK {
common.Must(response.WriteByte(1))
} else {
common.Must(response.WriteByte(0))
}
common.Must(
binary.Write(response, binary.BigEndian, hello.SendBPS),
binary.Write(response, binary.BigEndian, hello.RecvBPS),
binary.Write(response, binary.BigEndian, uint16(len(hello.Message))),
common.Error(response.WriteString(hello.Message)),
)
return common.Error(stream.Write(response.Bytes()))
}
type ClientRequest struct {
UDP bool
Host string
Port uint16
}
func ReadClientRequest(stream io.Reader) (*ClientRequest, error) {
var clientRequest ClientRequest
err := binary.Read(stream, binary.BigEndian, &clientRequest.UDP)
if err != nil {
return nil, err
}
var hostLen uint16
err = binary.Read(stream, binary.BigEndian, &hostLen)
if err != nil {
return nil, err
}
host := make([]byte, hostLen)
_, err = io.ReadFull(stream, host)
if err != nil {
return nil, err
}
clientRequest.Host = string(host)
err = binary.Read(stream, binary.BigEndian, &clientRequest.Port)
if err != nil {
return nil, err
}
return &clientRequest, nil
}
func WriteClientRequest(stream io.Writer, request ClientRequest) error {
var requestLen int
requestLen += 1 // udp
requestLen += 2 // host len
requestLen += len(request.Host)
requestLen += 2 // port
buffer := buf.NewSize(requestLen)
defer buffer.Release()
if request.UDP {
common.Must(buffer.WriteByte(1))
} else {
common.Must(buffer.WriteByte(0))
}
common.Must(
binary.Write(buffer, binary.BigEndian, uint16(len(request.Host))),
common.Error(buffer.WriteString(request.Host)),
binary.Write(buffer, binary.BigEndian, request.Port),
)
return common.Error(stream.Write(buffer.Bytes()))
}
type ServerResponse struct {
OK bool
UDPSessionID uint32
Message string
}
func ReadServerResponse(stream io.Reader) (*ServerResponse, error) {
var responseLen int
responseLen += 1 // ok
responseLen += 4 // udp session id
responseLen += 2 // message len
response := buf.NewSize(responseLen)
defer response.Release()
_, err := response.ReadFullFrom(stream, responseLen)
if err != nil {
return nil, err
}
var serverResponse ServerResponse
serverResponse.OK = response.Byte(0) == 1
serverResponse.UDPSessionID = binary.BigEndian.Uint32(response.Range(1, 5))
messageLen := binary.BigEndian.Uint16(response.Range(5, 7))
if messageLen == 0 {
return &serverResponse, nil
}
message := make([]byte, messageLen)
_, err = io.ReadFull(stream, message)
if err != nil {
return nil, err
}
serverResponse.Message = string(message)
return &serverResponse, nil
}
func WriteServerResponse(stream io.Writer, response ServerResponse) error {
var responseLen int
responseLen += 1 // ok
responseLen += 4 // udp session id
responseLen += 2 // message len
responseLen += len(response.Message)
buffer := buf.NewSize(responseLen)
defer buffer.Release()
if response.OK {
common.Must(buffer.WriteByte(1))
} else {
common.Must(buffer.WriteByte(0))
}
common.Must(
binary.Write(buffer, binary.BigEndian, response.UDPSessionID),
binary.Write(buffer, binary.BigEndian, uint16(len(response.Message))),
common.Error(buffer.WriteString(response.Message)),
)
return common.Error(stream.Write(buffer.Bytes()))
}
type UDPMessage struct {
SessionID uint32
Host string
Port uint16
MsgID uint16 // doesn't matter when not fragmented, but must not be 0 when fragmented
FragID uint8 // doesn't matter when not fragmented, starts at 0 when fragmented
FragCount uint8 // must be 1 when not fragmented
Data []byte
}
func (m UDPMessage) HeaderSize() int {
return 4 + 2 + len(m.Host) + 2 + 2 + 1 + 1 + 2
}
func (m UDPMessage) Size() int {
return m.HeaderSize() + len(m.Data)
}
func ParseUDPMessage(packet []byte) (message UDPMessage, err error) {
reader := bytes.NewReader(packet)
err = binary.Read(reader, binary.BigEndian, &message.SessionID)
if err != nil {
return
}
var hostLen uint16
err = binary.Read(reader, binary.BigEndian, &hostLen)
if err != nil {
return
}
_, err = reader.Seek(int64(hostLen), io.SeekCurrent)
if err != nil {
return
}
if 6+int(hostLen) > len(packet) {
err = E.New("invalid host length")
return
}
message.Host = string(packet[6 : 6+hostLen])
err = binary.Read(reader, binary.BigEndian, &message.Port)
if err != nil {
return
}
err = binary.Read(reader, binary.BigEndian, &message.MsgID)
if err != nil {
return
}
err = binary.Read(reader, binary.BigEndian, &message.FragID)
if err != nil {
return
}
err = binary.Read(reader, binary.BigEndian, &message.FragCount)
if err != nil {
return
}
var dataLen uint16
err = binary.Read(reader, binary.BigEndian, &dataLen)
if err != nil {
return
}
if reader.Len() != int(dataLen) {
err = E.New("invalid data length")
}
dataOffset := int(reader.Size()) - reader.Len()
message.Data = packet[dataOffset:]
return
}
func WriteUDPMessage(conn quic.Connection, message UDPMessage) error {
var messageLen int
messageLen += 4 // session id
messageLen += 2 // host len
messageLen += len(message.Host)
messageLen += 2 // port
messageLen += 2 // msg id
messageLen += 1 // frag id
messageLen += 1 // frag count
messageLen += 2 // data len
messageLen += len(message.Data)
buffer := buf.NewSize(messageLen)
defer buffer.Release()
err := writeUDPMessage(conn, message, buffer)
if errSize, ok := err.(quic.ErrMessageTooLarge); ok {
// need to frag
message.MsgID = uint16(rand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
fragMsgs := FragUDPMessage(message, int(errSize))
for _, fragMsg := range fragMsgs {
buffer.FullReset()
err = writeUDPMessage(conn, fragMsg, buffer)
if err != nil {
return err
}
}
return nil
}
return err
}
func writeUDPMessage(conn quic.Connection, message UDPMessage, buffer *buf.Buffer) error {
common.Must(
binary.Write(buffer, binary.BigEndian, message.SessionID),
binary.Write(buffer, binary.BigEndian, uint16(len(message.Host))),
common.Error(buffer.WriteString(message.Host)),
binary.Write(buffer, binary.BigEndian, message.Port),
binary.Write(buffer, binary.BigEndian, message.MsgID),
binary.Write(buffer, binary.BigEndian, message.FragID),
binary.Write(buffer, binary.BigEndian, message.FragCount),
binary.Write(buffer, binary.BigEndian, uint16(len(message.Data))),
common.Error(buffer.Write(message.Data)),
)
return conn.SendMessage(buffer.Bytes())
}
var _ net.Conn = (*Conn)(nil)
type Conn struct {
quic.Stream
destination M.Socksaddr
needReadResponse bool
}
func NewConn(stream quic.Stream, destination M.Socksaddr, isClient bool) *Conn {
return &Conn{
Stream: stream,
destination: destination,
needReadResponse: isClient,
}
}
func (c *Conn) Read(p []byte) (n int, err error) {
if c.needReadResponse {
var response *ServerResponse
response, err = ReadServerResponse(c.Stream)
if err != nil {
c.Close()
return
}
if !response.OK {
c.Close()
return 0, E.New("remote error: ", response.Message)
}
c.needReadResponse = false
}
return c.Stream.Read(p)
}
func (c *Conn) LocalAddr() net.Addr {
return M.Socksaddr{}
}
func (c *Conn) RemoteAddr() net.Addr {
return c.destination.TCPAddr()
}
func (c *Conn) ReaderReplaceable() bool {
return !c.needReadResponse
}
func (c *Conn) WriterReplaceable() bool {
return true
}
func (c *Conn) Upstream() any {
return c.Stream
}
type PacketConn struct {
session quic.Connection
stream quic.Stream
sessionId uint32
destination M.Socksaddr
msgCh <-chan *UDPMessage
closer io.Closer
}
func NewPacketConn(session quic.Connection, stream quic.Stream, sessionId uint32, destination M.Socksaddr, msgCh <-chan *UDPMessage, closer io.Closer) *PacketConn {
return &PacketConn{
session: session,
stream: stream,
sessionId: sessionId,
destination: destination,
msgCh: msgCh,
closer: closer,
}
}
func (c *PacketConn) Hold() {
// Hold the stream until it's closed
buf := make([]byte, 1024)
for {
_, err := c.stream.Read(buf)
if err != nil {
break
}
}
_ = c.Close()
}
func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
msg := <-c.msgCh
if msg == nil {
err = net.ErrClosed
return
}
err = common.Error(buffer.Write(msg.Data))
destination = M.ParseSocksaddrHostPort(msg.Host, msg.Port).Unwrap()
return
}
func (c *PacketConn) ReadPacketThreadSafe() (buffer *buf.Buffer, destination M.Socksaddr, err error) {
msg := <-c.msgCh
if msg == nil {
err = net.ErrClosed
return
}
buffer = buf.As(msg.Data)
destination = M.ParseSocksaddrHostPort(msg.Host, msg.Port).Unwrap()
return
}
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
return WriteUDPMessage(c.session, UDPMessage{
SessionID: c.sessionId,
Host: destination.AddrString(),
Port: destination.Port,
FragCount: 1,
Data: buffer.Bytes(),
})
}
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
msg := <-c.msgCh
if msg == nil {
err = net.ErrClosed
return
}
n = copy(p, msg.Data)
destination := M.ParseSocksaddrHostPort(msg.Host, msg.Port)
if destination.IsFqdn() {
addr = destination
} else {
addr = destination.UDPAddr()
}
return
}
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
err = c.WritePacket(buf.As(p), M.SocksaddrFromNet(addr))
if err == nil {
n = len(p)
}
return
}
func (c *PacketConn) LocalAddr() net.Addr {
return M.Socksaddr{}
}
func (c *PacketConn) RemoteAddr() net.Addr {
return c.destination.UDPAddr()
}
func (c *PacketConn) SetDeadline(t time.Time) error {
return os.ErrInvalid
}
func (c *PacketConn) SetReadDeadline(t time.Time) error {
return os.ErrInvalid
}
func (c *PacketConn) SetWriteDeadline(t time.Time) error {
return os.ErrInvalid
}
func (c *PacketConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *PacketConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return
}
func (c *PacketConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.destination)
}
func (c *PacketConn) Close() error {
return common.Close(c.stream, c.closer)
}

View File

@@ -0,0 +1,36 @@
package hysteria
import (
"regexp"
"strconv"
)
func StringToBps(s string) uint64 {
if s == "" {
return 0
}
m := regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`).FindStringSubmatch(s)
if m == nil {
return 0
}
var n uint64
switch m[2] {
case "K":
n = 1 << 10
case "M":
n = 1 << 20
case "G":
n = 1 << 30
case "T":
n = 1 << 40
default:
n = 1
}
v, _ := strconv.ParseUint(m[1], 10, 64)
n = v * n
if m[3] == "b" {
// Bits, need to convert to bytes
n = n >> 3
}
return n
}

View File

@@ -0,0 +1,68 @@
package hysteria
import (
"net"
"os"
"syscall"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/baderror"
)
type PacketConnWrapper struct {
net.PacketConn
}
func (c *PacketConnWrapper) SetReadBuffer(bytes int) error {
return common.MustCast[*net.UDPConn](c.PacketConn).SetReadBuffer(bytes)
}
func (c *PacketConnWrapper) SetWriteBuffer(bytes int) error {
return common.MustCast[*net.UDPConn](c.PacketConn).SetWriteBuffer(bytes)
}
func (c *PacketConnWrapper) SyscallConn() (syscall.RawConn, error) {
return common.MustCast[*net.UDPConn](c.PacketConn).SyscallConn()
}
func (c *PacketConnWrapper) File() (f *os.File, err error) {
return common.MustCast[*net.UDPConn](c.PacketConn).File()
}
func (c *PacketConnWrapper) Upstream() any {
return c.PacketConn
}
type StreamWrapper struct {
Conn quic.Connection
quic.Stream
}
func (s *StreamWrapper) Read(p []byte) (n int, err error) {
n, err = s.Stream.Read(p)
return n, baderror.WrapQUIC(err)
}
func (s *StreamWrapper) Write(p []byte) (n int, err error) {
n, err = s.Stream.Write(p)
return n, baderror.WrapQUIC(err)
}
func (s *StreamWrapper) LocalAddr() net.Addr {
return s.Conn.LocalAddr()
}
func (s *StreamWrapper) RemoteAddr() net.Addr {
return s.Conn.RemoteAddr()
}
func (s *StreamWrapper) Upstream() any {
return s.Stream
}
func (s *StreamWrapper) Close() error {
s.CancelRead(0)
s.Stream.Close()
return nil
}

118
transport/hysteria/xplus.go Normal file
View File

@@ -0,0 +1,118 @@
package hysteria
import (
"crypto/sha256"
"math/rand"
"net"
"sync"
"time"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
const xplusSaltLen = 16
func NewXPlusPacketConn(conn net.PacketConn, key []byte) net.PacketConn {
vectorisedWriter, isVectorised := bufio.CreateVectorisedPacketWriter(conn)
if isVectorised {
return &VectorisedXPlusConn{
XPlusPacketConn: XPlusPacketConn{
PacketConn: conn,
key: key,
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
},
writer: vectorisedWriter,
}
} else {
return &XPlusPacketConn{
PacketConn: conn,
key: key,
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
}
}
}
type XPlusPacketConn struct {
net.PacketConn
key []byte
randAccess sync.Mutex
rand *rand.Rand
}
func (c *XPlusPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, addr, err = c.PacketConn.ReadFrom(p)
if err != nil {
return
} else if n < xplusSaltLen {
n = 0
return
}
key := sha256.Sum256(append(c.key, p[:xplusSaltLen]...))
for i := range p[xplusSaltLen:] {
p[i] = p[xplusSaltLen+i] ^ key[i%sha256.Size]
}
n -= xplusSaltLen
return
}
func (c *XPlusPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
// can't use unsafe buffer on WriteTo
buffer := buf.NewSize(len(p) + xplusSaltLen)
defer buffer.Release()
salt := buffer.Extend(xplusSaltLen)
c.randAccess.Lock()
_, _ = c.rand.Read(salt)
c.randAccess.Unlock()
key := sha256.Sum256(append(c.key, salt...))
for i := range p {
common.Must(buffer.WriteByte(p[i] ^ key[i%sha256.Size]))
}
return c.PacketConn.WriteTo(buffer.Bytes(), addr)
}
func (c *XPlusPacketConn) Upstream() any {
return c.PacketConn
}
type VectorisedXPlusConn struct {
XPlusPacketConn
writer N.VectorisedPacketWriter
}
func (c *VectorisedXPlusConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
header := buf.NewSize(xplusSaltLen)
defer header.Release()
salt := header.Extend(xplusSaltLen)
c.randAccess.Lock()
_, _ = c.rand.Read(salt)
c.randAccess.Unlock()
key := sha256.Sum256(append(c.key, salt...))
for i := range p {
p[i] ^= key[i%sha256.Size]
}
return bufio.WriteVectorisedPacket(c.writer, [][]byte{header.Bytes(), p}, M.SocksaddrFromNet(addr))
}
func (c *VectorisedXPlusConn) WriteVectorisedPacket(buffers []*buf.Buffer, destination M.Socksaddr) error {
header := buf.NewSize(xplusSaltLen)
defer header.Release()
salt := header.Extend(xplusSaltLen)
c.randAccess.Lock()
_, _ = c.rand.Read(salt)
c.randAccess.Unlock()
key := sha256.Sum256(append(c.key, salt...))
var index int
for _, buffer := range buffers {
data := buffer.Bytes()
for i := range data {
data[i] ^= key[index%sha256.Size]
index++
}
}
buffers = append([]*buf.Buffer{header}, buffers...)
return c.writer.WriteVectorisedPacket(buffers, destination)
}

View File

@@ -64,7 +64,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
serverAddr: serverAddr,
host: options.Host,
method: options.Method,
headers: options.Headers.Build(),
headers: make(http.Header),
transport: transport,
http2: tlsConfig != nil,
}
@@ -83,6 +83,9 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
if err != nil {
return nil, E.New("failed to set path: " + err.Error())
}
for key, valueList := range options.Headers {
client.headers[key] = valueList
}
client.url = &uri
return client, nil
}

View File

@@ -55,7 +55,7 @@ func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig t
host: options.Host,
path: options.Path,
method: options.Method,
headers: options.Headers.Build(),
headers: make(http.Header),
}
if server.method == "" {
server.method = "PUT"
@@ -63,6 +63,9 @@ func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig t
if !strings.HasPrefix(server.path, "/") {
server.path = "/" + server.path
}
for key, value := range options.Headers {
server.headers[key] = value
}
server.httpServer = &http.Server{
Handler: server,
ReadHeaderTimeout: C.TCPTimeout,

View File

@@ -12,6 +12,7 @@ import (
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/hysteria"
"github.com/sagernet/sing-quic"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
@@ -92,7 +93,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
if err != nil {
return nil, err
}
return &StreamWrapper{Conn: conn, Stream: stream}, nil
return &hysteria.StreamWrapper{Conn: conn, Stream: stream}, nil
}
func (c *Client) Close() error {

View File

@@ -12,6 +12,7 @@ import (
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/hysteria"
"github.com/sagernet/sing-quic"
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
@@ -85,7 +86,7 @@ func (s *Server) streamAcceptLoop(conn quic.Connection) error {
if err != nil {
return err
}
go s.handler.NewConnection(conn.Context(), &StreamWrapper{Conn: conn, Stream: stream}, M.Metadata{})
go s.handler.NewConnection(conn.Context(), &hysteria.StreamWrapper{Conn: conn, Stream: stream}, M.Metadata{})
}
}

View File

@@ -1,41 +0,0 @@
package v2rayquic
import (
"net"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing/common/baderror"
)
type StreamWrapper struct {
Conn quic.Connection
quic.Stream
}
func (s *StreamWrapper) Read(p []byte) (n int, err error) {
n, err = s.Stream.Read(p)
return n, baderror.WrapQUIC(err)
}
func (s *StreamWrapper) Write(p []byte) (n int, err error) {
n, err = s.Stream.Write(p)
return n, baderror.WrapQUIC(err)
}
func (s *StreamWrapper) LocalAddr() net.Addr {
return s.Conn.LocalAddr()
}
func (s *StreamWrapper) RemoteAddr() net.Addr {
return s.Conn.RemoteAddr()
}
func (s *StreamWrapper) Upstream() any {
return s.Stream
}
func (s *StreamWrapper) Close() error {
s.CancelRead(0)
s.Stream.Close()
return nil
}