mirror of
https://github.com/cedar2025/hysteria.git
synced 2025-06-08 13:29:56 +00:00
First commit, forwarder is essentially complete
This commit is contained in:
commit
5547895dcb
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…
x
Reference in New Issue
Block a user