mirror of
https://github.com/apernet/hysteria.git
synced 2025-04-03 04:27:39 +03:00
WIP SOCKS5 proxy
This commit is contained in:
parent
a424a17af3
commit
d5640efd7e
25 changed files with 1024 additions and 214 deletions
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 Toby
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
8
README.md
Normal file
8
README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||

|
||||
|
||||
[![License][1]][2] [![Telegram][3]][4]
|
||||
|
||||
[1]: https://img.shields.io/github/license/tobyxdd/hysteria?style=flat-square&
|
||||
[2]: LICENSE.md
|
||||
[3]: https://patrolavia.github.io/telegram-badge/chat.png
|
||||
[4]: https://t.me/hysteria_github
|
8
cmd/client.json
Normal file
8
cmd/client.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"listen": "localhost:1080",
|
||||
"server": "toby.moe:36712",
|
||||
"name": "",
|
||||
"insecure": false,
|
||||
"up_mbps": 50,
|
||||
"down_mbps": 80
|
||||
}
|
84
cmd/config.go
Normal file
84
cmd/config.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
mbpsToBps = 125000
|
||||
|
||||
DefaultMaxReceiveStreamFlowControlWindow = 33554432
|
||||
DefaultMaxReceiveConnectionFlowControlWindow = 67108864
|
||||
DefaultMaxIncomingStreams = 200
|
||||
)
|
||||
|
||||
func loadConfig(cfg interface{}, args []string) error {
|
||||
cfgVal := reflect.ValueOf(cfg).Elem()
|
||||
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
fsValMap := make(map[reflect.Value]interface{}, cfgVal.NumField())
|
||||
for i := 0; i < cfgVal.NumField(); i++ {
|
||||
structField := cfgVal.Type().Field(i)
|
||||
tag := structField.Tag
|
||||
switch structField.Type.Kind() {
|
||||
case reflect.String:
|
||||
fsValMap[cfgVal.Field(i)] =
|
||||
fs.String(jsonTagToFlagName(tag.Get("json")), "", tag.Get("desc"))
|
||||
case reflect.Int:
|
||||
fsValMap[cfgVal.Field(i)] =
|
||||
fs.Int(jsonTagToFlagName(tag.Get("json")), 0, tag.Get("desc"))
|
||||
case reflect.Uint64:
|
||||
fsValMap[cfgVal.Field(i)] =
|
||||
fs.Uint64(jsonTagToFlagName(tag.Get("json")), 0, tag.Get("desc"))
|
||||
case reflect.Bool:
|
||||
var bf optionalBoolFlag
|
||||
fs.Var(&bf, jsonTagToFlagName(tag.Get("json")), tag.Get("desc"))
|
||||
fsValMap[cfgVal.Field(i)] = &bf
|
||||
}
|
||||
}
|
||||
configFile := fs.String("config", "", "Configuration file")
|
||||
// Parse
|
||||
if err := fs.Parse(args); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
// Put together the config
|
||||
if len(*configFile) > 0 {
|
||||
cb, err := ioutil.ReadFile(*configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(cb, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Flags override config from file
|
||||
for field, val := range fsValMap {
|
||||
switch v := val.(type) {
|
||||
case *string:
|
||||
if len(*v) > 0 {
|
||||
field.SetString(*v)
|
||||
}
|
||||
case *int:
|
||||
if *v != 0 {
|
||||
field.SetInt(int64(*v))
|
||||
}
|
||||
case *uint64:
|
||||
if *v != 0 {
|
||||
field.SetUint(*v)
|
||||
}
|
||||
case *optionalBoolFlag:
|
||||
if v.Exists {
|
||||
field.SetBool(v.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func jsonTagToFlagName(tag string) string {
|
||||
return strings.ReplaceAll(tag, "_", "-")
|
||||
}
|
40
cmd/main.go
Normal file
40
cmd/main.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var modeMap = map[string]func(args []string){
|
||||
"relay server": relayServer,
|
||||
"relay client": relayClient,
|
||||
"proxy server": proxyServer,
|
||||
"proxy client": proxyClient,
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 3 {
|
||||
fmt.Println()
|
||||
fmt.Printf("Usage: %s MODE SUBMODE [OPTIONS]\n\n"+
|
||||
"Available mode/submode combinations: "+getModes()+"\n"+
|
||||
"Use -h to see the available options for a mode.\n\n", os.Args[0])
|
||||
return
|
||||
}
|
||||
modeStr := fmt.Sprintf("%s %s", strings.ToLower(strings.TrimSpace(os.Args[1])),
|
||||
strings.ToLower(strings.TrimSpace(os.Args[2])))
|
||||
f := modeMap[modeStr]
|
||||
if f != nil {
|
||||
f(os.Args[3:])
|
||||
} else {
|
||||
fmt.Println("Invalid mode:", modeStr)
|
||||
}
|
||||
}
|
||||
|
||||
func getModes() string {
|
||||
modes := make([]string, 0, len(modeMap))
|
||||
for mode := range modeMap {
|
||||
modes = append(modes, mode)
|
||||
}
|
||||
return strings.Join(modes, ", ")
|
||||
}
|
88
cmd/proxy_client.go
Normal file
88
cmd/proxy_client.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"github.com/tobyxdd/hysteria/pkg/socks5"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os/user"
|
||||
)
|
||||
|
||||
func proxyClient(args []string) {
|
||||
var config proxyClientConfig
|
||||
err := loadConfig(&config, args)
|
||||
if err != nil {
|
||||
log.Fatalln("Unable to load configuration:", err)
|
||||
}
|
||||
if err := config.Check(); err != nil {
|
||||
log.Fatalln("Configuration error:", err)
|
||||
}
|
||||
if len(config.Name) == 0 {
|
||||
usr, err := user.Current()
|
||||
if err == nil {
|
||||
config.Name = usr.Name
|
||||
}
|
||||
}
|
||||
log.Printf("Configuration loaded: %+v\n", config)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
NextProtos: []string{proxyTLSProtocol},
|
||||
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
|
||||
}
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
MaxReceiveStreamFlowControlWindow: config.ReceiveWindowConn,
|
||||
MaxReceiveConnectionFlowControlWindow: config.ReceiveWindow,
|
||||
KeepAlive: true,
|
||||
}
|
||||
if quicConfig.MaxReceiveStreamFlowControlWindow == 0 {
|
||||
quicConfig.MaxReceiveStreamFlowControlWindow = DefaultMaxReceiveStreamFlowControlWindow
|
||||
}
|
||||
if quicConfig.MaxReceiveConnectionFlowControlWindow == 0 {
|
||||
quicConfig.MaxReceiveConnectionFlowControlWindow = DefaultMaxReceiveConnectionFlowControlWindow
|
||||
}
|
||||
|
||||
client, err := core.NewClient(config.ServerAddr, config.Name, "", tlsConfig, quicConfig,
|
||||
uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps,
|
||||
func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos {
|
||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln("Client initialization failed:", err)
|
||||
}
|
||||
defer client.Close()
|
||||
log.Println("Connected to", config.ServerAddr)
|
||||
|
||||
socks5server, err := socks5.NewServer(config.SOCKS5Addr, "", nil, 0, 0, 0)
|
||||
if err != nil {
|
||||
log.Fatalln("SOCKS5 server initialization failed:", err)
|
||||
}
|
||||
log.Println("SOCKS5 server up and running on", config.SOCKS5Addr)
|
||||
|
||||
log.Fatalln(socks5server.ListenAndServe(&socks5.HyHandler{
|
||||
Client: client,
|
||||
NewTCPRequestFunc: func(addr, reqAddr string) {
|
||||
log.Printf("[TCP] %s <-> %s\n", addr, reqAddr)
|
||||
},
|
||||
TCPRequestClosedFunc: func(addr, reqAddr string, err error) {
|
||||
log.Printf("Closed [TCP] %s <-> %s: %s\n", addr, reqAddr, err.Error())
|
||||
},
|
||||
}))
|
||||
}
|
69
cmd/proxy_config.go
Normal file
69
cmd/proxy_config.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package main
|
||||
|
||||
import "errors"
|
||||
|
||||
const proxyTLSProtocol = "hysteria-proxy"
|
||||
|
||||
type proxyClientConfig struct {
|
||||
SOCKS5Addr string `json:"socks5_addr" desc:"SOCKS5 listen address"`
|
||||
SOCKS5Timeout int `json:"socks5_timeout" desc:"SOCKS5 connection timeout in seconds"`
|
||||
ServerAddr string `json:"server" desc:"Server address"`
|
||||
Name string `json:"name" desc:"Client name presented to the server"`
|
||||
Insecure bool `json:"insecure" desc:"Ignore TLS certificate errors"`
|
||||
CustomCAFile string `json:"ca" desc:"Specify a trusted CA file"`
|
||||
UpMbps int `json:"up_mbps" desc:"Upload speed in Mbps"`
|
||||
DownMbps int `json:"down_mbps" desc:"Download speed in Mbps"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn" desc:"Max receive window size per connection"`
|
||||
ReceiveWindow uint64 `json:"recv_window" desc:"Max receive window size"`
|
||||
}
|
||||
|
||||
func (c *proxyClientConfig) Check() error {
|
||||
if len(c.SOCKS5Addr) == 0 {
|
||||
return errors.New("no SOCKS5 listen address")
|
||||
}
|
||||
if c.SOCKS5Timeout != 0 && c.SOCKS5Timeout <= 4 {
|
||||
return errors.New("invalid SOCKS5 timeout")
|
||||
}
|
||||
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 proxyServerConfig struct {
|
||||
ListenAddr string `json:"listen" desc:"Server listen address"`
|
||||
CertFile string `json:"cert" desc:"TLS certificate file"`
|
||||
KeyFile string `json:"key" desc:"TLS key file"`
|
||||
UpMbps int `json:"up_mbps" desc:"Max upload speed per client in Mbps"`
|
||||
DownMbps int `json:"down_mbps" desc:"Max download speed per client in Mbps"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn" desc:"Max receive window size per connection"`
|
||||
ReceiveWindowClient uint64 `json:"recv_window_client" desc:"Max receive window size per client"`
|
||||
MaxConnClient int `json:"max_conn_client" desc:"Max simultaneous connections allowed per client"`
|
||||
}
|
||||
|
||||
func (c *proxyServerConfig) Check() error {
|
||||
if len(c.ListenAddr) == 0 {
|
||||
return errors.New("no listen address")
|
||||
}
|
||||
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
|
||||
}
|
101
cmd/proxy_server.go
Normal file
101
cmd/proxy_server.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
)
|
||||
|
||||
func proxyServer(args []string) {
|
||||
var config proxyServerConfig
|
||||
err := loadConfig(&config, args)
|
||||
if err != nil {
|
||||
log.Fatalln("Unable to load configuration:", err)
|
||||
}
|
||||
if err := config.Check(); err != nil {
|
||||
log.Fatalln("Configuration error:", err.Error())
|
||||
}
|
||||
log.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{proxyTLSProtocol},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
MaxReceiveStreamFlowControlWindow: config.ReceiveWindowConn,
|
||||
MaxReceiveConnectionFlowControlWindow: config.ReceiveWindowClient,
|
||||
MaxIncomingStreams: config.MaxConnClient,
|
||||
KeepAlive: true,
|
||||
}
|
||||
if quicConfig.MaxReceiveStreamFlowControlWindow == 0 {
|
||||
quicConfig.MaxReceiveStreamFlowControlWindow = DefaultMaxReceiveStreamFlowControlWindow
|
||||
}
|
||||
if quicConfig.MaxReceiveConnectionFlowControlWindow == 0 {
|
||||
quicConfig.MaxReceiveConnectionFlowControlWindow = DefaultMaxReceiveConnectionFlowControlWindow
|
||||
}
|
||||
if quicConfig.MaxIncomingStreams == 0 {
|
||||
quicConfig.MaxIncomingStreams = DefaultMaxIncomingStreams
|
||||
}
|
||||
|
||||
server, err := core.NewServer(config.ListenAddr, tlsConfig, quicConfig,
|
||||
uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps,
|
||||
func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos {
|
||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||
},
|
||||
func(addr net.Addr, username string, password string, sSend uint64, sRecv uint64) (core.AuthResult, string) {
|
||||
// No authentication logic in relay, just log username and speed
|
||||
log.Printf("%s (%s) connected, negotiated speed (Mbps): Up %d / Down %d\n",
|
||||
addr.String(), username, sSend/mbpsToBps, sRecv/mbpsToBps)
|
||||
return core.AuthSuccess, ""
|
||||
},
|
||||
func(addr net.Addr, username string, err error) {
|
||||
log.Printf("%s (%s) disconnected: %s\n", addr.String(), username, err.Error())
|
||||
},
|
||||
func(addr net.Addr, username string, id int, packet bool, reqAddr string) (core.ConnectResult, string, io.ReadWriteCloser) {
|
||||
if !packet {
|
||||
// TCP
|
||||
log.Printf("%s (%s): [TCP] %s\n", addr.String(), username, reqAddr)
|
||||
conn, err := net.Dial("tcp", reqAddr)
|
||||
if err != nil {
|
||||
log.Printf("TCP error %s: %s\n", reqAddr, err.Error())
|
||||
return core.ConnFailed, err.Error(), nil
|
||||
}
|
||||
return core.ConnSuccess, "", conn
|
||||
} else {
|
||||
// UDP
|
||||
log.Printf("%s (%s): [UDP] %s\n", addr.String(), username, reqAddr)
|
||||
conn, err := net.Dial("udp", reqAddr)
|
||||
if err != nil {
|
||||
log.Printf("UDP error %s: %s\n", reqAddr, err.Error())
|
||||
return core.ConnFailed, err.Error(), nil
|
||||
}
|
||||
return core.ConnSuccess, "", conn
|
||||
}
|
||||
},
|
||||
func(addr net.Addr, username string, id int, packet bool, reqAddr string, err error) {
|
||||
if !packet {
|
||||
log.Printf("%s (%s): closed [TCP] %s: %s\n", addr.String(), username, reqAddr, err.Error())
|
||||
} else {
|
||||
log.Printf("%s (%s): closed [UDP] %s: %s\n", addr.String(), username, reqAddr, err.Error())
|
||||
}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalln("Server initialization failed:", err)
|
||||
}
|
||||
defer server.Close()
|
||||
log.Println("Up and running on", config.ListenAddr)
|
||||
|
||||
log.Fatalln(server.Serve())
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
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)
|
||||
}
|
||||
}
|
|
@ -8,15 +8,14 @@ import (
|
|||
"github.com/tobyxdd/hysteria/internal/utils"
|
||||
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os/user"
|
||||
)
|
||||
|
||||
func client(args []string) {
|
||||
var config cmdClientConfig
|
||||
func relayClient(args []string) {
|
||||
var config relayClientConfig
|
||||
err := loadConfig(&config, args)
|
||||
if err != nil {
|
||||
log.Fatalln("Unable to load configuration:", err)
|
||||
|
@ -33,7 +32,7 @@ func client(args []string) {
|
|||
log.Printf("Configuration loaded: %+v\n", config)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
NextProtos: []string{TLSAppProtocol},
|
||||
NextProtos: []string{relayTLSProtocol},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
// Load CA
|
||||
|
@ -70,7 +69,7 @@ func client(args []string) {
|
|||
log.Fatalln("Client initialization failed:", err)
|
||||
}
|
||||
defer client.Close()
|
||||
log.Println("Client initialization complete, connected to", config.ServerAddr)
|
||||
log.Println("Connected to", config.ServerAddr)
|
||||
|
||||
listener, err := net.Listen("tcp", config.ListenAddr)
|
||||
if err != nil {
|
||||
|
@ -84,16 +83,16 @@ func client(args []string) {
|
|||
if err != nil {
|
||||
log.Fatalln("TCP accept failed:", err)
|
||||
}
|
||||
go clientHandleConn(conn, client)
|
||||
go relayClientHandleConn(conn, client)
|
||||
}
|
||||
}
|
||||
|
||||
func clientHandleConn(conn net.Conn, client core.Client) {
|
||||
log.Println("New TCP connection from", conn.RemoteAddr().String())
|
||||
func relayClientHandleConn(conn net.Conn, client core.Client) {
|
||||
log.Println("New connection", conn.RemoteAddr().String())
|
||||
var closeErr error
|
||||
defer func() {
|
||||
_ = conn.Close()
|
||||
log.Println("TCP connection from", conn.RemoteAddr().String(), "closed", closeErr)
|
||||
log.Println("Connection", conn.RemoteAddr().String(), "closed", closeErr)
|
||||
}()
|
||||
rwc, err := client.Dial(false, "")
|
||||
if err != nil {
|
||||
|
@ -101,18 +100,5 @@ func clientHandleConn(conn net.Conn, client core.Client) {
|
|||
return
|
||||
}
|
||||
defer rwc.Close()
|
||||
closeErr = pipePair(conn, rwc)
|
||||
}
|
||||
|
||||
func pipePair(rw1, rw2 io.ReadWriter) error {
|
||||
// Pipes
|
||||
errChan := make(chan error, 2)
|
||||
go func() {
|
||||
errChan <- utils.Pipe(rw2, rw1, nil)
|
||||
}()
|
||||
go func() {
|
||||
errChan <- utils.Pipe(rw1, rw2, nil)
|
||||
}()
|
||||
// We only need the first error
|
||||
return <-errChan
|
||||
closeErr = utils.PipePair(conn, rwc, nil, nil)
|
||||
}
|
|
@ -1,25 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
import "errors"
|
||||
|
||||
const (
|
||||
mbpsToBps = 125000
|
||||
const relayTLSProtocol = "hysteria-relay"
|
||||
|
||||
TLSAppProtocol = "hysteria-relay"
|
||||
|
||||
DefaultMaxReceiveStreamFlowControlWindow = 33554432
|
||||
DefaultMaxReceiveConnectionFlowControlWindow = 67108864
|
||||
)
|
||||
|
||||
type cmdClientConfig struct {
|
||||
type relayClientConfig struct {
|
||||
ListenAddr string `json:"listen" desc:"TCP listen address"`
|
||||
ServerAddr string `json:"server" desc:"Server address"`
|
||||
Name string `json:"name" desc:"Client name presented to the server"`
|
||||
|
@ -31,7 +16,7 @@ type cmdClientConfig struct {
|
|||
ReceiveWindow uint64 `json:"recv_window" desc:"Max receive window size"`
|
||||
}
|
||||
|
||||
func (c *cmdClientConfig) Check() error {
|
||||
func (c *relayClientConfig) Check() error {
|
||||
if len(c.ListenAddr) == 0 {
|
||||
return errors.New("no listen address")
|
||||
}
|
||||
|
@ -48,7 +33,7 @@ func (c *cmdClientConfig) Check() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type cmdServerConfig struct {
|
||||
type relayServerConfig struct {
|
||||
ListenAddr string `json:"listen" desc:"Server listen address"`
|
||||
RemoteAddr string `json:"remote" desc:"Remote relay address"`
|
||||
CertFile string `json:"cert" desc:"TLS certificate file"`
|
||||
|
@ -60,7 +45,7 @@ type cmdServerConfig struct {
|
|||
MaxConnClient int `json:"max_conn_client" desc:"Max simultaneous connections allowed per client"`
|
||||
}
|
||||
|
||||
func (c *cmdServerConfig) Check() error {
|
||||
func (c *relayServerConfig) Check() error {
|
||||
if len(c.ListenAddr) == 0 {
|
||||
return errors.New("no listen address")
|
||||
}
|
||||
|
@ -82,69 +67,3 @@ func (c *cmdServerConfig) Check() error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadConfig(cfg interface{}, args []string) error {
|
||||
cfgVal := reflect.ValueOf(cfg).Elem()
|
||||
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
fsValMap := make(map[reflect.Value]interface{}, cfgVal.NumField())
|
||||
for i := 0; i < cfgVal.NumField(); i++ {
|
||||
structField := cfgVal.Type().Field(i)
|
||||
tag := structField.Tag
|
||||
switch structField.Type.Kind() {
|
||||
case reflect.String:
|
||||
fsValMap[cfgVal.Field(i)] =
|
||||
fs.String(jsonTagToFlagName(tag.Get("json")), "", tag.Get("desc"))
|
||||
case reflect.Int:
|
||||
fsValMap[cfgVal.Field(i)] =
|
||||
fs.Int(jsonTagToFlagName(tag.Get("json")), 0, tag.Get("desc"))
|
||||
case reflect.Uint64:
|
||||
fsValMap[cfgVal.Field(i)] =
|
||||
fs.Uint64(jsonTagToFlagName(tag.Get("json")), 0, tag.Get("desc"))
|
||||
case reflect.Bool:
|
||||
var bf optionalBoolFlag
|
||||
fs.Var(&bf, jsonTagToFlagName(tag.Get("json")), tag.Get("desc"))
|
||||
fsValMap[cfgVal.Field(i)] = &bf
|
||||
}
|
||||
}
|
||||
configFile := fs.String("config", "", "Configuration file")
|
||||
// Parse
|
||||
if err := fs.Parse(args); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
// Put together the config
|
||||
if len(*configFile) > 0 {
|
||||
cb, err := ioutil.ReadFile(*configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(cb, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Flags override config from file
|
||||
for field, val := range fsValMap {
|
||||
switch v := val.(type) {
|
||||
case *string:
|
||||
if len(*v) > 0 {
|
||||
field.SetString(*v)
|
||||
}
|
||||
case *int:
|
||||
if *v != 0 {
|
||||
field.SetInt(int64(*v))
|
||||
}
|
||||
case *uint64:
|
||||
if *v != 0 {
|
||||
field.SetUint(*v)
|
||||
}
|
||||
case *optionalBoolFlag:
|
||||
if v.Exists {
|
||||
field.SetBool(v.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func jsonTagToFlagName(tag string) string {
|
||||
return strings.ReplaceAll(tag, "_", "-")
|
||||
}
|
|
@ -11,8 +11,8 @@ import (
|
|||
"net"
|
||||
)
|
||||
|
||||
func server(args []string) {
|
||||
var config cmdServerConfig
|
||||
func relayServer(args []string) {
|
||||
var config relayServerConfig
|
||||
err := loadConfig(&config, args)
|
||||
if err != nil {
|
||||
log.Fatalln("Unable to load configuration:", err)
|
||||
|
@ -28,13 +28,14 @@ func server(args []string) {
|
|||
}
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
NextProtos: []string{TLSAppProtocol},
|
||||
NextProtos: []string{relayTLSProtocol},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
MaxReceiveStreamFlowControlWindow: config.ReceiveWindowConn,
|
||||
MaxReceiveConnectionFlowControlWindow: config.ReceiveWindowClient,
|
||||
MaxIncomingStreams: config.MaxConnClient,
|
||||
KeepAlive: true,
|
||||
}
|
||||
if quicConfig.MaxReceiveStreamFlowControlWindow == 0 {
|
||||
|
@ -43,6 +44,9 @@ func server(args []string) {
|
|||
if quicConfig.MaxReceiveConnectionFlowControlWindow == 0 {
|
||||
quicConfig.MaxReceiveConnectionFlowControlWindow = DefaultMaxReceiveConnectionFlowControlWindow
|
||||
}
|
||||
if quicConfig.MaxIncomingStreams == 0 {
|
||||
quicConfig.MaxIncomingStreams = DefaultMaxIncomingStreams
|
||||
}
|
||||
|
||||
server, err := core.NewServer(config.ListenAddr, tlsConfig, quicConfig,
|
||||
uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps,
|
||||
|
@ -51,34 +55,34 @@ func server(args []string) {
|
|||
},
|
||||
func(addr net.Addr, username string, password string, sSend uint64, sRecv uint64) (core.AuthResult, string) {
|
||||
// No authentication logic in relay, just log username and speed
|
||||
log.Printf("Client %s connected, negotiated speed in Mbps: Up %d / Down %d\n",
|
||||
addr.String(), sSend/mbpsToBps, sRecv/mbpsToBps)
|
||||
log.Printf("%s (%s) connected, negotiated speed (Mbps): Up %d / Down %d\n",
|
||||
addr.String(), username, sSend/mbpsToBps, sRecv/mbpsToBps)
|
||||
return core.AuthSuccess, ""
|
||||
},
|
||||
func(addr net.Addr, username string, err error) {
|
||||
log.Printf("Client %s (%s) disconnected: %s\n", addr.String(), username, err.Error())
|
||||
log.Printf("%s (%s) disconnected: %s\n", addr.String(), username, err.Error())
|
||||
},
|
||||
func(addr net.Addr, username string, id int, isUDP bool, reqAddr string) (core.ConnectResult, string, io.ReadWriteCloser) {
|
||||
log.Printf("Client %s (%s) opened stream ID %d\n", addr.String(), username, id)
|
||||
if isUDP {
|
||||
func(addr net.Addr, username string, id int, packet bool, reqAddr string) (core.ConnectResult, string, io.ReadWriteCloser) {
|
||||
log.Printf("%s (%s): new stream ID %d\n", addr.String(), username, id)
|
||||
if packet {
|
||||
return core.ConnBlocked, "unsupported", nil
|
||||
}
|
||||
conn, err := net.Dial("tcp", config.RemoteAddr)
|
||||
if err != nil {
|
||||
log.Printf("TCP error when connecting to %s: %s", config.RemoteAddr, err.Error())
|
||||
log.Printf("TCP error %s: %s\n", config.RemoteAddr, err.Error())
|
||||
return core.ConnFailed, err.Error(), nil
|
||||
}
|
||||
return core.ConnSuccess, "", conn
|
||||
},
|
||||
func(addr net.Addr, username string, id int, isUDP bool, reqAddr string, err error) {
|
||||
log.Printf("Client %s (%s) closed stream ID %d: %s", addr.String(), username, id, err.Error())
|
||||
func(addr net.Addr, username string, id int, packet bool, reqAddr string, err error) {
|
||||
log.Printf("%s (%s): closed stream ID %d: %s\n", addr.String(), username, id, err.Error())
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalln("Server initialization failed:", err)
|
||||
}
|
||||
defer server.Close()
|
||||
log.Println("The server is now up and running :)")
|
||||
log.Println("Up and running on", config.ListenAddr)
|
||||
|
||||
log.Fatalln("Server error:", server.Serve())
|
||||
log.Fatalln(server.Serve())
|
||||
}
|
9
cmd/server.json
Normal file
9
cmd/server.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"listen": ":36712",
|
||||
"remote": "localhost:1080",
|
||||
"cert": "/home/ubuntu/.caddy/acme/acme-v02.api.letsencrypt.org/sites/toby.moe/toby.moe.crt",
|
||||
"key": "/home/ubuntu/.caddy/acme/acme-v02.api.letsencrypt.org/sites/toby.moe/toby.moe.key",
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100,
|
||||
"max_conn_client": 200
|
||||
}
|
BIN
docs/logos/readme.png
Normal file
BIN
docs/logos/readme.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
8
go.mod
8
go.mod
|
@ -4,6 +4,12 @@ go 1.14
|
|||
|
||||
require github.com/golang/protobuf v1.3.1
|
||||
|
||||
require github.com/lucas-clemente/quic-go v0.15.2
|
||||
require (
|
||||
github.com/lucas-clemente/quic-go v0.15.2
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997
|
||||
github.com/txthinking/socks5 v0.0.0-20200327133705-caf148ab5e9d
|
||||
github.com/txthinking/x v0.0.0-20200330144832-5ad2416896a9 // indirect
|
||||
)
|
||||
|
||||
replace github.com/lucas-clemente/quic-go => github.com/tobyxdd/quic-go v0.1.3-tquic-1
|
||||
|
|
8
go.sum
8
go.sum
|
@ -79,6 +79,8 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
|||
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/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
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=
|
||||
|
@ -117,6 +119,12 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
|||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tobyxdd/quic-go v0.1.3-tquic-1 h1:LOD8EsuNTYaDInkkgy3swL8d3y2SIliKaGlGSomvlik=
|
||||
github.com/tobyxdd/quic-go v0.1.3-tquic-1/go.mod h1:oj40DjNLuNugvtXWg4PwaYgv7tAbzAabrT57CC69EhI=
|
||||
github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997 h1:vlDgnShahmE2XLslpr0hnzxfAmSj3JLX2CYi8Xct7G4=
|
||||
github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
|
||||
github.com/txthinking/socks5 v0.0.0-20200327133705-caf148ab5e9d h1:V+Wyj2AqtLwLG7KnniV8QG+gEkENPsudZbivvLyX4kw=
|
||||
github.com/txthinking/socks5 v0.0.0-20200327133705-caf148ab5e9d/go.mod h1:d3n8NJ6QMRb6I/WAlp4z5ZPAoaeqDmX5NgVZA0mhe+I=
|
||||
github.com/txthinking/x v0.0.0-20200330144832-5ad2416896a9 h1:ngJOce33YJJT1PFTfC9ao7S27AfrUh11Dr3Bc+ooBdM=
|
||||
github.com/txthinking/x v0.0.0-20200330144832-5ad2416896a9/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4=
|
||||
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=
|
||||
|
|
|
@ -49,17 +49,17 @@ func NewClient(serverAddr string, username string, password string, tlsConfig *t
|
|||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Client) Dial(udp bool, addr string) (io.ReadWriteCloser, error) {
|
||||
func (c *Client) Dial(packet bool, addr string) (io.ReadWriteCloser, error) {
|
||||
stream, err := c.openStreamWithReconnect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Send request
|
||||
req := &ClientConnectRequest{Address: addr}
|
||||
if udp {
|
||||
req.Type = ConnectionType_UDP
|
||||
if packet {
|
||||
req.Type = ConnectionType_Packet
|
||||
} else {
|
||||
req.Type = ConnectionType_TCP
|
||||
req.Type = ConnectionType_Stream
|
||||
}
|
||||
err = writeClientConnectRequest(stream, req)
|
||||
if err != nil {
|
||||
|
@ -77,7 +77,7 @@ func (c *Client) Dial(udp bool, addr string) (io.ReadWriteCloser, error) {
|
|||
return nil, fmt.Errorf("server rejected the connection %s (msg: %s)",
|
||||
resp.Result.String(), resp.Message)
|
||||
}
|
||||
if udp {
|
||||
if packet {
|
||||
return &utils.PacketReadWriteCloser{Orig: stream}, nil
|
||||
} else {
|
||||
return stream, nil
|
||||
|
|
|
@ -51,18 +51,18 @@ func (AuthResult) EnumDescriptor() ([]byte, []int) {
|
|||
type ConnectionType int32
|
||||
|
||||
const (
|
||||
ConnectionType_TCP ConnectionType = 0
|
||||
ConnectionType_UDP ConnectionType = 1
|
||||
ConnectionType_Stream ConnectionType = 0
|
||||
ConnectionType_Packet ConnectionType = 1
|
||||
)
|
||||
|
||||
var ConnectionType_name = map[int32]string{
|
||||
0: "TCP",
|
||||
1: "UDP",
|
||||
0: "Stream",
|
||||
1: "Packet",
|
||||
}
|
||||
|
||||
var ConnectionType_value = map[string]int32{
|
||||
"TCP": 0,
|
||||
"UDP": 1,
|
||||
"Stream": 0,
|
||||
"Packet": 1,
|
||||
}
|
||||
|
||||
func (x ConnectionType) String() string {
|
||||
|
@ -334,7 +334,7 @@ func (m *ClientConnectRequest) GetType() ConnectionType {
|
|||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return ConnectionType_TCP
|
||||
return ConnectionType_Stream
|
||||
}
|
||||
|
||||
func (m *ClientConnectRequest) GetAddress() string {
|
||||
|
@ -408,32 +408,33 @@ func init() {
|
|||
}
|
||||
|
||||
var fileDescriptor_0c5120591600887d = []byte{
|
||||
// 431 bytes of a gzipped FileDescriptorProto
|
||||
// 434 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xd1, 0x6e, 0xd3, 0x30,
|
||||
0x14, 0x86, 0xd7, 0xae, 0x5b, 0xb7, 0x13, 0x36, 0x32, 0x6f, 0x13, 0x83, 0x1b, 0x20, 0x57, 0x55,
|
||||
0x91, 0x2a, 0x34, 0x9e, 0x20, 0x75, 0x82, 0xa8, 0xa8, 0xd2, 0xc9, 0x69, 0xb9, 0xe0, 0x82, 0x2a,
|
||||
0x4b, 0x8e, 0x58, 0xa5, 0xcc, 0x36, 0xb6, 0x33, 0x34, 0xf1, 0xf2, 0x28, 0x8e, 0x93, 0xae, 0x48,
|
||||
0x48, 0xbb, 0xeb, 0x39, 0xe7, 0xd7, 0xff, 0xf9, 0xab, 0x02, 0x27, 0xb9, 0xe0, 0x46, 0x89, 0x72,
|
||||
0x22, 0x95, 0x30, 0x82, 0x0c, 0x72, 0xa1, 0x30, 0xa0, 0x70, 0x90, 0x4a, 0xc4, 0x82, 0xbc, 0x86,
|
||||
0x23, 0x8d, 0xbc, 0x58, 0xdf, 0x4a, 0x7d, 0xd5, 0x7b, 0xd7, 0x1b, 0x0d, 0xd8, 0xb0, 0x9e, 0xa7,
|
||||
0x52, 0x93, 0xb7, 0xe0, 0x29, 0xcc, 0x71, 0xf3, 0x80, 0xf6, 0xda, 0xb7, 0x57, 0x70, 0xab, 0xa9,
|
||||
0xd4, 0x41, 0x04, 0x40, 0x15, 0x16, 0xc8, 0xcd, 0x26, 0x2b, 0xc9, 0x1b, 0x38, 0xaa, 0x34, 0x2a,
|
||||
0x9e, 0xdd, 0xa3, 0x6d, 0x3a, 0x66, 0xdd, 0x5c, 0xdf, 0x64, 0xa6, 0xf5, 0x6f, 0xa1, 0x0a, 0xdb,
|
||||
0x73, 0xcc, 0xba, 0x39, 0xb8, 0x83, 0x33, 0x5a, 0x6e, 0x90, 0x9b, 0xb0, 0x32, 0x77, 0x0c, 0x7f,
|
||||
0x55, 0xa8, 0x0d, 0xf9, 0x08, 0x90, 0x77, 0xd5, 0xb6, 0xce, 0xbb, 0xf6, 0x27, 0xf5, 0xd3, 0x27,
|
||||
0x5b, 0x24, 0x7b, 0x92, 0x21, 0xef, 0xe1, 0x40, 0xd7, 0x46, 0xb6, 0xdf, 0xbb, 0xf6, 0x9a, 0xb0,
|
||||
0x95, 0x64, 0xcd, 0x25, 0xf8, 0x03, 0x24, 0x45, 0xf5, 0x80, 0xaa, 0x21, 0x69, 0x29, 0xb8, 0x46,
|
||||
0x32, 0x82, 0x43, 0x85, 0xba, 0x2a, 0x8d, 0xc5, 0x9c, 0xb6, 0x18, 0x97, 0xa9, 0x4a, 0xc3, 0xdc,
|
||||
0x9d, 0x5c, 0xc1, 0xf0, 0x1e, 0xb5, 0xce, 0x7e, 0xa2, 0x93, 0x68, 0xc7, 0x2d, 0x7c, 0xff, 0xbf,
|
||||
0xf0, 0xef, 0x70, 0xd1, 0x68, 0x52, 0xc1, 0x39, 0xe6, 0xa6, 0x35, 0x1d, 0xc1, 0xc0, 0x3c, 0x4a,
|
||||
0x74, 0xf0, 0x0b, 0xe7, 0xd8, 0x64, 0x36, 0x82, 0x2f, 0x1f, 0x25, 0x32, 0x9b, 0xa8, 0xf1, 0x59,
|
||||
0x51, 0x28, 0xd4, 0xba, 0xc5, 0xbb, 0x31, 0xf8, 0x01, 0x97, 0x8d, 0x58, 0xd7, 0xed, 0xdc, 0x3e,
|
||||
0xfc, 0xe3, 0x76, 0xbe, 0x53, 0xff, 0x5c, 0xbd, 0x71, 0x02, 0xb0, 0xfd, 0x3b, 0x88, 0x0f, 0x2f,
|
||||
0xc2, 0xd5, 0xf2, 0xcb, 0x3a, 0x5d, 0x51, 0x1a, 0xa7, 0xa9, 0xbf, 0x47, 0x2e, 0xe1, 0xcc, 0x6e,
|
||||
0x66, 0xc9, 0xb7, 0x70, 0x3e, 0x8b, 0xd6, 0x94, 0xc5, 0x91, 0xdf, 0x23, 0xaf, 0xe0, 0xdc, 0xad,
|
||||
0x97, 0x31, 0x4b, 0xc2, 0xf9, 0x3a, 0x66, 0x6c, 0xc1, 0xfc, 0xfe, 0x38, 0x80, 0xd3, 0x5d, 0x43,
|
||||
0x32, 0x84, 0xfd, 0x25, 0xbd, 0xf1, 0xf7, 0xea, 0x1f, 0xab, 0xe8, 0xc6, 0xef, 0x8d, 0x23, 0x38,
|
||||
0xd9, 0x79, 0x66, 0x8d, 0xa5, 0x8b, 0x24, 0x79, 0x82, 0x7d, 0x09, 0x9e, 0xdd, 0x7c, 0x0e, 0x67,
|
||||
0x73, 0x0b, 0x6c, 0x23, 0xd3, 0xf9, 0x82, 0x7e, 0x8d, 0x23, 0xbf, 0x7f, 0x7b, 0x68, 0x3f, 0xfa,
|
||||
0x4f, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x65, 0xfc, 0xeb, 0x5c, 0x05, 0x03, 0x00, 0x00,
|
||||
0x14, 0x86, 0xd7, 0xd2, 0x75, 0xdb, 0x09, 0x1b, 0x99, 0xb7, 0x89, 0xc1, 0x0d, 0x90, 0xab, 0xaa,
|
||||
0x48, 0x15, 0x1a, 0x4f, 0x90, 0x3a, 0x41, 0x54, 0x54, 0x29, 0x72, 0x3a, 0x2e, 0xb8, 0xa0, 0xca,
|
||||
0x92, 0x23, 0x56, 0x91, 0xda, 0xc6, 0x76, 0x86, 0x26, 0x5e, 0x1e, 0xc5, 0x71, 0xd2, 0x16, 0x09,
|
||||
0x89, 0xbb, 0x9e, 0x73, 0x7e, 0xfd, 0x9f, 0xbf, 0x2a, 0x70, 0x9a, 0x0b, 0x6e, 0x94, 0x28, 0x27,
|
||||
0x52, 0x09, 0x23, 0xc8, 0x20, 0x17, 0x0a, 0x03, 0x0a, 0x87, 0xa9, 0x44, 0x2c, 0xc8, 0x0b, 0x38,
|
||||
0xd6, 0xc8, 0x8b, 0xd5, 0x9d, 0xd4, 0xd7, 0xbd, 0xd7, 0xbd, 0xd1, 0x80, 0x1d, 0xd5, 0xf3, 0x54,
|
||||
0x6a, 0xf2, 0x0a, 0x3c, 0x85, 0x39, 0xae, 0x1f, 0xd0, 0x5e, 0xfb, 0xf6, 0x0a, 0x6e, 0x35, 0x95,
|
||||
0x3a, 0x88, 0x00, 0xa8, 0xc2, 0x02, 0xb9, 0x59, 0x67, 0x25, 0x79, 0x09, 0xc7, 0x95, 0x46, 0xc5,
|
||||
0xb3, 0x0d, 0xda, 0xa6, 0x13, 0xd6, 0xcd, 0xf5, 0x4d, 0x66, 0x5a, 0xff, 0x12, 0xaa, 0xb0, 0x3d,
|
||||
0x27, 0xac, 0x9b, 0x83, 0x7b, 0x38, 0xa7, 0xe5, 0x1a, 0xb9, 0x09, 0x2b, 0x73, 0xcf, 0xf0, 0x67,
|
||||
0x85, 0xda, 0x90, 0x77, 0x00, 0x79, 0x57, 0x6d, 0xeb, 0xbc, 0x1b, 0x7f, 0x52, 0x3f, 0x7d, 0xb2,
|
||||
0x45, 0xb2, 0x9d, 0x0c, 0x79, 0x03, 0x87, 0xba, 0x36, 0xb2, 0xfd, 0xde, 0x8d, 0xd7, 0x84, 0xad,
|
||||
0x24, 0x6b, 0x2e, 0xc1, 0x6f, 0x20, 0x29, 0xaa, 0x07, 0x54, 0x0d, 0x49, 0x4b, 0xc1, 0x35, 0x92,
|
||||
0x11, 0x0c, 0x15, 0xea, 0xaa, 0x34, 0x16, 0x73, 0xd6, 0x62, 0x5c, 0xa6, 0x2a, 0x0d, 0x73, 0x77,
|
||||
0x72, 0x0d, 0x47, 0x1b, 0xd4, 0x3a, 0xfb, 0x8e, 0x4e, 0xa2, 0x1d, 0xb7, 0xf0, 0x27, 0xff, 0x84,
|
||||
0x7f, 0x85, 0xcb, 0x46, 0x93, 0x0a, 0xce, 0x31, 0x37, 0xad, 0xe9, 0x08, 0x06, 0xe6, 0x51, 0xa2,
|
||||
0x83, 0x5f, 0x3a, 0xc7, 0x26, 0xb3, 0x16, 0x7c, 0xf9, 0x28, 0x91, 0xd9, 0x44, 0x8d, 0xcf, 0x8a,
|
||||
0x42, 0xa1, 0xd6, 0x2d, 0xde, 0x8d, 0xc1, 0x37, 0xb8, 0x6a, 0xc4, 0xba, 0x6e, 0xe7, 0xf6, 0xf6,
|
||||
0x2f, 0xb7, 0x8b, 0xbd, 0xfa, 0xff, 0xd5, 0x1b, 0x27, 0x00, 0xdb, 0xbf, 0x83, 0xf8, 0xf0, 0x34,
|
||||
0xbc, 0x5d, 0x7e, 0x5c, 0xa5, 0xb7, 0x94, 0xc6, 0x69, 0xea, 0x1f, 0x90, 0x2b, 0x38, 0xb7, 0x9b,
|
||||
0x59, 0xf2, 0x25, 0x9c, 0xcf, 0xa2, 0x15, 0x65, 0x71, 0xe4, 0xf7, 0xc8, 0x73, 0xb8, 0x70, 0xeb,
|
||||
0x65, 0xcc, 0x92, 0x70, 0xbe, 0x8a, 0x19, 0x5b, 0x30, 0xbf, 0x3f, 0x1e, 0xc1, 0xd9, 0xbe, 0x21,
|
||||
0x01, 0x18, 0xa6, 0x46, 0x61, 0xb6, 0xf1, 0x0f, 0xea, 0xdf, 0x9f, 0xb3, 0xfc, 0x07, 0x1a, 0xbf,
|
||||
0x37, 0x8e, 0xe0, 0x74, 0xef, 0xb1, 0x35, 0x9c, 0x2e, 0x92, 0x64, 0x07, 0xfe, 0x0c, 0x3c, 0xbb,
|
||||
0xf9, 0x10, 0xce, 0xe6, 0x16, 0xdb, 0x46, 0xa6, 0xf3, 0x05, 0xfd, 0x14, 0x47, 0x7e, 0xff, 0x6e,
|
||||
0x68, 0x3f, 0xfd, 0xf7, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xd2, 0x2f, 0x8d, 0x6f, 0x0b, 0x03,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -29,8 +29,8 @@ message ServerAuthResponse {
|
|||
}
|
||||
|
||||
enum ConnectionType {
|
||||
TCP = 0;
|
||||
UDP = 1;
|
||||
Stream = 0;
|
||||
Packet = 1;
|
||||
}
|
||||
|
||||
enum ConnectResult {
|
||||
|
|
|
@ -175,25 +175,12 @@ func (s *Server) handleStream(addr net.Addr, username string, stream quic.Stream
|
|||
return
|
||||
}
|
||||
switch req.Type {
|
||||
case ConnectionType_TCP:
|
||||
err = s.pipePair(stream, conn)
|
||||
case ConnectionType_UDP:
|
||||
err = s.pipePair(&utils.PacketReadWriteCloser{Orig: stream}, conn)
|
||||
case ConnectionType_Stream:
|
||||
err = utils.PipePair(stream, conn, &s.outboundBytes, &s.inboundBytes)
|
||||
case ConnectionType_Packet:
|
||||
err = utils.PipePair(&utils.PacketReadWriteCloser{Orig: stream}, conn, &s.outboundBytes, &s.inboundBytes)
|
||||
default:
|
||||
err = fmt.Errorf("unsupported connection type %s", req.Type.String())
|
||||
}
|
||||
s.requestClosedFunc(addr, username, int(stream.StreamID()), req.Type, req.Address, err)
|
||||
}
|
||||
|
||||
func (s *Server) pipePair(rw1, rw2 io.ReadWriter) error {
|
||||
// Pipes
|
||||
errChan := make(chan error, 2)
|
||||
go func() {
|
||||
errChan <- utils.Pipe(rw2, rw1, &s.outboundBytes)
|
||||
}()
|
||||
go func() {
|
||||
errChan <- utils.Pipe(rw1, rw2, &s.inboundBytes)
|
||||
}()
|
||||
// We only need the first error
|
||||
return <-errChan
|
||||
}
|
||||
|
|
|
@ -25,3 +25,15 @@ func Pipe(src, dst io.ReadWriter, atomicCounter *uint64) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PipePair(rw1, rw2 io.ReadWriter, rw1WriteCounter, rw2WriteCounter *uint64) error {
|
||||
errChan := make(chan error, 2)
|
||||
go func() {
|
||||
errChan <- Pipe(rw2, rw1, rw1WriteCounter)
|
||||
}()
|
||||
go func() {
|
||||
errChan <- Pipe(rw1, rw2, rw2WriteCounter)
|
||||
}()
|
||||
// We only need the first error
|
||||
return <-errChan
|
||||
}
|
||||
|
|
|
@ -27,8 +27,8 @@ const (
|
|||
type CongestionFactory core.CongestionFactory
|
||||
type ClientAuthFunc func(addr net.Addr, username string, password string, sSend uint64, sRecv uint64) (AuthResult, string)
|
||||
type ClientDisconnectedFunc core.ClientDisconnectedFunc
|
||||
type HandleRequestFunc func(addr net.Addr, username string, id int, isUDP bool, reqAddr string) (ConnectResult, string, io.ReadWriteCloser)
|
||||
type RequestClosedFunc func(addr net.Addr, username string, id int, isUDP bool, reqAddr string, err error)
|
||||
type HandleRequestFunc func(addr net.Addr, username string, id int, packet bool, reqAddr string) (ConnectResult, string, io.ReadWriteCloser)
|
||||
type RequestClosedFunc func(addr net.Addr, username string, id int, packet bool, reqAddr string, err error)
|
||||
|
||||
type Server interface {
|
||||
Serve() error
|
||||
|
@ -49,16 +49,16 @@ func NewServer(addr string, tlsConfig *tls.Config, quicConfig *quic.Config,
|
|||
},
|
||||
core.ClientDisconnectedFunc(clientDisconnectedFunc),
|
||||
func(addr net.Addr, username string, id int, reqType core.ConnectionType, reqAddr string) (core.ConnectResult, string, io.ReadWriteCloser) {
|
||||
r, msg, conn := handleRequestFunc(addr, username, id, reqType == core.ConnectionType_UDP, reqAddr)
|
||||
r, msg, conn := handleRequestFunc(addr, username, id, reqType == core.ConnectionType_Packet, reqAddr)
|
||||
return core.ConnectResult(r), msg, conn
|
||||
},
|
||||
func(addr net.Addr, username string, id int, reqType core.ConnectionType, reqAddr string, err error) {
|
||||
requestClosedFunc(addr, username, id, reqType == core.ConnectionType_UDP, reqAddr, err)
|
||||
requestClosedFunc(addr, username, id, reqType == core.ConnectionType_Packet, reqAddr, err)
|
||||
})
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
Dial(udp bool, addr string) (io.ReadWriteCloser, error)
|
||||
Dial(packet bool, addr string) (io.ReadWriteCloser, error)
|
||||
Stats() (inbound uint64, outbound uint64)
|
||||
Close() error
|
||||
}
|
||||
|
|
56
pkg/socks5/handler.go
Normal file
56
pkg/socks5/handler.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package socks5
|
||||
|
||||
import (
|
||||
"github.com/tobyxdd/hysteria/internal/utils"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"github.com/txthinking/socks5"
|
||||
"net"
|
||||
)
|
||||
|
||||
type HyHandler struct {
|
||||
Client core.Client
|
||||
NewTCPRequestFunc func(addr, reqAddr string)
|
||||
TCPRequestClosedFunc func(addr, reqAddr string, err error)
|
||||
}
|
||||
|
||||
func (h *HyHandler) TCPHandle(server *Server, conn *net.TCPConn, request *socks5.Request) error {
|
||||
if request.Cmd == socks5.CmdConnect {
|
||||
h.NewTCPRequestFunc(conn.RemoteAddr().String(), request.Address())
|
||||
var closeErr error
|
||||
defer func() {
|
||||
h.TCPRequestClosedFunc(conn.RemoteAddr().String(), request.Address(), closeErr)
|
||||
}()
|
||||
rc, err := h.Client.Dial(false, request.Address())
|
||||
if err != nil {
|
||||
_ = sendFailed(request, conn, socks5.RepHostUnreachable)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
// All good
|
||||
p := socks5.NewReply(socks5.RepSuccess, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00})
|
||||
_, _ = p.WriteTo(conn)
|
||||
defer rc.Close()
|
||||
closeErr = utils.PipePair(conn, rc, nil, nil)
|
||||
return nil
|
||||
} else {
|
||||
p := socks5.NewReply(socks5.RepCommandNotSupported, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00})
|
||||
_, _ = p.WriteTo(conn)
|
||||
return ErrUnsupportedCmd
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HyHandler) UDPHandle(server *Server, addr *net.UDPAddr, datagram *socks5.Datagram) error {
|
||||
// Not supported for now
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendFailed(request *socks5.Request, conn *net.TCPConn, rep byte) error {
|
||||
var p *socks5.Reply
|
||||
if request.Atyp == socks5.ATYPIPv4 || request.Atyp == socks5.ATYPDomain {
|
||||
p = socks5.NewReply(rep, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00})
|
||||
} else {
|
||||
p = socks5.NewReply(rep, socks5.ATYPIPv6, net.IPv6zero, []byte{0x00, 0x00})
|
||||
}
|
||||
_, err := p.WriteTo(conn)
|
||||
return err
|
||||
}
|
429
pkg/socks5/server.go
Normal file
429
pkg/socks5/server.go
Normal file
|
@ -0,0 +1,429 @@
|
|||
package socks5
|
||||
|
||||
import "errors"
|
||||
|
||||
// Modified based on https://github.com/txthinking/socks5/blob/master/server.go
|
||||
|
||||
import (
|
||||
"github.com/txthinking/socks5"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/txthinking/runnergroup"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsupportedCmd = errors.New("unsupported command")
|
||||
ErrUserPassAuth = errors.New("invalid username or password")
|
||||
)
|
||||
|
||||
// Server is socks5 server wrapper
|
||||
type Server struct {
|
||||
AuthFunc func(username, password string) bool
|
||||
Method byte
|
||||
SupportedCommands []byte
|
||||
TCPAddr *net.TCPAddr
|
||||
UDPAddr *net.UDPAddr
|
||||
ServerAddr *net.UDPAddr
|
||||
TCPListen *net.TCPListener
|
||||
UDPConn *net.UDPConn
|
||||
UDPExchanges *cache.Cache
|
||||
TCPDeadline int
|
||||
UDPDeadline int
|
||||
UDPSessionTime int // If client does't send address, use this fixed time
|
||||
Handle Handler
|
||||
TCPUDPAssociate *cache.Cache
|
||||
RunnerGroup *runnergroup.RunnerGroup
|
||||
}
|
||||
|
||||
// UDPExchange used to store client address and remote connection
|
||||
type UDPExchange struct {
|
||||
ClientAddr *net.UDPAddr
|
||||
RemoteConn *net.UDPConn
|
||||
}
|
||||
|
||||
func NewServer(addr, ip string, authFunc func(username, password string) bool, tcpDeadline, udpDeadline, udpSessionTime int) (*Server, error) {
|
||||
_, p, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
taddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uaddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
saddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(ip, p))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := socks5.MethodNone
|
||||
if authFunc != nil {
|
||||
m = socks5.MethodUsernamePassword
|
||||
}
|
||||
cs := cache.New(cache.NoExpiration, cache.NoExpiration)
|
||||
cs1 := cache.New(cache.NoExpiration, cache.NoExpiration)
|
||||
s := &Server{
|
||||
Method: m,
|
||||
AuthFunc: authFunc,
|
||||
SupportedCommands: []byte{socks5.CmdConnect, socks5.CmdUDP},
|
||||
TCPAddr: taddr,
|
||||
UDPAddr: uaddr,
|
||||
ServerAddr: saddr,
|
||||
UDPExchanges: cs,
|
||||
TCPDeadline: tcpDeadline,
|
||||
UDPDeadline: udpDeadline,
|
||||
UDPSessionTime: udpSessionTime,
|
||||
TCPUDPAssociate: cs1,
|
||||
RunnerGroup: runnergroup.New(),
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Negotiate handle negotiate packet.
|
||||
// This method do not handle gssapi(0x01) method now.
|
||||
// Error or OK both replied.
|
||||
func (s *Server) Negotiate(c *net.TCPConn) error {
|
||||
rq, err := socks5.NewNegotiationRequestFrom(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var got bool
|
||||
var m byte
|
||||
for _, m = range rq.Methods {
|
||||
if m == s.Method {
|
||||
got = true
|
||||
}
|
||||
}
|
||||
if !got {
|
||||
rp := socks5.NewNegotiationReply(socks5.MethodUnsupportAll)
|
||||
if _, err := rp.WriteTo(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
rp := socks5.NewNegotiationReply(s.Method)
|
||||
if _, err := rp.WriteTo(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.Method == socks5.MethodUsernamePassword {
|
||||
urq, err := socks5.NewUserPassNegotiationRequestFrom(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !s.AuthFunc(string(urq.Uname), string(urq.Passwd)) {
|
||||
urp := socks5.NewUserPassNegotiationReply(socks5.UserPassStatusFailure)
|
||||
if _, err := urp.WriteTo(c); err != nil {
|
||||
return err
|
||||
}
|
||||
return ErrUserPassAuth
|
||||
}
|
||||
urp := socks5.NewUserPassNegotiationReply(socks5.UserPassStatusSuccess)
|
||||
if _, err := urp.WriteTo(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRequest get request packet from client, and check command according to SupportedCommands
|
||||
// Error replied.
|
||||
func (s *Server) GetRequest(c *net.TCPConn) (*socks5.Request, error) {
|
||||
r, err := socks5.NewRequestFrom(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var supported bool
|
||||
for _, c := range s.SupportedCommands {
|
||||
if r.Cmd == c {
|
||||
supported = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !supported {
|
||||
var p *socks5.Reply
|
||||
if r.Atyp == socks5.ATYPIPv4 || r.Atyp == socks5.ATYPDomain {
|
||||
p = socks5.NewReply(socks5.RepCommandNotSupported, socks5.ATYPIPv4, net.IPv4zero, []byte{0x00, 0x00})
|
||||
} else {
|
||||
p = socks5.NewReply(socks5.RepCommandNotSupported, socks5.ATYPIPv6, net.IPv6zero, []byte{0x00, 0x00})
|
||||
}
|
||||
if _, err := p.WriteTo(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, ErrUnsupportedCmd
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Run server
|
||||
func (s *Server) ListenAndServe(h Handler) error {
|
||||
if h == nil {
|
||||
s.Handle = &DefaultHandle{}
|
||||
} else {
|
||||
s.Handle = h
|
||||
}
|
||||
s.RunnerGroup.Add(&runnergroup.Runner{
|
||||
Start: func() error {
|
||||
return s.RunTCPServer()
|
||||
},
|
||||
Stop: func() error {
|
||||
if s.TCPListen != nil {
|
||||
return s.TCPListen.Close()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
s.RunnerGroup.Add(&runnergroup.Runner{
|
||||
Start: func() error {
|
||||
return s.RunUDPServer()
|
||||
},
|
||||
Stop: func() error {
|
||||
if s.UDPConn != nil {
|
||||
return s.UDPConn.Close()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
return s.RunnerGroup.Wait()
|
||||
}
|
||||
|
||||
// RunTCPServer starts tcp server
|
||||
func (s *Server) RunTCPServer() error {
|
||||
var err error
|
||||
s.TCPListen, err = net.ListenTCP("tcp", s.TCPAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.TCPListen.Close()
|
||||
for {
|
||||
c, err := s.TCPListen.AcceptTCP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func(c *net.TCPConn) {
|
||||
defer c.Close()
|
||||
if s.TCPDeadline != 0 {
|
||||
if err := c.SetDeadline(time.Now().Add(time.Duration(s.TCPDeadline) * time.Second)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := s.Negotiate(c); err != nil {
|
||||
return
|
||||
}
|
||||
r, err := s.GetRequest(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = s.Handle.TCPHandle(s, c, r)
|
||||
}(c)
|
||||
}
|
||||
}
|
||||
|
||||
// RunUDPServer starts udp server
|
||||
func (s *Server) RunUDPServer() error {
|
||||
var err error
|
||||
s.UDPConn, err = net.ListenUDP("udp", s.UDPAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.UDPConn.Close()
|
||||
for {
|
||||
b := make([]byte, 65536)
|
||||
n, addr, err := s.UDPConn.ReadFromUDP(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func(addr *net.UDPAddr, b []byte) {
|
||||
d, err := socks5.NewDatagramFromBytes(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if d.Frag != 0x00 {
|
||||
return
|
||||
}
|
||||
_ = s.Handle.UDPHandle(s, addr, d)
|
||||
}(addr, b[0:n])
|
||||
}
|
||||
}
|
||||
|
||||
// Stop server
|
||||
func (s *Server) Shutdown() error {
|
||||
return s.RunnerGroup.Done()
|
||||
}
|
||||
|
||||
// TCP connection waits for associated UDP to close
|
||||
func (s *Server) TCPWaitsForUDP(addr *net.UDPAddr) error {
|
||||
_, p, err := net.SplitHostPort(addr.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p == "0" {
|
||||
time.Sleep(time.Duration(s.UDPSessionTime) * time.Second)
|
||||
return nil
|
||||
}
|
||||
ch := make(chan byte)
|
||||
s.TCPUDPAssociate.Set(addr.String(), ch, cache.DefaultExpiration)
|
||||
<-ch
|
||||
return nil
|
||||
}
|
||||
|
||||
// UDP releases associated TCP
|
||||
func (s *Server) UDPReleasesTCP(addr *net.UDPAddr) {
|
||||
v, ok := s.TCPUDPAssociate.Get(addr.String())
|
||||
if ok {
|
||||
ch := v.(chan byte)
|
||||
ch <- 0x00
|
||||
s.TCPUDPAssociate.Delete(addr.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Handler handle tcp, udp request
|
||||
type Handler interface {
|
||||
// Request has not been replied yet
|
||||
TCPHandle(*Server, *net.TCPConn, *socks5.Request) error
|
||||
UDPHandle(*Server, *net.UDPAddr, *socks5.Datagram) error
|
||||
}
|
||||
|
||||
// DefaultHandle implements Handler interface
|
||||
type DefaultHandle struct {
|
||||
}
|
||||
|
||||
// TCPHandle auto handle request. You may prefer to do yourself.
|
||||
func (h *DefaultHandle) TCPHandle(s *Server, c *net.TCPConn, r *socks5.Request) error {
|
||||
if r.Cmd == socks5.CmdConnect {
|
||||
rc, err := r.Connect(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
go func() {
|
||||
var bf [1024 * 2]byte
|
||||
for {
|
||||
if s.TCPDeadline != 0 {
|
||||
if err := rc.SetDeadline(time.Now().Add(time.Duration(s.TCPDeadline) * time.Second)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
i, err := rc.Read(bf[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if _, err := c.Write(bf[0:i]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
var bf [1024 * 2]byte
|
||||
for {
|
||||
if s.TCPDeadline != 0 {
|
||||
if err := c.SetDeadline(time.Now().Add(time.Duration(s.TCPDeadline) * time.Second)); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
i, err := c.Read(bf[:])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if _, err := rc.Write(bf[0:i]); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.Cmd == socks5.CmdUDP {
|
||||
caddr, err := r.UDP(c, s.ServerAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.TCPWaitsForUDP(caddr); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ErrUnsupportedCmd
|
||||
}
|
||||
|
||||
// UDPHandle auto handle packet. You may prefer to do yourself.
|
||||
func (h *DefaultHandle) UDPHandle(s *Server, addr *net.UDPAddr, d *socks5.Datagram) error {
|
||||
send := func(ue *UDPExchange, data []byte) error {
|
||||
_, err := ue.RemoteConn.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if socks5.Debug {
|
||||
log.Printf("Sent UDP data to remote. client: %#v server: %#v remote: %#v data: %#v\n", ue.ClientAddr.String(), ue.RemoteConn.LocalAddr().String(), ue.RemoteConn.RemoteAddr().String(), data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var ue *UDPExchange
|
||||
iue, ok := s.UDPExchanges.Get(addr.String())
|
||||
if ok {
|
||||
ue = iue.(*UDPExchange)
|
||||
return send(ue, d.Data)
|
||||
}
|
||||
|
||||
if socks5.Debug {
|
||||
log.Printf("Call udp: %#v\n", d.Address())
|
||||
}
|
||||
c, err := socks5.Dial.Dial("udp", d.Address())
|
||||
if err != nil {
|
||||
s.UDPReleasesTCP(addr)
|
||||
return err
|
||||
}
|
||||
// A UDP association terminates when the TCP connection that the UDP
|
||||
// ASSOCIATE request arrived on terminates.
|
||||
rc := c.(*net.UDPConn)
|
||||
ue = &UDPExchange{
|
||||
ClientAddr: addr,
|
||||
RemoteConn: rc,
|
||||
}
|
||||
if socks5.Debug {
|
||||
log.Printf("Created remote UDP conn for client. client: %#v server: %#v remote: %#v\n", addr.String(), ue.RemoteConn.LocalAddr().String(), d.Address())
|
||||
}
|
||||
if err := send(ue, d.Data); err != nil {
|
||||
s.UDPReleasesTCP(ue.ClientAddr)
|
||||
ue.RemoteConn.Close()
|
||||
return err
|
||||
}
|
||||
s.UDPExchanges.Set(ue.ClientAddr.String(), ue, cache.DefaultExpiration)
|
||||
go func(ue *UDPExchange) {
|
||||
defer func() {
|
||||
s.UDPReleasesTCP(ue.ClientAddr)
|
||||
s.UDPExchanges.Delete(ue.ClientAddr.String())
|
||||
ue.RemoteConn.Close()
|
||||
}()
|
||||
var b [65536]byte
|
||||
for {
|
||||
if s.UDPDeadline != 0 {
|
||||
if err := ue.RemoteConn.SetDeadline(time.Now().Add(time.Duration(s.UDPDeadline) * time.Second)); err != nil {
|
||||
log.Println(err)
|
||||
break
|
||||
}
|
||||
}
|
||||
n, err := ue.RemoteConn.Read(b[:])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if socks5.Debug {
|
||||
log.Printf("Got UDP data from remote. client: %#v server: %#v remote: %#v data: %#v\n", ue.ClientAddr.String(), ue.RemoteConn.LocalAddr().String(), ue.RemoteConn.RemoteAddr().String(), b[0:n])
|
||||
}
|
||||
a, addr, port, err := socks5.ParseAddress(ue.ClientAddr.String())
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
break
|
||||
}
|
||||
d1 := socks5.NewDatagram(a, addr, port, b[0:n])
|
||||
if _, err := s.UDPConn.WriteToUDP(d1.Bytes(), ue.ClientAddr); err != nil {
|
||||
break
|
||||
}
|
||||
if socks5.Debug {
|
||||
log.Printf("Sent Datagram. client: %#v server: %#v remote: %#v data: %#v %#v %#v %#v %#v %#v datagram address: %#v\n", ue.ClientAddr.String(), ue.RemoteConn.LocalAddr().String(), ue.RemoteConn.RemoteAddr().String(), d1.Rsv, d1.Frag, d1.Atyp, d1.DstAddr, d1.DstPort, d1.Data, d1.Address())
|
||||
}
|
||||
}
|
||||
}(ue)
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue