WIP SOCKS5 proxy

This commit is contained in:
Toby 2020-04-22 13:45:25 -07:00
parent a424a17af3
commit d5640efd7e
25 changed files with 1024 additions and 214 deletions

21
LICENSE.md Normal file
View 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
View file

@ -0,0 +1,8 @@
![Logo](docs/logos/readme.png)
[![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
View 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
View 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
View 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
View 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
View 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
View 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())
}

View file

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

View file

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

View file

@ -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, "_", "-")
}

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

8
go.mod
View file

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

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

View file

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

View file

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

View file

@ -29,8 +29,8 @@ message ServerAuthResponse {
}
enum ConnectionType {
TCP = 0;
UDP = 1;
Stream = 0;
Packet = 1;
}
enum ConnectResult {

View file

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

View file

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

View file

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