mirror of
https://github.com/apernet/hysteria.git
synced 2025-04-03 04:27:39 +03:00
First commit, forwarder is essentially complete
This commit is contained in:
commit
5547895dcb
22 changed files with 1999 additions and 0 deletions
183
.gitignore
vendored
Normal file
183
.gitignore
vendored
Normal file
|
@ -0,0 +1,183 @@
|
|||
# Created by https://www.gitignore.io/api/go,linux,macos,windows,intellij+all
|
||||
# Edit at https://www.gitignore.io/?templates=go,linux,macos,windows,intellij+all
|
||||
|
||||
### Go ###
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
### Go Patch ###
|
||||
/vendor/
|
||||
/Godeps/
|
||||
|
||||
### Intellij+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Intellij+all Patch ###
|
||||
# Ignores the whole .idea folder and all .iml files
|
||||
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||
|
||||
.idea/
|
||||
|
||||
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||
|
||||
*.iml
|
||||
modules.xml
|
||||
.idea/misc.xml
|
||||
*.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
.idea/sonarlint
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.gitignore.io/api/go,linux,macos,windows,intellij+all
|
||||
|
||||
cmd/forwarder/*.json
|
||||
cmd/forwarder/forwarder
|
169
cmd/forwarder/client.go
Normal file
169
cmd/forwarder/client.go
Normal file
|
@ -0,0 +1,169 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/forwarder"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/user"
|
||||
)
|
||||
|
||||
func loadCmdClientConfig(args []string) (CmdClientConfig, error) {
|
||||
fs := flag.NewFlagSet("client", flag.ContinueOnError)
|
||||
// Config file
|
||||
configFile := fs.String("config", "", "Configuration file path")
|
||||
// Listen
|
||||
listen := fs.String("listen", "", "TCP listen address")
|
||||
// Server
|
||||
server := fs.String("server", "", "Server address")
|
||||
// Name
|
||||
name := fs.String("name", "", "Client name presented to the server")
|
||||
// Insecure
|
||||
var insecure optionalBoolFlag
|
||||
fs.Var(&insecure, "insecure", "Ignore TLS certificate errors")
|
||||
// Custom CA
|
||||
customCAFile := fs.String("ca", "", "Specify a trusted CA file")
|
||||
// Up Mbps
|
||||
upMbps := fs.Int("up-mbps", 0, "Upload speed in Mbps")
|
||||
// Down Mbps
|
||||
downMbps := fs.Int("down-mbps", 0, "Download speed in Mbps")
|
||||
// Receive window conn
|
||||
recvWindowConn := fs.Uint64("recv-window-conn", 0, "Max receive window size per connection")
|
||||
// Receive window
|
||||
recvWindow := fs.Uint64("recv-window", 0, "Max receive window size")
|
||||
// Parse
|
||||
if err := fs.Parse(args); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
// Put together the config
|
||||
var config CmdClientConfig
|
||||
// Load from file first
|
||||
if len(*configFile) > 0 {
|
||||
cb, err := ioutil.ReadFile(*configFile)
|
||||
if err != nil {
|
||||
return CmdClientConfig{}, err
|
||||
}
|
||||
if err := json.Unmarshal(cb, &config); err != nil {
|
||||
return CmdClientConfig{}, err
|
||||
}
|
||||
}
|
||||
// Then CLI options can override config
|
||||
if len(*listen) > 0 {
|
||||
config.ListenAddr = *listen
|
||||
}
|
||||
if len(*server) > 0 {
|
||||
config.ServerAddr = *server
|
||||
}
|
||||
if len(*name) > 0 {
|
||||
config.Name = *name
|
||||
}
|
||||
if insecure.Exists {
|
||||
config.Insecure = insecure.Value
|
||||
}
|
||||
if len(*customCAFile) > 0 {
|
||||
config.CustomCAFile = *customCAFile
|
||||
}
|
||||
if *upMbps != 0 {
|
||||
config.UpMbps = *upMbps
|
||||
}
|
||||
if *downMbps != 0 {
|
||||
config.DownMbps = *downMbps
|
||||
}
|
||||
if *recvWindowConn != 0 {
|
||||
config.ReceiveWindowConn = *recvWindowConn
|
||||
}
|
||||
if *recvWindow != 0 {
|
||||
config.ReceiveWindow = *recvWindow
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func client(args []string) {
|
||||
config, err := loadCmdClientConfig(args)
|
||||
if err != nil {
|
||||
log.Fatalln("Unable to load configuration:", err.Error())
|
||||
}
|
||||
if err := config.Check(); err != nil {
|
||||
log.Fatalln("Configuration error:", err.Error())
|
||||
}
|
||||
if len(config.Name) == 0 {
|
||||
usr, err := user.Current()
|
||||
if err == nil {
|
||||
config.Name = usr.Name
|
||||
}
|
||||
}
|
||||
fmt.Printf("Configuration loaded: %+v\n", config)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
NextProtos: []string{forwarder.TLSAppProtocol},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
// Load CA
|
||||
if len(config.CustomCAFile) > 0 {
|
||||
bs, err := ioutil.ReadFile(config.CustomCAFile)
|
||||
if err != nil {
|
||||
log.Fatalln("Unable to load CA file:", err)
|
||||
}
|
||||
cp := x509.NewCertPool()
|
||||
if !cp.AppendCertsFromPEM(bs) {
|
||||
log.Fatalln("Unable to parse CA file", config.CustomCAFile)
|
||||
}
|
||||
tlsConfig.RootCAs = cp
|
||||
}
|
||||
|
||||
logChan := make(chan string, 4)
|
||||
|
||||
go func() {
|
||||
_, err = forwarder.NewClient(config.ListenAddr, config.ServerAddr, forwarder.ClientConfig{
|
||||
Name: config.Name,
|
||||
TLSConfig: tlsConfig,
|
||||
Speed: &forwarder.Speed{
|
||||
SendBPS: uint64(config.UpMbps) * mbpsToBps,
|
||||
ReceiveBPS: uint64(config.DownMbps) * mbpsToBps,
|
||||
},
|
||||
MaxReceiveWindowPerConnection: config.ReceiveWindowConn,
|
||||
MaxReceiveWindow: config.ReceiveWindow,
|
||||
CongestionFactory: func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos {
|
||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||
},
|
||||
}, forwarder.ClientCallbacks{
|
||||
ServerConnectedCallback: func(addr net.Addr, banner string, cSend uint64, cRecv uint64) {
|
||||
logChan <- fmt.Sprintf("Connected to server %s, negotiated speed in Mbps: Up %d / Down %d",
|
||||
addr.String(), cSend/mbpsToBps, cRecv/mbpsToBps)
|
||||
logChan <- fmt.Sprintf("Server banner: [%s]", banner)
|
||||
},
|
||||
ServerErrorCallback: func(err error) {
|
||||
logChan <- fmt.Sprintf("Error connecting to the server: %s", err.Error())
|
||||
},
|
||||
NewTCPConnectionCallback: func(addr net.Addr) {
|
||||
logChan <- fmt.Sprintf("New connection: %s", addr.String())
|
||||
},
|
||||
TCPConnectionClosedCallback: func(addr net.Addr, err error) {
|
||||
logChan <- fmt.Sprintf("Connection %s closed: %s", addr.String(), err.Error())
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln("Client startup failure:", err)
|
||||
} else {
|
||||
log.Println("The client is now up and running :)")
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
logStr := <-logChan
|
||||
if len(logStr) == 0 {
|
||||
break
|
||||
}
|
||||
log.Println(logStr)
|
||||
}
|
||||
|
||||
}
|
76
cmd/forwarder/config.go
Normal file
76
cmd/forwarder/config.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type CmdClientConfig struct {
|
||||
ListenAddr string `json:"listen"`
|
||||
ServerAddr string `json:"server"`
|
||||
Name string `json:"name"`
|
||||
Insecure bool `json:"insecure"`
|
||||
CustomCAFile string `json:"ca"`
|
||||
UpMbps int `json:"up_mbps"`
|
||||
DownMbps int `json:"down_mbps"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn"`
|
||||
ReceiveWindow uint64 `json:"recv_window"`
|
||||
}
|
||||
|
||||
func (c *CmdClientConfig) Check() error {
|
||||
if len(c.ListenAddr) == 0 {
|
||||
return errors.New("no listen address")
|
||||
}
|
||||
if len(c.ServerAddr) == 0 {
|
||||
return errors.New("no server address")
|
||||
}
|
||||
if c.UpMbps <= 0 || c.DownMbps <= 0 {
|
||||
return errors.New("invalid speed")
|
||||
}
|
||||
if (c.ReceiveWindowConn != 0 && c.ReceiveWindowConn < 65536) ||
|
||||
(c.ReceiveWindow != 0 && c.ReceiveWindow < 65536) {
|
||||
return errors.New("invalid receive window size")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ForwardEntry struct {
|
||||
ListenAddr string `json:"listen"`
|
||||
RemoteAddr string `json:"remote"`
|
||||
}
|
||||
|
||||
func (e *ForwardEntry) String() string {
|
||||
return fmt.Sprintf("%s <-> %s", e.ListenAddr, e.RemoteAddr)
|
||||
}
|
||||
|
||||
type CmdServerConfig struct {
|
||||
Entries []ForwardEntry `json:"entries"`
|
||||
Banner string `json:"banner"`
|
||||
CertFile string `json:"cert"`
|
||||
KeyFile string `json:"key"`
|
||||
UpMbps int `json:"up_mbps"`
|
||||
DownMbps int `json:"down_mbps"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn"`
|
||||
ReceiveWindowClient uint64 `json:"recv_window_client"`
|
||||
MaxConnClient int `json:"max_conn_client"`
|
||||
}
|
||||
|
||||
func (c *CmdServerConfig) Check() error {
|
||||
if len(c.Entries) == 0 {
|
||||
return errors.New("no entries")
|
||||
}
|
||||
if len(c.CertFile) == 0 || len(c.KeyFile) == 0 {
|
||||
return errors.New("TLS cert or key not provided")
|
||||
}
|
||||
if c.UpMbps < 0 || c.DownMbps < 0 {
|
||||
return errors.New("invalid speed")
|
||||
}
|
||||
if (c.ReceiveWindowConn != 0 && c.ReceiveWindowConn < 65536) ||
|
||||
(c.ReceiveWindowClient != 0 && c.ReceiveWindowClient < 65536) {
|
||||
return errors.New("invalid receive window size")
|
||||
}
|
||||
if c.MaxConnClient < 0 {
|
||||
return errors.New("invalid max connections per client")
|
||||
}
|
||||
return nil
|
||||
}
|
40
cmd/forwarder/flags.go
Normal file
40
cmd/forwarder/flags.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type optionalBoolFlag struct {
|
||||
Exists bool
|
||||
Value bool
|
||||
}
|
||||
|
||||
func (flag *optionalBoolFlag) String() string {
|
||||
return strconv.FormatBool(flag.Value)
|
||||
}
|
||||
|
||||
func (flag *optionalBoolFlag) Set(s string) error {
|
||||
v, err := strconv.ParseBool(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flag.Exists = true
|
||||
flag.Value = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *optionalBoolFlag) IsBoolFlag() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type stringSliceFlag []string
|
||||
|
||||
func (flag *stringSliceFlag) String() string {
|
||||
return strings.Join(*flag, ";")
|
||||
}
|
||||
|
||||
func (flag *stringSliceFlag) Set(s string) error {
|
||||
*flag = append(*flag, s)
|
||||
return nil
|
||||
}
|
26
cmd/forwarder/main.go
Normal file
26
cmd/forwarder/main.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println()
|
||||
fmt.Printf("Usage: %s MODE [OPTIONS]\n\n"+
|
||||
"Modes: server / client\n"+
|
||||
"Use -h to see the available options for a mode.\n\n", os.Args[0])
|
||||
return
|
||||
}
|
||||
mode := strings.ToLower(strings.TrimSpace(os.Args[1]))
|
||||
switch mode {
|
||||
case "server", "s":
|
||||
server(os.Args[2:])
|
||||
case "client", "c":
|
||||
client(os.Args[2:])
|
||||
default:
|
||||
fmt.Println("Invalid mode:", mode)
|
||||
}
|
||||
}
|
204
cmd/forwarder/server.go
Normal file
204
cmd/forwarder/server.go
Normal file
|
@ -0,0 +1,204 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/forwarder"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const mbpsToBps = 125000
|
||||
|
||||
func loadCmdServerConfig(args []string) (CmdServerConfig, error) {
|
||||
fs := flag.NewFlagSet("server", flag.ContinueOnError)
|
||||
// Config file
|
||||
configFile := fs.String("config", "", "Configuration file path")
|
||||
// Entries
|
||||
var entries stringSliceFlag
|
||||
fs.Var(&entries, "entry", "Add a forwarding entry. Separate the listen address and the remote address with a comma. You can add this option multiple times. Example: localhost:444,google.com:443")
|
||||
// Banner
|
||||
banner := fs.String("banner", "", "A banner to present to clients")
|
||||
// Cert file
|
||||
certFile := fs.String("cert", "", "TLS certificate file")
|
||||
// Key file
|
||||
keyFile := fs.String("key", "", "TLS key file")
|
||||
// Up Mbps
|
||||
upMbps := fs.Int("up-mbps", 0, "Max upload speed per client in Mbps")
|
||||
// Down Mbps
|
||||
downMbps := fs.Int("down-mbps", 0, "Max download speed per client in Mbps")
|
||||
// Receive window conn
|
||||
recvWindowConn := fs.Uint64("recv-window-conn", 0, "Max receive window size per connection")
|
||||
// Receive window client
|
||||
recvWindowClient := fs.Uint64("recv-window-client", 0, "Max receive window size per client")
|
||||
// Max conn client
|
||||
maxConnClient := fs.Int("max-conn-client", 0, "Max simultaneous connections allowed per client")
|
||||
// Parse
|
||||
if err := fs.Parse(args); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
// Put together the config
|
||||
var config CmdServerConfig
|
||||
// Load from file first
|
||||
if len(*configFile) > 0 {
|
||||
cb, err := ioutil.ReadFile(*configFile)
|
||||
if err != nil {
|
||||
return CmdServerConfig{}, err
|
||||
}
|
||||
if err := json.Unmarshal(cb, &config); err != nil {
|
||||
return CmdServerConfig{}, err
|
||||
}
|
||||
}
|
||||
// Then CLI options can override config
|
||||
if len(entries) > 0 {
|
||||
fe, err := flagToEntries(entries)
|
||||
if err != nil {
|
||||
return CmdServerConfig{}, err
|
||||
}
|
||||
config.Entries = append(config.Entries, fe...)
|
||||
}
|
||||
if len(*banner) > 0 {
|
||||
config.Banner = *banner
|
||||
}
|
||||
if len(*certFile) > 0 {
|
||||
config.CertFile = *certFile
|
||||
}
|
||||
if len(*keyFile) > 0 {
|
||||
config.KeyFile = *keyFile
|
||||
}
|
||||
if *upMbps != 0 {
|
||||
config.UpMbps = *upMbps
|
||||
}
|
||||
if *downMbps != 0 {
|
||||
config.DownMbps = *downMbps
|
||||
}
|
||||
if *recvWindowConn != 0 {
|
||||
config.ReceiveWindowConn = *recvWindowConn
|
||||
}
|
||||
if *recvWindowClient != 0 {
|
||||
config.ReceiveWindowClient = *recvWindowClient
|
||||
}
|
||||
if *maxConnClient != 0 {
|
||||
config.MaxConnClient = *maxConnClient
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func flagToEntries(f stringSliceFlag) ([]ForwardEntry, error) {
|
||||
out := make([]ForwardEntry, len(f))
|
||||
for i, entry := range f {
|
||||
es := strings.Split(entry, ",")
|
||||
if len(es) != 2 {
|
||||
return nil, fmt.Errorf("incorrect entry syntax: %s", entry)
|
||||
}
|
||||
out[i] = ForwardEntry{
|
||||
ListenAddr: es[0],
|
||||
RemoteAddr: es[1],
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func server(args []string) {
|
||||
config, err := loadCmdServerConfig(args)
|
||||
if err != nil {
|
||||
log.Fatalln("Unable to load configuration:", err.Error())
|
||||
}
|
||||
if err := config.Check(); err != nil {
|
||||
log.Fatalln("Configuration error:", err.Error())
|
||||
}
|
||||
fmt.Printf("Configuration loaded: %+v\n", config)
|
||||
// Load cert
|
||||
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
|
||||
if err != nil {
|
||||
log.Fatalln("Unable to load the certificate:", err)
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
NextProtos: []string{forwarder.TLSAppProtocol},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
logChan := make(chan string, 4)
|
||||
|
||||
go func() {
|
||||
server := forwarder.NewServer(forwarder.ServerConfig{
|
||||
BannerMessage: config.Banner,
|
||||
TLSConfig: tlsConfig,
|
||||
MaxSpeedPerClient: &forwarder.Speed{
|
||||
SendBPS: uint64(config.UpMbps) * mbpsToBps,
|
||||
ReceiveBPS: uint64(config.DownMbps) * mbpsToBps,
|
||||
},
|
||||
MaxReceiveWindowPerConnection: config.ReceiveWindowConn,
|
||||
MaxReceiveWindowPerClient: config.ReceiveWindowClient,
|
||||
MaxConnectionPerClient: config.MaxConnClient,
|
||||
CongestionFactory: func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos {
|
||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||
},
|
||||
}, forwarder.ServerCallbacks{
|
||||
ClientConnectedCallback: func(listenAddr string, clientAddr net.Addr, name string, sSend uint64, sRecv uint64) {
|
||||
if len(name) > 0 {
|
||||
logChan <- fmt.Sprintf("[%s] Client %s (%s) connected, negotiated speed in Mbps: Up %d / Down %d",
|
||||
listenAddr, clientAddr.String(), name, sSend/mbpsToBps, sRecv/mbpsToBps)
|
||||
} else {
|
||||
logChan <- fmt.Sprintf("[%s] Client %s connected, negotiated speed in Mbps: Up %d / Down %d",
|
||||
listenAddr, clientAddr.String(), sSend/mbpsToBps, sRecv/mbpsToBps)
|
||||
}
|
||||
},
|
||||
ClientDisconnectedCallback: func(listenAddr string, clientAddr net.Addr, name string, err error) {
|
||||
if len(name) > 0 {
|
||||
logChan <- fmt.Sprintf("[%s] Client %s (%s) disconnected: %s",
|
||||
listenAddr, clientAddr.String(), name, err.Error())
|
||||
} else {
|
||||
logChan <- fmt.Sprintf("[%s] Client %s disconnected: %s",
|
||||
listenAddr, clientAddr.String(), err.Error())
|
||||
}
|
||||
},
|
||||
ClientNewStreamCallback: func(listenAddr string, clientAddr net.Addr, name string, id int) {
|
||||
if len(name) > 0 {
|
||||
logChan <- fmt.Sprintf("[%s] Client %s (%s) opened stream ID %d",
|
||||
listenAddr, clientAddr.String(), name, id)
|
||||
} else {
|
||||
logChan <- fmt.Sprintf("[%s] Client %s opened stream ID %d",
|
||||
listenAddr, clientAddr.String(), id)
|
||||
}
|
||||
},
|
||||
ClientStreamClosedCallback: func(listenAddr string, clientAddr net.Addr, name string, id int, err error) {
|
||||
if len(name) > 0 {
|
||||
logChan <- fmt.Sprintf("[%s] Client %s (%s) closed stream ID %d: %s",
|
||||
listenAddr, clientAddr.String(), name, id, err.Error())
|
||||
} else {
|
||||
logChan <- fmt.Sprintf("[%s] Client %s closed stream ID %d: %s",
|
||||
listenAddr, clientAddr.String(), id, err.Error())
|
||||
}
|
||||
},
|
||||
TCPErrorCallback: func(listenAddr string, remoteAddr string, err error) {
|
||||
logChan <- fmt.Sprintf("[%s] TCP error when connecting to %s: %s",
|
||||
listenAddr, remoteAddr, err.Error())
|
||||
},
|
||||
})
|
||||
for _, entry := range config.Entries {
|
||||
log.Println("Starting", entry.String(), "...")
|
||||
if err := server.Add(entry.ListenAddr, entry.RemoteAddr); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
log.Println("The server is now up and running :)")
|
||||
}()
|
||||
|
||||
for {
|
||||
logStr := <-logChan
|
||||
if len(logStr) == 0 {
|
||||
break
|
||||
}
|
||||
log.Println(logStr)
|
||||
}
|
||||
|
||||
}
|
9
go.mod
Normal file
9
go.mod
Normal file
|
@ -0,0 +1,9 @@
|
|||
module github.com/tobyxdd/hysteria
|
||||
|
||||
go 1.14
|
||||
|
||||
require github.com/golang/protobuf v1.3.1
|
||||
|
||||
require github.com/lucas-clemente/quic-go v0.15.2
|
||||
|
||||
replace github.com/lucas-clemente/quic-go => github.com/tobyxdd/quic-go v0.1.1-tquic-1
|
211
go.sum
Normal file
211
go.sum
Normal file
|
@ -0,0 +1,211 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75 h1:3ILjVyslFbc4jl1w5TWuvvslFD/nDfR2H8tVaMVLrEY=
|
||||
github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75/go.mod h1:uAXEEpARkRhCZfEvy/y0Jcc888f9tHCc1W7/UeEtreE=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.4.0 h1:Rd1kQnQu0Hq3qvJppYSG0HtP+f5LPPUiDswTLiEegLg=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
|
||||
github.com/marten-seemann/qtls v0.8.0 h1:aj+MPLibzKByw8CmG0WvWgbtBkctYPAXeB11cQJC8mo=
|
||||
github.com/marten-seemann/qtls v0.8.0/go.mod h1:Lao6jDqlCfxyLKYFmZXGm2LSHBgVn+P+ROOex6YkT+k=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
|
||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
|
||||
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tobyxdd/quic-go v0.1.1-tquic-1 h1:KDhDFNe+IlI+ZOvEG6wA5JcNq8OUO029OYi8bq7XnSU=
|
||||
github.com/tobyxdd/quic-go v0.1.1-tquic-1/go.mod h1:qxmO5Y4ZMhdNkunGfxuZnZXnJwYpW9vjQkyrZ7BsgUI=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
200
internal/forwarder/client.go
Normal file
200
internal/forwarder/client.go
Normal file
|
@ -0,0 +1,200 @@
|
|||
package forwarder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/tobyxdd/hysteria/internal/utils"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type QUICClient struct {
|
||||
inboundBytes, outboundBytes uint64 // atomic
|
||||
|
||||
reconnectMutex sync.Mutex
|
||||
quicSession quic.Session
|
||||
listener net.Listener
|
||||
remoteAddr string
|
||||
name string
|
||||
tlsConfig *tls.Config
|
||||
sendBPS, recvBPS uint64
|
||||
recvWindowConn, recvWindow uint64
|
||||
closed bool
|
||||
|
||||
newCongestion CongestionFactory
|
||||
onServerConnected ServerConnectedCallback
|
||||
onServerError ServerErrorCallback
|
||||
onNewTCPConnection NewTCPConnectionCallback
|
||||
onTCPConnectionClosed TCPConnectionClosedCallback
|
||||
}
|
||||
|
||||
func NewQUICClient(addr string, remoteAddr string, name string, tlsConfig *tls.Config,
|
||||
sendBPS uint64, recvBPS uint64, recvWindowConn uint64, recvWindow uint64,
|
||||
newCongestion CongestionFactory,
|
||||
onServerConnected ServerConnectedCallback,
|
||||
onServerError ServerErrorCallback,
|
||||
onNewTCPConnection NewTCPConnectionCallback,
|
||||
onTCPConnectionClosed TCPConnectionClosedCallback) (*QUICClient, error) {
|
||||
// Local TCP listener
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &QUICClient{
|
||||
listener: listener,
|
||||
remoteAddr: remoteAddr,
|
||||
name: name,
|
||||
tlsConfig: tlsConfig,
|
||||
sendBPS: sendBPS,
|
||||
recvBPS: recvBPS,
|
||||
recvWindowConn: recvWindowConn,
|
||||
recvWindow: recvWindow,
|
||||
newCongestion: newCongestion,
|
||||
onServerConnected: onServerConnected,
|
||||
onServerError: onServerError,
|
||||
onNewTCPConnection: onNewTCPConnection,
|
||||
onTCPConnectionClosed: onTCPConnectionClosed,
|
||||
}
|
||||
if err := c.connectToServer(); err != nil {
|
||||
_ = c.listener.Close()
|
||||
return nil, err
|
||||
}
|
||||
go c.acceptLoop()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *QUICClient) Close() error {
|
||||
err1 := c.listener.Close()
|
||||
c.reconnectMutex.Lock()
|
||||
err2 := c.quicSession.CloseWithError(closeErrorCodeGeneric, "generic")
|
||||
c.closed = true
|
||||
c.reconnectMutex.Unlock()
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
return err2
|
||||
}
|
||||
|
||||
func (c *QUICClient) Stats() (string, uint64, uint64) {
|
||||
return c.remoteAddr, atomic.LoadUint64(&c.inboundBytes), atomic.LoadUint64(&c.outboundBytes)
|
||||
}
|
||||
|
||||
func (c *QUICClient) acceptLoop() {
|
||||
for {
|
||||
conn, err := c.listener.Accept()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
go c.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *QUICClient) connectToServer() error {
|
||||
qs, err := quic.DialAddr(c.remoteAddr, c.tlsConfig, &quic.Config{
|
||||
MaxReceiveStreamFlowControlWindow: c.recvWindowConn,
|
||||
MaxReceiveConnectionFlowControlWindow: c.recvWindow,
|
||||
KeepAlive: true,
|
||||
})
|
||||
if err != nil {
|
||||
c.onServerError(err)
|
||||
return err
|
||||
}
|
||||
// Control stream
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), controlStreamTimeout)
|
||||
ctlStream, err := qs.OpenStreamSync(ctx)
|
||||
ctxCancel()
|
||||
if err != nil {
|
||||
_ = qs.CloseWithError(closeErrorCodeProtocolFailure, "control stream error")
|
||||
c.onServerError(err)
|
||||
return err
|
||||
}
|
||||
banner, cSendBPS, cRecvBPS, err := handleControlStream(qs, ctlStream, c.name, c.sendBPS, c.recvBPS, c.newCongestion)
|
||||
if err != nil {
|
||||
_ = qs.CloseWithError(closeErrorCodeProtocolFailure, "control stream handling error")
|
||||
c.onServerError(err)
|
||||
return err
|
||||
}
|
||||
// All good
|
||||
c.quicSession = qs
|
||||
c.onServerConnected(qs.RemoteAddr(), banner, cSendBPS, cRecvBPS)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *QUICClient) openStreamWithReconnect() (quic.Stream, error) {
|
||||
c.reconnectMutex.Lock()
|
||||
defer c.reconnectMutex.Unlock()
|
||||
if c.closed {
|
||||
return nil, errors.New("client closed")
|
||||
}
|
||||
stream, err := c.quicSession.OpenStream()
|
||||
if err == nil {
|
||||
// All good
|
||||
return stream, nil
|
||||
}
|
||||
// Something is wrong
|
||||
c.onServerError(err)
|
||||
if nErr, ok := err.(net.Error); ok && nErr.Temporary() {
|
||||
// Temporary error, just return
|
||||
return nil, err
|
||||
}
|
||||
// Permanent error, need to reconnect
|
||||
if err := c.connectToServer(); err != nil {
|
||||
// Still error, oops
|
||||
return nil, err
|
||||
}
|
||||
// We are not going to try again even if it still fails the second time
|
||||
stream, err = c.quicSession.OpenStream()
|
||||
if err != nil {
|
||||
c.onServerError(err)
|
||||
}
|
||||
return stream, err
|
||||
}
|
||||
|
||||
// Negotiate speed, return banner, send & receive speed
|
||||
func handleControlStream(qs quic.Session, stream quic.Stream, name string, sendBPS uint64, recvBPS uint64,
|
||||
newCongestion CongestionFactory) (string, uint64, uint64, error) {
|
||||
err := writeClientSpeedRequest(stream, &ClientSpeedRequest{
|
||||
Name: name,
|
||||
Speed: &Speed{
|
||||
SendBps: sendBPS,
|
||||
ReceiveBps: recvBPS,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", 0, 0, err
|
||||
}
|
||||
// Response
|
||||
resp, err := readServerSpeedResponse(stream)
|
||||
if err != nil {
|
||||
return "", 0, 0, err
|
||||
}
|
||||
// Set the congestion accordingly
|
||||
if newCongestion != nil {
|
||||
qs.SetCongestion(newCongestion(resp.Speed.ReceiveBps))
|
||||
}
|
||||
return resp.Banner, resp.Speed.ReceiveBps, resp.Speed.SendBps, nil
|
||||
}
|
||||
|
||||
func (c *QUICClient) handleConn(conn net.Conn) {
|
||||
c.onNewTCPConnection(conn.RemoteAddr())
|
||||
defer conn.Close()
|
||||
stream, err := c.openStreamWithReconnect()
|
||||
if err != nil {
|
||||
c.onTCPConnectionClosed(conn.RemoteAddr(), err)
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
// From TCP to QUIC
|
||||
go func() {
|
||||
_ = utils.Pipe(conn, stream, &c.outboundBytes)
|
||||
_ = conn.Close()
|
||||
_ = stream.Close()
|
||||
}()
|
||||
// From QUIC to TCP
|
||||
err = utils.Pipe(stream, conn, &c.inboundBytes)
|
||||
// Closed
|
||||
c.onTCPConnectionClosed(conn.RemoteAddr(), err)
|
||||
}
|
67
internal/forwarder/control.go
Normal file
67
internal/forwarder/control.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package forwarder
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
closeErrorCodeGeneric = 0
|
||||
closeErrorCodeProtocolFailure = 1
|
||||
)
|
||||
|
||||
func readDataBlock(r io.Reader) ([]byte, error) {
|
||||
var sz uint32
|
||||
if err := binary.Read(r, controlProtocolEndian, &sz); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, sz)
|
||||
_, err := io.ReadFull(r, buf)
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func writeDataBlock(w io.Writer, data []byte) error {
|
||||
sz := uint32(len(data))
|
||||
if err := binary.Write(w, controlProtocolEndian, &sz); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := w.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func readClientSpeedRequest(r io.Reader) (*ClientSpeedRequest, error) {
|
||||
bs, err := readDataBlock(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var req ClientSpeedRequest
|
||||
err = proto.Unmarshal(bs, &req)
|
||||
return &req, err
|
||||
}
|
||||
|
||||
func writeClientSpeedRequest(w io.Writer, req *ClientSpeedRequest) error {
|
||||
bs, err := proto.Marshal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeDataBlock(w, bs)
|
||||
}
|
||||
|
||||
func readServerSpeedResponse(r io.Reader) (*ServerSpeedResponse, error) {
|
||||
bs, err := readDataBlock(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp ServerSpeedResponse
|
||||
err = proto.Unmarshal(bs, &resp)
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
func writeServerSpeedResponse(w io.Writer, resp *ServerSpeedResponse) error {
|
||||
bs, err := proto.Marshal(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeDataBlock(w, bs)
|
||||
}
|
206
internal/forwarder/control.pb.go
Normal file
206
internal/forwarder/control.pb.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: control.proto
|
||||
|
||||
package forwarder
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type Speed struct {
|
||||
SendBps uint64 `protobuf:"varint,1,opt,name=send_bps,json=sendBps,proto3" json:"send_bps,omitempty"`
|
||||
ReceiveBps uint64 `protobuf:"varint,2,opt,name=receive_bps,json=receiveBps,proto3" json:"receive_bps,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Speed) Reset() { *m = Speed{} }
|
||||
func (m *Speed) String() string { return proto.CompactTextString(m) }
|
||||
func (*Speed) ProtoMessage() {}
|
||||
func (*Speed) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0c5120591600887d, []int{0}
|
||||
}
|
||||
|
||||
func (m *Speed) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Speed.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Speed) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Speed.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Speed) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Speed.Merge(m, src)
|
||||
}
|
||||
func (m *Speed) XXX_Size() int {
|
||||
return xxx_messageInfo_Speed.Size(m)
|
||||
}
|
||||
func (m *Speed) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Speed.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Speed proto.InternalMessageInfo
|
||||
|
||||
func (m *Speed) GetSendBps() uint64 {
|
||||
if m != nil {
|
||||
return m.SendBps
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Speed) GetReceiveBps() uint64 {
|
||||
if m != nil {
|
||||
return m.ReceiveBps
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ClientSpeedRequest struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Speed *Speed `protobuf:"bytes,2,opt,name=speed,proto3" json:"speed,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ClientSpeedRequest) Reset() { *m = ClientSpeedRequest{} }
|
||||
func (m *ClientSpeedRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*ClientSpeedRequest) ProtoMessage() {}
|
||||
func (*ClientSpeedRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0c5120591600887d, []int{1}
|
||||
}
|
||||
|
||||
func (m *ClientSpeedRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ClientSpeedRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ClientSpeedRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ClientSpeedRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ClientSpeedRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ClientSpeedRequest.Merge(m, src)
|
||||
}
|
||||
func (m *ClientSpeedRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_ClientSpeedRequest.Size(m)
|
||||
}
|
||||
func (m *ClientSpeedRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ClientSpeedRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ClientSpeedRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *ClientSpeedRequest) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *ClientSpeedRequest) GetSpeed() *Speed {
|
||||
if m != nil {
|
||||
return m.Speed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ServerSpeedResponse struct {
|
||||
Banner string `protobuf:"bytes,1,opt,name=banner,proto3" json:"banner,omitempty"`
|
||||
Limited bool `protobuf:"varint,2,opt,name=limited,proto3" json:"limited,omitempty"`
|
||||
Limit *Speed `protobuf:"bytes,3,opt,name=limit,proto3" json:"limit,omitempty"`
|
||||
Speed *Speed `protobuf:"bytes,4,opt,name=speed,proto3" json:"speed,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ServerSpeedResponse) Reset() { *m = ServerSpeedResponse{} }
|
||||
func (m *ServerSpeedResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*ServerSpeedResponse) ProtoMessage() {}
|
||||
func (*ServerSpeedResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0c5120591600887d, []int{2}
|
||||
}
|
||||
|
||||
func (m *ServerSpeedResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ServerSpeedResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ServerSpeedResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ServerSpeedResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ServerSpeedResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ServerSpeedResponse.Merge(m, src)
|
||||
}
|
||||
func (m *ServerSpeedResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_ServerSpeedResponse.Size(m)
|
||||
}
|
||||
func (m *ServerSpeedResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ServerSpeedResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ServerSpeedResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *ServerSpeedResponse) GetBanner() string {
|
||||
if m != nil {
|
||||
return m.Banner
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *ServerSpeedResponse) GetLimited() bool {
|
||||
if m != nil {
|
||||
return m.Limited
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *ServerSpeedResponse) GetLimit() *Speed {
|
||||
if m != nil {
|
||||
return m.Limit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ServerSpeedResponse) GetSpeed() *Speed {
|
||||
if m != nil {
|
||||
return m.Speed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Speed)(nil), "forwarder.Speed")
|
||||
proto.RegisterType((*ClientSpeedRequest)(nil), "forwarder.ClientSpeedRequest")
|
||||
proto.RegisterType((*ServerSpeedResponse)(nil), "forwarder.ServerSpeedResponse")
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterFile("control.proto", fileDescriptor_0c5120591600887d)
|
||||
}
|
||||
|
||||
var fileDescriptor_0c5120591600887d = []byte{
|
||||
// 220 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0x4d, 0x4a, 0xc6, 0x30,
|
||||
0x10, 0x86, 0xa9, 0xf6, 0xfb, 0x9b, 0x0f, 0x41, 0x46, 0x90, 0xba, 0x52, 0xba, 0x10, 0x57, 0x5d,
|
||||
0xe8, 0x0d, 0xbe, 0x5e, 0x40, 0xd2, 0x03, 0x48, 0x7f, 0x46, 0x08, 0xb4, 0x49, 0x9c, 0x89, 0xf5,
|
||||
0x28, 0x5e, 0x57, 0x3a, 0x8d, 0xba, 0xd2, 0xdd, 0xbc, 0x79, 0x92, 0x67, 0x5e, 0x02, 0x17, 0xbd,
|
||||
0x77, 0x91, 0xfd, 0x58, 0x05, 0xf6, 0xd1, 0xe3, 0xe1, 0xd5, 0xf3, 0x47, 0xcb, 0x03, 0x71, 0x59,
|
||||
0xc3, 0xa6, 0x09, 0x44, 0x03, 0xde, 0xc0, 0x5e, 0xc8, 0x0d, 0x2f, 0x5d, 0x90, 0x22, 0xbb, 0xcb,
|
||||
0x1e, 0x72, 0xb3, 0x5b, 0xf2, 0x29, 0x08, 0xde, 0xc2, 0x91, 0xa9, 0x27, 0x3b, 0x93, 0xd2, 0x33,
|
||||
0xa5, 0x90, 0x8e, 0x4e, 0x41, 0xca, 0x67, 0xc0, 0x7a, 0xb4, 0xe4, 0xa2, 0xaa, 0x0c, 0xbd, 0xbd,
|
||||
0x93, 0x44, 0x44, 0xc8, 0x5d, 0x3b, 0x91, 0xda, 0x0e, 0x46, 0x67, 0xbc, 0x87, 0x8d, 0x2c, 0x77,
|
||||
0x54, 0x72, 0x7c, 0xbc, 0xac, 0x7e, 0x9a, 0x54, 0xeb, 0xdb, 0x15, 0x97, 0x9f, 0x19, 0x5c, 0x35,
|
||||
0xc4, 0x33, 0x71, 0x52, 0x4a, 0xf0, 0x4e, 0x08, 0xaf, 0x61, 0xdb, 0xb5, 0xce, 0x11, 0x27, 0x6b,
|
||||
0x4a, 0x58, 0xc0, 0x6e, 0xb4, 0x93, 0x8d, 0xc9, 0xbc, 0x37, 0xdf, 0x71, 0xd9, 0xa8, 0x63, 0x71,
|
||||
0xfe, 0xd7, 0x46, 0xc5, 0xbf, 0xcd, 0xf2, 0x7f, 0x9b, 0x75, 0x5b, 0xfd, 0xc2, 0xa7, 0xaf, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0xb2, 0x10, 0x5a, 0xf2, 0x53, 0x01, 0x00, 0x00,
|
||||
}
|
19
internal/forwarder/control.proto
Normal file
19
internal/forwarder/control.proto
Normal file
|
@ -0,0 +1,19 @@
|
|||
syntax = "proto3";
|
||||
package forwarder;
|
||||
|
||||
message Speed {
|
||||
uint64 send_bps = 1;
|
||||
uint64 receive_bps = 2;
|
||||
}
|
||||
|
||||
message ClientSpeedRequest {
|
||||
string name = 1;
|
||||
Speed speed = 2;
|
||||
}
|
||||
|
||||
message ServerSpeedResponse {
|
||||
string banner = 1;
|
||||
bool limited = 2;
|
||||
Speed limit = 3;
|
||||
Speed speed = 4;
|
||||
}
|
10
internal/forwarder/params.go
Normal file
10
internal/forwarder/params.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package forwarder
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
const controlStreamTimeout = 10 * time.Second
|
||||
|
||||
var controlProtocolEndian = binary.BigEndian
|
3
internal/forwarder/protogen.go
Normal file
3
internal/forwarder/protogen.go
Normal file
|
@ -0,0 +1,3 @@
|
|||
package forwarder
|
||||
|
||||
//go:generate protoc --go_out=. control.proto
|
173
internal/forwarder/server.go
Normal file
173
internal/forwarder/server.go
Normal file
|
@ -0,0 +1,173 @@
|
|||
package forwarder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/tobyxdd/hysteria/internal/utils"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type QUICServer struct {
|
||||
inboundBytes, outboundBytes uint64 // atomic
|
||||
|
||||
listener quic.Listener
|
||||
remoteAddr string
|
||||
banner string
|
||||
sendBPS, recvBPS uint64
|
||||
|
||||
newCongestion CongestionFactory
|
||||
onClientConnected ClientConnectedCallback
|
||||
onClientDisconnected ClientDisconnectedCallback
|
||||
onClientNewStream ClientNewStreamCallback
|
||||
onClientStreamClosed ClientStreamClosedCallback
|
||||
onTCPError TCPErrorCallback
|
||||
}
|
||||
|
||||
func NewQUICServer(addr string, remoteAddr string, banner string, tlsConfig *tls.Config,
|
||||
sendBPS uint64, recvBPS uint64, recvWindowConn uint64, recvWindowClients uint64,
|
||||
clientMaxConn int, newCongestion CongestionFactory,
|
||||
onClientConnected ClientConnectedCallback,
|
||||
onClientDisconnected ClientDisconnectedCallback,
|
||||
onClientNewStream ClientNewStreamCallback,
|
||||
onClientStreamClosed ClientStreamClosedCallback,
|
||||
onTCPError TCPErrorCallback) (*QUICServer, error) {
|
||||
listener, err := quic.ListenAddr(addr, tlsConfig, &quic.Config{
|
||||
MaxReceiveStreamFlowControlWindow: recvWindowConn,
|
||||
MaxReceiveConnectionFlowControlWindow: recvWindowClients,
|
||||
MaxIncomingStreams: clientMaxConn,
|
||||
KeepAlive: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &QUICServer{
|
||||
listener: listener,
|
||||
remoteAddr: remoteAddr,
|
||||
banner: banner,
|
||||
sendBPS: sendBPS,
|
||||
recvBPS: recvBPS,
|
||||
newCongestion: newCongestion,
|
||||
onClientConnected: onClientConnected,
|
||||
onClientDisconnected: onClientDisconnected,
|
||||
onClientNewStream: onClientNewStream,
|
||||
onClientStreamClosed: onClientStreamClosed,
|
||||
onTCPError: onTCPError,
|
||||
}
|
||||
go s.acceptLoop()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *QUICServer) Close() error {
|
||||
return s.listener.Close()
|
||||
}
|
||||
|
||||
func (s *QUICServer) Stats() (string, uint64, uint64) {
|
||||
return s.remoteAddr, atomic.LoadUint64(&s.inboundBytes), atomic.LoadUint64(&s.outboundBytes)
|
||||
}
|
||||
|
||||
func (s *QUICServer) acceptLoop() {
|
||||
for {
|
||||
cs, err := s.listener.Accept(context.Background())
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
go s.handleClient(cs)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *QUICServer) handleClient(cs quic.Session) {
|
||||
// Expect the client to create a control stream and send its own information
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), controlStreamTimeout)
|
||||
ctlStream, err := cs.AcceptStream(ctx)
|
||||
ctxCancel()
|
||||
if err != nil {
|
||||
_ = cs.CloseWithError(closeErrorCodeProtocolFailure, "control stream error")
|
||||
return
|
||||
}
|
||||
name, sSend, sRecv, err := s.handleControlStream(cs, ctlStream)
|
||||
if err != nil {
|
||||
_ = cs.CloseWithError(closeErrorCodeProtocolFailure, "control stream handling error")
|
||||
return
|
||||
}
|
||||
// Only after a successful exchange of information do we consider this a valid client
|
||||
s.onClientConnected(cs.RemoteAddr(), name, sSend, sRecv)
|
||||
// Start accepting streams to be forwarded
|
||||
var closeErr error
|
||||
for {
|
||||
stream, err := cs.AcceptStream(context.Background())
|
||||
if err != nil {
|
||||
closeErr = err
|
||||
break
|
||||
}
|
||||
go s.handleStream(cs.RemoteAddr(), name, stream)
|
||||
}
|
||||
s.onClientDisconnected(cs.RemoteAddr(), name, closeErr)
|
||||
_ = cs.CloseWithError(closeErrorCodeGeneric, "generic")
|
||||
}
|
||||
|
||||
// Negotiate speed & return client name
|
||||
func (s *QUICServer) handleControlStream(cs quic.Session, stream quic.Stream) (string, uint64, uint64, error) {
|
||||
req, err := readClientSpeedRequest(stream)
|
||||
if err != nil {
|
||||
return "", 0, 0, err
|
||||
}
|
||||
if req.Speed == nil || req.Speed.SendBps == 0 || req.Speed.ReceiveBps == 0 {
|
||||
return "", 0, 0, errors.New("incorrect speed information provided by the client")
|
||||
}
|
||||
limited := false
|
||||
serverSendBPS, serverReceiveBPS := req.Speed.ReceiveBps, req.Speed.SendBps
|
||||
if s.sendBPS > 0 && serverSendBPS > s.sendBPS {
|
||||
limited = true
|
||||
serverSendBPS = s.sendBPS
|
||||
}
|
||||
if s.recvBPS > 0 && serverReceiveBPS > s.recvBPS {
|
||||
limited = true
|
||||
serverReceiveBPS = s.recvBPS
|
||||
}
|
||||
// Response
|
||||
err = writeServerSpeedResponse(stream, &ServerSpeedResponse{
|
||||
Banner: s.banner,
|
||||
Limited: limited,
|
||||
Limit: &Speed{
|
||||
SendBps: s.sendBPS,
|
||||
ReceiveBps: s.recvBPS,
|
||||
},
|
||||
Speed: &Speed{
|
||||
SendBps: serverSendBPS,
|
||||
ReceiveBps: serverReceiveBPS,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", 0, 0, err
|
||||
}
|
||||
// Set the congestion accordingly
|
||||
if s.newCongestion != nil {
|
||||
cs.SetCongestion(s.newCongestion(serverSendBPS))
|
||||
}
|
||||
return req.Name, serverSendBPS, serverReceiveBPS, nil
|
||||
}
|
||||
|
||||
func (s *QUICServer) handleStream(addr net.Addr, name string, stream quic.Stream) {
|
||||
s.onClientNewStream(addr, name, int(stream.StreamID()))
|
||||
defer stream.Close()
|
||||
tcpConn, err := net.Dial("tcp", s.remoteAddr)
|
||||
if err != nil {
|
||||
s.onTCPError(s.remoteAddr, err)
|
||||
s.onClientStreamClosed(addr, name, int(stream.StreamID()), err)
|
||||
return
|
||||
}
|
||||
defer tcpConn.Close()
|
||||
// From TCP to QUIC
|
||||
go func() {
|
||||
_ = utils.Pipe(tcpConn, stream, &s.outboundBytes)
|
||||
_ = tcpConn.Close()
|
||||
_ = stream.Close()
|
||||
}()
|
||||
// From QUIC to TCP
|
||||
err = utils.Pipe(stream, tcpConn, &s.inboundBytes)
|
||||
// Closed
|
||||
s.onClientStreamClosed(addr, name, int(stream.StreamID()), err)
|
||||
}
|
21
internal/forwarder/types.go
Normal file
21
internal/forwarder/types.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package forwarder
|
||||
|
||||
import (
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"net"
|
||||
)
|
||||
|
||||
type CongestionFactory func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos
|
||||
|
||||
// For server
|
||||
type ClientConnectedCallback func(addr net.Addr, name string, sSend uint64, sRecv uint64)
|
||||
type ClientDisconnectedCallback func(addr net.Addr, name string, err error)
|
||||
type ClientNewStreamCallback func(addr net.Addr, name string, id int)
|
||||
type ClientStreamClosedCallback func(addr net.Addr, name string, id int, err error)
|
||||
type TCPErrorCallback func(remoteAddr string, err error)
|
||||
|
||||
// For client
|
||||
type ServerConnectedCallback func(addr net.Addr, banner string, cSend uint64, cRecv uint64)
|
||||
type ServerErrorCallback func(err error)
|
||||
type NewTCPConnectionCallback func(addr net.Addr)
|
||||
type TCPConnectionClosedCallback func(addr net.Addr, err error)
|
25
internal/utils/pipe.go
Normal file
25
internal/utils/pipe.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const pipeBufferSize = 16384
|
||||
|
||||
func Pipe(src, dst io.ReadWriter, atomicCounter *uint64) error {
|
||||
buf := make([]byte, pipeBufferSize)
|
||||
for {
|
||||
rn, err := src.Read(buf)
|
||||
if rn > 0 {
|
||||
wn, err := dst.Write(buf[:rn])
|
||||
atomic.AddUint64(atomicCounter, uint64(wn))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
70
pkg/congestion/brutal.go
Normal file
70
pkg/congestion/brutal.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package congestion
|
||||
|
||||
import (
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BrutalSender sends packets at a constant rate and does not react to any changes in the network environment,
|
||||
// hence the name.
|
||||
type BrutalSender struct {
|
||||
rttStats *congestion.RTTStats
|
||||
bps congestion.ByteCount
|
||||
}
|
||||
|
||||
func NewBrutalSender(bps congestion.ByteCount) *BrutalSender {
|
||||
return &BrutalSender{
|
||||
bps: bps,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BrutalSender) SetRTTStats(rttStats *congestion.RTTStats) {
|
||||
b.rttStats = rttStats
|
||||
}
|
||||
|
||||
func (b *BrutalSender) TimeUntilSend(bytesInFlight congestion.ByteCount) time.Duration {
|
||||
return time.Duration(congestion.ByteCount(time.Second) * congestion.MaxPacketSizeIPv4 / (2 * b.bps))
|
||||
}
|
||||
|
||||
func (b *BrutalSender) CanSend(bytesInFlight congestion.ByteCount) bool {
|
||||
return bytesInFlight < b.GetCongestionWindow()
|
||||
}
|
||||
|
||||
func (b *BrutalSender) GetCongestionWindow() congestion.ByteCount {
|
||||
rtt := maxDuration(b.rttStats.LatestRTT(), b.rttStats.SmoothedRTT())
|
||||
if rtt <= 0 {
|
||||
return 10240
|
||||
}
|
||||
return b.bps * congestion.ByteCount(rtt) / congestion.ByteCount(time.Second)
|
||||
}
|
||||
|
||||
func (b *BrutalSender) OnPacketSent(sentTime time.Time, bytesInFlight congestion.ByteCount,
|
||||
packetNumber congestion.PacketNumber, bytes congestion.ByteCount, isRetransmittable bool) {
|
||||
}
|
||||
|
||||
func (b *BrutalSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes congestion.ByteCount,
|
||||
priorInFlight congestion.ByteCount, eventTime time.Time) {
|
||||
}
|
||||
|
||||
func (b *BrutalSender) OnPacketLost(number congestion.PacketNumber, lostBytes congestion.ByteCount,
|
||||
priorInFlight congestion.ByteCount) {
|
||||
}
|
||||
|
||||
func (b *BrutalSender) InSlowStart() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *BrutalSender) InRecovery() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *BrutalSender) MaybeExitSlowStart() {}
|
||||
|
||||
func (b *BrutalSender) OnRetransmissionTimeout(packetsRetransmitted bool) {}
|
||||
|
||||
func maxDuration(a, b time.Duration) time.Duration {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
70
pkg/forwarder/client.go
Normal file
70
pkg/forwarder/client.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package forwarder
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"github.com/tobyxdd/hysteria/internal/forwarder"
|
||||
"net"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
qc *forwarder.QUICClient
|
||||
}
|
||||
|
||||
func NewClient(localAddr string, remoteAddr string, config ClientConfig, callbacks ClientCallbacks) (Client, error) {
|
||||
// Fix config first
|
||||
if config.Speed == nil || config.Speed.SendBPS == 0 || config.Speed.ReceiveBPS == 0 {
|
||||
return nil, errors.New("invalid speed")
|
||||
}
|
||||
if config.TLSConfig == nil {
|
||||
config.TLSConfig = &tls.Config{NextProtos: []string{TLSAppProtocol}}
|
||||
}
|
||||
if config.MaxReceiveWindowPerConnection == 0 {
|
||||
config.MaxReceiveWindowPerConnection = defaultReceiveWindowConn
|
||||
}
|
||||
if config.MaxReceiveWindow == 0 {
|
||||
config.MaxReceiveWindow = defaultReceiveWindow
|
||||
}
|
||||
qc, err := forwarder.NewQUICClient(localAddr, remoteAddr, config.Name, config.TLSConfig,
|
||||
config.Speed.SendBPS, config.Speed.ReceiveBPS,
|
||||
config.MaxReceiveWindowPerConnection, config.MaxReceiveWindow,
|
||||
forwarder.CongestionFactory(config.CongestionFactory),
|
||||
func(addr net.Addr, banner string, cSend uint64, cRecv uint64) {
|
||||
if callbacks.ServerConnectedCallback != nil {
|
||||
callbacks.ServerConnectedCallback(addr, banner, cSend, cRecv)
|
||||
}
|
||||
},
|
||||
func(err error) {
|
||||
if callbacks.ServerErrorCallback != nil {
|
||||
callbacks.ServerErrorCallback(err)
|
||||
}
|
||||
},
|
||||
func(addr net.Addr) {
|
||||
if callbacks.NewTCPConnectionCallback != nil {
|
||||
callbacks.NewTCPConnectionCallback(addr)
|
||||
}
|
||||
},
|
||||
func(addr net.Addr, err error) {
|
||||
if callbacks.TCPConnectionClosedCallback != nil {
|
||||
callbacks.TCPConnectionClosedCallback(addr, err)
|
||||
}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &client{qc: qc}, nil
|
||||
}
|
||||
|
||||
func (c *client) Stats() Stats {
|
||||
addr, in, out := c.qc.Stats()
|
||||
return Stats{
|
||||
RemoteAddr: addr,
|
||||
inboundBytes: in,
|
||||
outboundBytes: out,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Close() error {
|
||||
return c.Close()
|
||||
}
|
89
pkg/forwarder/interface.go
Normal file
89
pkg/forwarder/interface.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package forwarder
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"github.com/tobyxdd/hysteria/internal/forwarder"
|
||||
"net"
|
||||
)
|
||||
|
||||
type CongestionFactory forwarder.CongestionFactory
|
||||
|
||||
// A server can support multiple forwarding entries (listenAddr/remoteAddr pairs)
|
||||
type Server interface {
|
||||
Add(listenAddr, remoteAddr string) error
|
||||
Remove(listenAddr string) error
|
||||
Stats() map[string]Stats
|
||||
}
|
||||
|
||||
// An empty ServerConfig is a valid one
|
||||
type ServerConfig struct {
|
||||
// A banner message that will be sent to the client after the connection is established.
|
||||
// No message if not set.
|
||||
BannerMessage string
|
||||
// TLSConfig is used to configure the TLS server.
|
||||
// Use an insecure self-signed certificate if not set.
|
||||
TLSConfig *tls.Config
|
||||
// MaxSpeedPerClient is the maximum allowed sending and receiving speed for each client.
|
||||
// Sending speed will never exceed this limit, even if a client demands a larger value.
|
||||
// No restrictions if not set.
|
||||
MaxSpeedPerClient *Speed
|
||||
// Corresponds to MaxReceiveStreamFlowControlWindow in QUIC.
|
||||
MaxReceiveWindowPerConnection uint64
|
||||
// Corresponds to MaxReceiveConnectionFlowControlWindow in QUIC.
|
||||
MaxReceiveWindowPerClient uint64
|
||||
// Max number of simultaneous connections allowed for a client
|
||||
MaxConnectionPerClient int
|
||||
// Congestion factory
|
||||
CongestionFactory CongestionFactory
|
||||
}
|
||||
|
||||
type ServerCallbacks struct {
|
||||
ClientConnectedCallback func(listenAddr string, clientAddr net.Addr, name string, sSend uint64, sRecv uint64)
|
||||
ClientDisconnectedCallback func(listenAddr string, clientAddr net.Addr, name string, err error)
|
||||
ClientNewStreamCallback func(listenAddr string, clientAddr net.Addr, name string, id int)
|
||||
ClientStreamClosedCallback func(listenAddr string, clientAddr net.Addr, name string, id int, err error)
|
||||
TCPErrorCallback func(listenAddr string, remoteAddr string, err error)
|
||||
}
|
||||
|
||||
// A client supports one forwarding entry
|
||||
type Client interface {
|
||||
Stats() Stats
|
||||
Close() error
|
||||
}
|
||||
|
||||
// An empty ClientConfig is NOT a valid one, as Speed must be set
|
||||
type ClientConfig struct {
|
||||
// A client can report its name to the server after the connection is established.
|
||||
// No name if not set.
|
||||
Name string
|
||||
// TLSConfig is used to configure the TLS client.
|
||||
// Use default settings if not set.
|
||||
TLSConfig *tls.Config
|
||||
// Speed reported by the client when negotiating with the server.
|
||||
// The actual speed will also depend on the configuration of the server.
|
||||
Speed *Speed
|
||||
// Corresponds to MaxReceiveStreamFlowControlWindow in QUIC.
|
||||
MaxReceiveWindowPerConnection uint64
|
||||
// Corresponds to MaxReceiveConnectionFlowControlWindow in QUIC.
|
||||
MaxReceiveWindow uint64
|
||||
// Congestion factory
|
||||
CongestionFactory CongestionFactory
|
||||
}
|
||||
|
||||
type ClientCallbacks struct {
|
||||
ServerConnectedCallback func(addr net.Addr, banner string, cSend uint64, cRecv uint64)
|
||||
ServerErrorCallback func(err error)
|
||||
NewTCPConnectionCallback func(addr net.Addr)
|
||||
TCPConnectionClosedCallback func(addr net.Addr, err error)
|
||||
}
|
||||
|
||||
type Speed struct {
|
||||
SendBPS uint64
|
||||
ReceiveBPS uint64
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
RemoteAddr string
|
||||
inboundBytes uint64
|
||||
outboundBytes uint64
|
||||
}
|
9
pkg/forwarder/params.go
Normal file
9
pkg/forwarder/params.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package forwarder
|
||||
|
||||
const (
|
||||
TLSAppProtocol = "hysteria-forwarder"
|
||||
|
||||
defaultReceiveWindowConn = 33554432
|
||||
defaultReceiveWindow = 67108864
|
||||
defaultMaxClientConn = 100
|
||||
)
|
119
pkg/forwarder/server.go
Normal file
119
pkg/forwarder/server.go
Normal file
|
@ -0,0 +1,119 @@
|
|||
package forwarder
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"github.com/tobyxdd/hysteria/internal/forwarder"
|
||||
"math/big"
|
||||
"net"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
config ServerConfig
|
||||
callbacks ServerCallbacks
|
||||
entries map[string]*forwarder.QUICServer
|
||||
}
|
||||
|
||||
func NewServer(config ServerConfig, callbacks ServerCallbacks) Server {
|
||||
// Fix config first
|
||||
if config.TLSConfig == nil {
|
||||
config.TLSConfig = generateInsecureTLSConfig()
|
||||
}
|
||||
if config.MaxSpeedPerClient == nil {
|
||||
config.MaxSpeedPerClient = &Speed{0, 0}
|
||||
}
|
||||
if config.MaxReceiveWindowPerConnection == 0 {
|
||||
config.MaxReceiveWindowPerConnection = defaultReceiveWindowConn
|
||||
}
|
||||
if config.MaxReceiveWindowPerClient == 0 {
|
||||
config.MaxReceiveWindowPerClient = defaultReceiveWindow
|
||||
}
|
||||
if config.MaxConnectionPerClient <= 0 {
|
||||
config.MaxConnectionPerClient = defaultMaxClientConn
|
||||
}
|
||||
return &server{config: config, callbacks: callbacks, entries: make(map[string]*forwarder.QUICServer)}
|
||||
}
|
||||
|
||||
func (s *server) Add(listenAddr, remoteAddr string) error {
|
||||
qs, err := forwarder.NewQUICServer(listenAddr, remoteAddr, s.config.BannerMessage, s.config.TLSConfig,
|
||||
s.config.MaxSpeedPerClient.SendBPS, s.config.MaxSpeedPerClient.ReceiveBPS,
|
||||
s.config.MaxReceiveWindowPerConnection, s.config.MaxReceiveWindowPerClient,
|
||||
s.config.MaxConnectionPerClient, forwarder.CongestionFactory(s.config.CongestionFactory),
|
||||
func(addr net.Addr, name string, sSend uint64, sRecv uint64) {
|
||||
if s.callbacks.ClientConnectedCallback != nil {
|
||||
s.callbacks.ClientConnectedCallback(listenAddr, addr, name, sSend, sRecv)
|
||||
}
|
||||
},
|
||||
func(addr net.Addr, name string, err error) {
|
||||
if s.callbacks.ClientDisconnectedCallback != nil {
|
||||
s.callbacks.ClientDisconnectedCallback(listenAddr, addr, name, err)
|
||||
}
|
||||
},
|
||||
func(addr net.Addr, name string, id int) {
|
||||
if s.callbacks.ClientNewStreamCallback != nil {
|
||||
s.callbacks.ClientNewStreamCallback(listenAddr, addr, name, id)
|
||||
}
|
||||
},
|
||||
func(addr net.Addr, name string, id int, err error) {
|
||||
if s.callbacks.ClientStreamClosedCallback != nil {
|
||||
s.callbacks.ClientStreamClosedCallback(listenAddr, addr, name, id, err)
|
||||
}
|
||||
},
|
||||
func(remoteAddr string, err error) {
|
||||
if s.callbacks.TCPErrorCallback != nil {
|
||||
s.callbacks.TCPErrorCallback(listenAddr, remoteAddr, err)
|
||||
}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.entries[listenAddr] = qs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) Remove(listenAddr string) error {
|
||||
defer delete(s.entries, listenAddr)
|
||||
if qs, ok := s.entries[listenAddr]; ok && qs != nil {
|
||||
return qs.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) Stats() map[string]Stats {
|
||||
r := make(map[string]Stats, len(s.entries))
|
||||
for laddr, sv := range s.entries {
|
||||
addr, in, out := sv.Stats()
|
||||
r[laddr] = Stats{
|
||||
RemoteAddr: addr,
|
||||
inboundBytes: in,
|
||||
outboundBytes: out,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func generateInsecureTLSConfig() *tls.Config {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
NextProtos: []string{TLSAppProtocol},
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue