From 0ff759e7564f4c0076b93e8451a87fafad1a078f Mon Sep 17 00:00:00 2001 From: jkito <belter@riseup.net> Date: Fri, 5 Jul 2024 00:06:52 +0530 Subject: [PATCH] update obfsvpn to latest commit on main branch this updates the helpers starting the obfsvpn bridge to use the new client interface that was introduced in obfsvpn 1.0.0 --- go.mod | 5 +- go.sum | 9 +- pkg/vpn/openvpn.go | 69 +- .../0xacab.org/leap/obfsvpn/client/client.go | 472 +++++++++++-- .../leap/obfsvpn/client/hopclient.go | 444 ------------ .../leap/obfsvpn/client/interfaces.go | 7 - vendor/0xacab.org/leap/obfsvpn/obfsvpn/kcp.go | 56 ++ .../leap/obfsvpn/obfsvpn/listener.go | 45 +- .../github.com/kalikaneko/socks5/.gitignore | 3 - vendor/github.com/kalikaneko/socks5/LICENSE | 22 - vendor/github.com/kalikaneko/socks5/README.md | 316 --------- .../github.com/kalikaneko/socks5/address.go | 263 ------- vendor/github.com/kalikaneko/socks5/auth.go | 165 ----- vendor/github.com/kalikaneko/socks5/client.go | 542 --------------- vendor/github.com/kalikaneko/socks5/common.go | 47 -- vendor/github.com/kalikaneko/socks5/conn.go | 116 ---- vendor/github.com/kalikaneko/socks5/port.go | 101 --- .../github.com/kalikaneko/socks5/protocol.go | 155 ----- vendor/github.com/kalikaneko/socks5/reply.go | 14 - .../github.com/kalikaneko/socks5/request.go | 70 -- vendor/github.com/kalikaneko/socks5/server.go | 653 ------------------ .../github.com/kalikaneko/socks5/transport.go | 122 ---- vendor/github.com/xtaci/kcp-go/v5/.travis.yml | 4 + vendor/github.com/xtaci/kcp-go/v5/README.md | 6 - vendor/github.com/xtaci/kcp-go/v5/donate.png | Bin 4420 -> 0 bytes vendor/github.com/xtaci/kcp-go/v5/fec.go | 12 +- vendor/github.com/xtaci/kcp-go/v5/sess.go | 49 +- .../xtaci/kcp-go/v5/wechat_donate.jpg | Bin 35307 -> 0 bytes vendor/modules.txt | 11 +- 29 files changed, 581 insertions(+), 3197 deletions(-) delete mode 100644 vendor/0xacab.org/leap/obfsvpn/client/hopclient.go delete mode 100644 vendor/0xacab.org/leap/obfsvpn/client/interfaces.go create mode 100644 vendor/0xacab.org/leap/obfsvpn/obfsvpn/kcp.go delete mode 100644 vendor/github.com/kalikaneko/socks5/.gitignore delete mode 100644 vendor/github.com/kalikaneko/socks5/LICENSE delete mode 100644 vendor/github.com/kalikaneko/socks5/README.md delete mode 100644 vendor/github.com/kalikaneko/socks5/address.go delete mode 100644 vendor/github.com/kalikaneko/socks5/auth.go delete mode 100644 vendor/github.com/kalikaneko/socks5/client.go delete mode 100644 vendor/github.com/kalikaneko/socks5/common.go delete mode 100644 vendor/github.com/kalikaneko/socks5/conn.go delete mode 100644 vendor/github.com/kalikaneko/socks5/port.go delete mode 100644 vendor/github.com/kalikaneko/socks5/protocol.go delete mode 100644 vendor/github.com/kalikaneko/socks5/reply.go delete mode 100644 vendor/github.com/kalikaneko/socks5/request.go delete mode 100644 vendor/github.com/kalikaneko/socks5/server.go delete mode 100644 vendor/github.com/kalikaneko/socks5/transport.go delete mode 100644 vendor/github.com/xtaci/kcp-go/v5/donate.png delete mode 100644 vendor/github.com/xtaci/kcp-go/v5/wechat_donate.jpg diff --git a/go.mod b/go.mod index a3c7bd75..4d44b63a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22 toolchain go1.22.2 require ( - 0xacab.org/leap/obfsvpn v0.0.0-20240422180703-83037b24d5cc + 0xacab.org/leap/obfsvpn v1.0.1-0.20240625123757-59f234eea051 git.torproject.org/pluggable-transports/goptlib.git v1.3.0 git.torproject.org/pluggable-transports/snowflake.git v1.1.0 github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a @@ -16,7 +16,7 @@ require ( github.com/pion/webrtc/v3 v3.2.24 github.com/sevlyar/go-daemon v0.1.6 github.com/smartystreets/goconvey v1.6.4 - github.com/xtaci/kcp-go/v5 v5.6.1 + github.com/xtaci/kcp-go/v5 v5.6.3 github.com/xtaci/smux v1.5.24 // Do not update obfs4 past e330d1b7024b, a backwards incompatible change was // made that will break negotiation!! riseup should move to the newest asap. @@ -56,7 +56,6 @@ require ( github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect - github.com/kalikaneko/socks5 v1.0.1 // indirect github.com/klauspost/compress v1.16.6 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/reedsolomon v1.12.1 // indirect diff --git a/go.sum b/go.sum index 9a9a8a31..56ee9356 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ 0xacab.org/leap/bitmask-core v0.0.0-20240529192952-8ea2f4de269e h1:dg8K5g8gpWYh8GfJPJKlSF6twnjFa4Ui9FMEahAf3jw= 0xacab.org/leap/bitmask-core v0.0.0-20240529192952-8ea2f4de269e/go.mod h1:kuAy05ISfgrZJIPj2i7lXRxRlFF8gtRy3KvYYOgMYvI= -0xacab.org/leap/obfsvpn v0.0.0-20240422180703-83037b24d5cc h1:QyADySTHZtWA81k58D/eD5aLFG2n8QaeKjIB/aMI3OU= -0xacab.org/leap/obfsvpn v0.0.0-20240422180703-83037b24d5cc/go.mod h1:cOGeSmVkgxW5qYIOSvkBeFxwBnoOYsjuQWFYB5YYlm4= +0xacab.org/leap/obfsvpn v1.0.1-0.20240625123757-59f234eea051 h1:z9sIIud8NbKW5+Qp0k6BnogK3O8CPWCN3Rx5Jbzq7bQ= +0xacab.org/leap/obfsvpn v1.0.1-0.20240625123757-59f234eea051/go.mod h1:uesfK5XkHYSgHRpE9/Bb2yKkKTE6VFViyjbU/z4ST60= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= @@ -262,8 +262,6 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kalikaneko/socks5 v1.0.1 h1:j60X3e1sAmy/LK/WtHyU3xH6uMmwlRJyc9TE3AqYDpU= -github.com/kalikaneko/socks5 v1.0.1/go.mod h1:XAMwFixakJUP0wv6pEVp6v2wx3SwhQEdL8TGDYKFAGU= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= @@ -563,8 +561,9 @@ github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgk github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg= github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= -github.com/xtaci/kcp-go/v5 v5.6.1 h1:Pwn0aoeNSPF9dTS7IgiPXn0HEtaIlVb6y5UKWPsx8bI= github.com/xtaci/kcp-go/v5 v5.6.1/go.mod h1:W3kVPyNYwZ06p79dNwFWQOVFrdcBpDBsdyvK8moQrYo= +github.com/xtaci/kcp-go/v5 v5.6.3 h1:yd59SKXdJ0PBxeMBy3apalxFCEmBLGgQmL6nP46tU0g= +github.com/xtaci/kcp-go/v5 v5.6.3/go.mod h1:uIuw2KEg3FcmEdS4PeXHaGty9Ui7NYb1WKIrSDwpMg4= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E= github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0= diff --git a/pkg/vpn/openvpn.go b/pkg/vpn/openvpn.go index 1ece141f..416774f8 100644 --- a/pkg/vpn/openvpn.go +++ b/pkg/vpn/openvpn.go @@ -30,7 +30,8 @@ import ( "0xacab.org/leap/bitmask-vpn/pkg/config" "0xacab.org/leap/bitmask-vpn/pkg/vpn/bonafide" - obfsvpn "0xacab.org/leap/obfsvpn/client" + obfsvpnClient "0xacab.org/leap/obfsvpn/client" + "0xacab.org/leap/obfsvpn/obfsvpn" ) const ( @@ -68,11 +69,32 @@ func (b *Bitmask) CanStartVPN() bool { func (b *Bitmask) startTransportForPrivateBridge(ctx context.Context, gw bonafide.Gateway) (proxy string, err error) { proxyAddr := "127.0.0.1:8080" - kcpMode := false + kcpConfig := obfsvpn.KCPConfig{ + Enabled: false, + } if os.Getenv("LEAP_KCP") == "1" { - kcpMode = true + kcpConfig = *obfsvpn.DefaultKCPConfig() + } + + obfsvpnCfg := obfsvpnClient.Config{ + ProxyAddr: proxyAddr, + HoppingConfig: obfsvpnClient.HoppingConfig{ + Enabled: false, + }, + KCPConfig: kcpConfig, + Obfs4Cert: gw.Options["cert"], + RemoteIP: gw.IPAddress, + RemotePort: gw.Ports[0], } - b.obfsvpnProxy = obfsvpn.NewClient(ctx, kcpMode, proxyAddr, gw.Options["cert"]).(*obfsvpn.Client) + log.Info().Str("OBFS4 local proxy address:", obfsvpnCfg.ProxyAddr). + Str("OBFS4 Cert:", obfsvpnCfg.Obfs4Cert). + Bool("OBFS4+KCP:", kcpConfig.Enabled). + Str("OBFS4 Hostname", gw.Host). + Str("OBFS4 IP", gw.IPAddress). + Str("OBFS4 Port:", obfsvpnCfg.RemotePort). + Msg("OBFS4 bridge connection parameters") + ctx, cancelFunc := context.WithCancel(ctx) + b.obfsvpnProxy = obfsvpnClient.NewClient(ctx, cancelFunc, obfsvpnCfg) go func() { _, err = b.obfsvpnProxy.Start() if err != nil { @@ -126,21 +148,25 @@ func (b *Bitmask) startTransport(ctx context.Context, host string) (proxy string Str("ip", gw.IPAddress). Msg("Selected Gateway") - kcpMode := false + kcpConfig := obfsvpn.KCPConfig{ + Enabled: false, + } if os.Getenv("LEAP_KCP") == "1" { - kcpMode = true + kcpConfig = *obfsvpn.DefaultKCPConfig() } - log.Debug(). - Str("host", gw.Host). - Str("ip", gw.IPAddress). - Bool("kcp", kcpMode). - Str("cert", gw.Options["cert"]). - Str("proxyAddr", proxyAddr). - Str("transport", b.transport). - Msg("Using gateway") - - b.obfsvpnProxy = obfsvpn.NewClient(ctx, kcpMode, proxyAddr, gw.Options["cert"]).(*obfsvpn.Client) + obfsvpnCfg := obfsvpnClient.Config{ + ProxyAddr: proxyAddr, + HoppingConfig: obfsvpnClient.HoppingConfig{ + Enabled: false, + }, + KCPConfig: kcpConfig, + Obfs4Cert: gw.Options["cert"], + RemoteIP: gw.IPAddress, + RemotePort: gw.Ports[0], + } + ctx, cancelFunc := context.WithCancel(ctx) + b.obfsvpnProxy = obfsvpnClient.NewClient(ctx, cancelFunc, obfsvpnCfg) go func() { _, err = b.obfsvpnProxy.Start() if err != nil { @@ -154,6 +180,14 @@ func (b *Bitmask) startTransport(ctx context.Context, host string) (proxy string Str("host", gw.Host). Msg("Connected via obfs4") }() + log.Debug(). + Str("host", gw.Host). + Str("ip", gw.IPAddress). + Bool("kcp", kcpConfig.Enabled). + Str("cert", gw.Options["cert"]). + Str("proxyAddr", proxyAddr). + Str("transport", b.transport). + Msg("Using gateway") return proxyAddr, nil } @@ -278,8 +312,7 @@ func (b *Bitmask) startOpenVPN(ctx context.Context) error { } proxyArgs := strings.Split(proxy, ":") - arg = append(arg, "--socks-proxy", proxyArgs[0], proxyArgs[1]) - arg = append(arg, "--remote", gw.IPAddress, gw.Ports[0], "tcp4") + arg = append(arg, "--remote", proxyArgs[0], proxyArgs[1], "udp") arg = append(arg, "--route", gw.IPAddress, "255.255.255.255", "net_gateway") } else { diff --git a/vendor/0xacab.org/leap/obfsvpn/client/client.go b/vendor/0xacab.org/leap/obfsvpn/client/client.go index 193b88e3..ad9745fb 100644 --- a/vendor/0xacab.org/leap/obfsvpn/client/client.go +++ b/vendor/0xacab.org/leap/obfsvpn/client/client.go @@ -1,18 +1,28 @@ -// Package client exposes a socks5 proxy that uses obfs4 to communicate with the server, -// with an optional kcp wire transport. +// Package client exposes a proxy that uses obfs4 to communicate with the server, +// with an optional KCP wire transport. package client import ( "context" + "encoding/json" "errors" "fmt" "log" + "math/rand" "net" "sync" + "time" "0xacab.org/leap/obfsvpn/obfsvpn" - "github.com/kalikaneko/socks5" - "github.com/xtaci/kcp-go" +) + +type clientState string + +const ( + starting clientState = "STARTING" + running clientState = "RUNNING" + stopping clientState = "STOPPING" + stopped clientState = "STOPPED" ) var ( @@ -21,113 +31,453 @@ var ( ErrBadConfig = errors.New("configuration error") ) -type Client struct { - ctx context.Context - kcp bool - SocksAddr string - obfs4Cert string - server *socks5.Server - EventLogger EventLogger - mux sync.Mutex -} - type EventLogger interface { Log(state string, message string) Error(message string) } -func NewClient(ctx context.Context, kcp bool, socksAddr, obfs4Cert string) ObfsClient { +const ( + dialGiveUpTime = 15 * time.Second +) + +type Obfs4Config struct { + Remote string + Cert string +} + +func (oc *Obfs4Config) String() string { + return oc.Remote +} + +type Config struct { + ProxyAddr string `json:"proxy_addr"` + HoppingConfig HoppingConfig `json:"hopping_config"` + KCPConfig obfsvpn.KCPConfig `json:"kcp_config"` + RemoteIP string `json:"remote_ip"` + RemotePort string `json:"remote_port"` + Obfs4Cert string `json:"obfs4_cert"` +} + +type HoppingConfig struct { + Enabled bool `json:"enabled"` + Remotes []string `json:"remotes"` + Obfs4Certs []string `json:"obfs4_certs"` + PortSeed int64 `json:"port_seed"` + PortCount uint `json:"port_count"` + MinHopSeconds uint `json:"min_hop_seconds"` + HopJitter uint `json:"hop_jitter"` +} + +type Client struct { + kcpConfig obfsvpn.KCPConfig + ProxyAddr string + newObfs4Conn chan net.Conn + obfs4Conns []net.Conn + obfs4Endpoints []*Obfs4Config + obfs4Dialer *obfsvpn.Dialer + obfs4Failures map[string]int32 + EventLogger EventLogger + state clientState + ctx context.Context + mux sync.Mutex + stop context.CancelFunc + openvpnConn *net.UDPConn + openvpnAddr *net.UDPAddr + openvpnAddrLock sync.RWMutex + outLock sync.Mutex + hopEnabled bool + minHopSeconds uint + hopJitter uint +} + +func NewClient(ctx context.Context, stop context.CancelFunc, config Config) *Client { + obfs4Endpoints := generateObfs4Config(config) return &Client{ - ctx: ctx, - kcp: kcp, - obfs4Cert: obfs4Cert, - SocksAddr: socksAddr, + ProxyAddr: config.ProxyAddr, + hopEnabled: config.HoppingConfig.Enabled, + ctx: ctx, + hopJitter: config.HoppingConfig.HopJitter, + kcpConfig: config.KCPConfig, + obfs4Failures: map[string]int32{}, + minHopSeconds: config.HoppingConfig.MinHopSeconds, + newObfs4Conn: make(chan net.Conn), + obfs4Endpoints: obfs4Endpoints, + stop: stop, + state: stopped, + } +} + +// NewFFIClient creates a new client +// This function is exposed to the JNI and since it's not allowed to pass objects that contain slices (other than byte slices) over the JNI +// we have to pass a json formatted string and convert it to a Config struct for further processing +func NewFFIClient(jsonConfig string) (*Client, error) { + config := Config{} + err := json.Unmarshal([]byte(jsonConfig), &config) + if err != nil { + return nil, err } + ctx, stop := context.WithCancel(context.Background()) + return NewClient(ctx, stop, config), nil +} + +func generateObfs4Config(config Config) []*Obfs4Config { + obfsEndpoints := []*Obfs4Config{} + + if config.HoppingConfig.Enabled { + for i, obfs4Remote := range config.HoppingConfig.Remotes { + // We want a non-crypto RNG so that we can share a seed + // #nosec G404 + r := rand.New(rand.NewSource(config.HoppingConfig.PortSeed)) + for pi := 0; pi < int(config.HoppingConfig.PortCount); pi++ { + portOffset := r.Intn(obfsvpn.PortHopRange) + addr := net.JoinHostPort(obfs4Remote, fmt.Sprint(portOffset+obfsvpn.MinHopPort)) + obfsEndpoints = append(obfsEndpoints, &Obfs4Config{ + Cert: config.HoppingConfig.Obfs4Certs[i], + Remote: addr, + }) + } + } + } else { + addr := net.JoinHostPort(config.RemoteIP, config.RemotePort) + obfsEndpoints = append(obfsEndpoints, &Obfs4Config{ + Cert: config.Obfs4Cert, + Remote: addr, + }) + } + + log.Printf("obfs4 endpoints: %+v", obfsEndpoints) + return obfsEndpoints } func (c *Client) Start() (bool, error) { + var err error + c.mux.Lock() defer func() { - c.log("STOPPED", "") + c.updateState(stopped) + + if err != nil { + c.mux.Unlock() + } }() if c.IsStarted() { c.error("Cannot start proxy server, already running") - return false, ErrAlreadyRunning + err = ErrAlreadyRunning + return false, err } - c.server = &socks5.Server{ - Addr: c.SocksAddr, - BindIP: "127.0.0.1", + if len(c.obfs4Endpoints) == 0 { + c.error("Cannot start proxy server, no valid endpoints") + err = ErrBadConfig + return false, err } - dialer, err := obfsvpn.NewDialerFromCert(c.obfs4Cert) + c.updateState(starting) + + obfs4Endpoint := c.obfs4Endpoints[0] + + c.obfs4Dialer, err = obfsvpn.NewDialerFromCert(obfs4Endpoint.Cert) if err != nil { - c.error("Error getting dialer: %v\n", err) - return false, err + return false, fmt.Errorf("could not dial obfs4 remote: %w", err) } - switch { - case c.kcp: - dialer.DialFunc = func(network, address string) (net.Conn, error) { - c.log("RUNNING", "client.Start(): dialing kcp://%s\n", address) - return kcp.Dial(address) - } + if c.kcpConfig.Enabled { + c.obfs4Dialer.DialFunc = obfsvpn.GetKCPDialer(c.kcpConfig, c.log) + } + + obfs4Conn, err := c.obfs4Dialer.Dial("tcp", obfs4Endpoint.Remote) + if err != nil { + c.error("Could not dial obfs4 remote: %v", err) + return false, fmt.Errorf("could not dial remote: %w", err) + } + + c.obfs4Conns = []net.Conn{obfs4Conn} + + c.updateState(running) + + proxyAddr, err := net.ResolveUDPAddr("udp", c.ProxyAddr) + if err != nil { + return false, fmt.Errorf("cannot resolve UDP addr: %w", err) } - c.server.Dial = dialer.Dial + c.openvpnConn, err = net.ListenUDP("udp", proxyAddr) + if err != nil { + return false, fmt.Errorf("error accepting udp connection: %w", err) + } + + if c.hopEnabled { + go c.hop() + } - c.log("RUNNING", "[+] Starting socks5 proxy at %s\n", c.SocksAddr) + go c.readUDPWriteTCP() - errCh := make(chan error) - go c.startSocksServer(errCh) + go c.readTCPWriteUDP() c.mux.Unlock() - select { - case <-c.ctx.Done(): - return true, nil - case err := <-errCh: - c.server = nil - return false, err + <-c.ctx.Done() + + return true, nil +} + +// updateState sets a new client state, logs it and sends an event to the clients +// EventLogger in case it is available. Always set the state with this function in +// order to ensure integrating clients receive an update state event via FFI. +func (c *Client) updateState(state clientState) { + c.state = state + c.log("Update state: %v", state) +} + +// pickRandomRemote returns a random remote from the internal array. +// An obvious improvement to this function is to check the number of failures in c.obfs4Failures and avoid +// a given remote if it failed more than a threshold. A consecuence is that +// we'll have to return an unrecoverable error from hop() if there are no +// more usable remotes. If we ever want to get fancy, an even better heuristic +// can be to avoid IPs that have more failures than the average. +func (c *Client) pickRandomEndpoint() *Obfs4Config { + // #nosec G404 + i := rand.Intn(len(c.obfs4Endpoints)) + endpoint := c.obfs4Endpoints[i] + // here we could check if the number of failures is ok-ish. we can also do moving averages etc. + return endpoint +} + +func (c *Client) hop() { + for { + select { + case <-c.ctx.Done(): + return + default: + } + + // #nosec G404 + sleepSeconds := rand.Intn(int(c.hopJitter)) + int(c.minHopSeconds) + c.log("Sleeping %d seconds...", sleepSeconds) + time.Sleep(time.Duration(sleepSeconds) * time.Second) + + obfs4Endpoint := c.pickRandomEndpoint() + + host, port, err := net.SplitHostPort(obfs4Endpoint.Remote) + if err != nil { + c.error("Could not split obfs4 remote: %v", err) + continue + } + remoteAddrs, err := net.DefaultResolver.LookupHost(c.ctx, host) + if err != nil { + c.error("Could not lookup obfs4 remote: %v", err) + continue + } + + if len(remoteAddrs) <= 0 { + c.error("Could not lookup obfs4 remote: %v", err) + continue + } + + newRemote := net.JoinHostPort(remoteAddrs[0], port) + + for _, obfs4Conn := range c.obfs4Conns { + if obfs4Conn.RemoteAddr().String() == newRemote { + c.log("Not hopping to address already in obfs4Conns list: %v", newRemote) + continue + } + } + + c.log("HOPPING to %+v", newRemote) + + obfs4Dialer, err := obfsvpn.NewDialerFromCert(obfs4Endpoint.Cert) + if err != nil { + c.error("Could not dial obfs4 remote: %v", err) + return + } + + if c.kcpConfig.Enabled { + c.obfs4Dialer.DialFunc = obfsvpn.GetKCPDialer(c.kcpConfig, c.log) + } + + ctx, cancel := context.WithTimeout(context.Background(), dialGiveUpTime) + defer cancel() + + c.log("Dialing new remote: %v", newRemote) + newObfs4Conn, err := obfs4Dialer.DialContext(ctx, "tcp", newRemote) + + if err != nil { + _, ok := c.obfs4Failures[newRemote] + if ok { + c.obfs4Failures[newRemote] += 1 + } else { + c.obfs4Failures[newRemote] = 1 + } + c.error("Could not dial obfs4 remote: %v (failures: %d)", err, c.obfs4Failures[newRemote]) + } + + if newObfs4Conn == nil { + c.error("Did not get obfs4: %v ", err) + } else { + c.outLock.Lock() + c.obfs4Conns = append([]net.Conn{newObfs4Conn}, c.obfs4Conns...) + c.outLock.Unlock() + + c.newObfs4Conn <- newObfs4Conn + c.log("Dialed new remote") + + // If we wait sleepSeconds here to clean up the previous connection, we can guarantee that the + // connection list will not grow unbounded + go func() { + time.Sleep(time.Duration(sleepSeconds) * time.Second) + + c.cleanupOldConn() + }() + } } } -func (c *Client) startSocksServer(ch chan error) { - if err := c.server.ListenAndServe(); err != nil { - c.error("error while listening: %v\n", err) - ch <- err +func (c *Client) cleanupOldConn() { + c.outLock.Lock() + defer c.outLock.Unlock() + + if len(c.obfs4Conns) > 1 { + c.log("Connections: %v", len(c.obfs4Conns)) + connToClose := c.obfs4Conns[len(c.obfs4Conns)-1] + if connToClose != nil { + c.log("Cleaning up old connection to %v", connToClose.RemoteAddr()) + + err := connToClose.Close() + if err != nil { + c.log("Error closing obfs4 connection to %v: %v", connToClose.RemoteAddr(), err) + } + } + + // Remove the connection from our tracking list + c.obfs4Conns = c.obfs4Conns[:len(c.obfs4Conns)-1] } } -func (c *Client) Stop() (bool, error) { - if !c.IsStarted() { - return false, ErrNotRunning +func (c *Client) readUDPWriteTCP() { + datagramBuffer := make([]byte, obfsvpn.MaxUDPLen) + for { + select { + case <-c.ctx.Done(): + return + default: + } + + tcpBuffer, newOpenvpnAddr, err := obfsvpn.ReadUDPFrameTCP(c.openvpnConn, datagramBuffer) + if err != nil { + c.error("Read err from %v: %v", c.openvpnConn.LocalAddr(), err) + continue + } + + if newOpenvpnAddr != c.openvpnAddr { + c.openvpnAddrLock.Lock() + c.openvpnAddr = newOpenvpnAddr + c.openvpnAddrLock.Unlock() + } + + func() { + // Always write to the first connection in our list because it will be most up to date + func() { + conn, err := c.getUsableConnection() + if err != nil { + c.log("Cannot get connection: %s", err) + return + } + _, err = conn.Write(tcpBuffer) + if err != nil { + c.log("Write err from %v to %v: %v", conn.LocalAddr(), conn.RemoteAddr(), err) + return + } + }() + }() + } +} + +func (c *Client) getUsableConnection() (net.Conn, error) { + c.outLock.Lock() + defer c.outLock.Unlock() + + if len(c.obfs4Conns) == 0 { + return nil, errors.New("no usable connection") + } else { + return c.obfs4Conns[0], nil + } +} + +func (c *Client) readTCPWriteUDP() { + for { + select { + case <-c.ctx.Done(): + return + default: + } + + fromTCP := make(chan []byte, 2048) + + handleObfs4Conn := func(conn net.Conn) { + datagramBuffer := make([]byte, obfsvpn.MaxUDPLen) + lengthBuffer := make([]byte, 2) + for { + udpBuffer, err := obfsvpn.ReadTCPFrameUDP(conn, datagramBuffer, lengthBuffer) + if err != nil { + c.error("Reading/framing error: %v", err) + return + } + + fromTCP <- udpBuffer + } + } + + go func() { + for { + newObfs4Conn := <-c.newObfs4Conn + + go handleObfs4Conn(newObfs4Conn) + } + }() + + go handleObfs4Conn(c.obfs4Conns[0]) + + for { + tcpBytes := <-fromTCP + + c.openvpnAddrLock.RLock() + _, err := c.openvpnConn.WriteToUDP(tcpBytes, c.openvpnAddr) + c.openvpnAddrLock.RUnlock() + if err != nil { + c.error("Write err from %v to %v: %v", c.openvpnConn.LocalAddr(), c.openvpnConn.RemoteAddr(), err) + return + } + } } +} +func (c *Client) Stop() (bool, error) { c.mux.Lock() defer c.mux.Unlock() - if err := c.server.Close(); err != nil { - c.error("error while stopping: %v\n", err) - return false, err + if !c.IsStarted() { + return false, ErrNotRunning } - c.server = nil + c.stop() + c.openvpnConn.Close() + + c.updateState(stopped) + return true, nil } -func (c *Client) log(state string, format string, a ...interface{}) { +func (c *Client) log(format string, a ...interface{}) { if c.EventLogger != nil { - c.EventLogger.Log(state, fmt.Sprintf(format, a...)) + c.EventLogger.Log(string(c.state), fmt.Sprintf(format, a...)) return } if format == "" { - log.Print(a...) + log.Println(a...) return } - log.Printf(format, a...) + log.Printf(format+"\n", a...) } func (c *Client) error(format string, a ...interface{}) { @@ -136,12 +486,12 @@ func (c *Client) error(format string, a ...interface{}) { return } if format == "" { - log.Print(a...) + log.Println(a...) return } - log.Printf(format, a...) + log.Printf(format+"\n", a...) } func (c *Client) IsStarted() bool { - return c.server != nil + return c.state != stopped } diff --git a/vendor/0xacab.org/leap/obfsvpn/client/hopclient.go b/vendor/0xacab.org/leap/obfsvpn/client/hopclient.go deleted file mode 100644 index 2f5fbee0..00000000 --- a/vendor/0xacab.org/leap/obfsvpn/client/hopclient.go +++ /dev/null @@ -1,444 +0,0 @@ -// Package client exposes a socks5 proxy that uses obfs4 to communicate with the server, -// with an optional KCP wire transport. -package client - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "log" - "math/rand" - "net" - "sync" - "time" - - "0xacab.org/leap/obfsvpn/obfsvpn" - "github.com/xtaci/kcp-go" -) - -type clientState string - -const ( - starting clientState = "STARTING" - running clientState = "RUNNING" - stopping clientState = "STOPPING" - stopped clientState = "STOPPED" -) - -const ( - dialGiveUpTime = 15 * time.Second -) - -type Obfs4Config struct { - Remote string - Cert string -} - -func (oc *Obfs4Config) String() string { - return oc.Remote -} - -type HoppingConfig struct { - KCP bool `json:"kcp"` - ProxyAddr string `json:"proxy_addr"` - Remotes []string `json:"remotes"` - Certs []string `json:"certs"` - PortSeed int64 `json:"port_seed"` - PortCount uint `json:"port_count"` - MinHopSeconds uint `json:"min_hop_seconds"` - HopJitter uint `json:"hop_jitter"` -} - -type HopClient struct { - kcp bool - ProxyAddr string - newObfs4Conn chan net.Conn - obfs4Conns []net.Conn - obfs4Endpoints []*Obfs4Config - obfs4Dialer *obfsvpn.Dialer - obfs4Failures map[string]int32 - EventLogger EventLogger - state clientState - ctx context.Context - stop context.CancelFunc - openvpnConn *net.UDPConn - openvpnAddr *net.UDPAddr - openvpnAddrLock sync.RWMutex - outLock sync.Mutex - minHopSeconds uint - hopJitter uint -} - -func NewHopClient(ctx context.Context, stop context.CancelFunc, config HoppingConfig) ObfsClient { - obfs4Endpoints := generateObfs4Config(config.Remotes, config.PortSeed, config.PortCount, config.Certs) - return &HopClient{ - ProxyAddr: config.ProxyAddr, - - ctx: ctx, - hopJitter: config.HopJitter, - kcp: config.KCP, - obfs4Failures: map[string]int32{}, - minHopSeconds: config.MinHopSeconds, - newObfs4Conn: make(chan net.Conn), - obfs4Endpoints: obfs4Endpoints, - stop: stop, - state: stopped, - } -} - -// NewFFIHopClient creates a new Hopping PT client -// This function is exposed to the JNI and since it's not allowed to pass objects that contain slices (other than byte slices) over the JNI -// we have to pass a json formatted string and convert it to a HoppingConfig struct for further processing -func NewFFIHopClient(hoppingConfig string) (*HopClient, error) { - config := HoppingConfig{} - err := json.Unmarshal([]byte(hoppingConfig), &config) - if err != nil { - return nil, err - } - ctx, stop := context.WithCancel(context.Background()) - return NewHopClient(ctx, stop, config).(*HopClient), nil -} - -func generateObfs4Config(remoteIPs []string, portSeed int64, portCount uint, certs []string) []*Obfs4Config { - obfsEndpoints := []*Obfs4Config{} - - for i, obfs4Remote := range remoteIPs { - // We want a non-crypto RNG so that we can share a seed - // #nosec G404 - r := rand.New(rand.NewSource(portSeed)) - for pi := 0; pi < int(portCount); pi++ { - portOffset := r.Intn(obfsvpn.PortHopRange) - addr := net.JoinHostPort(obfs4Remote, fmt.Sprint(portOffset+obfsvpn.MinHopPort)) - obfsEndpoints = append(obfsEndpoints, &Obfs4Config{ - Cert: certs[i], - Remote: addr, - }) - } - } - - log.Printf("obfs4 endpoints: %+v", obfsEndpoints) - return obfsEndpoints -} - -func (c *HopClient) Start() (bool, error) { - defer func() { - c.state = stopped - c.log("Start function ended") - }() - - if c.IsStarted() { - c.error("Cannot start proxy server, already running") - return false, ErrAlreadyRunning - } - - if len(c.obfs4Endpoints) == 0 { - c.error("Cannot start proxy server, no valid endpoints") - return false, ErrBadConfig - } - - c.state = starting - - var err error - - obfs4Endpoint := c.obfs4Endpoints[0] - - c.obfs4Dialer, err = obfsvpn.NewDialerFromCert(obfs4Endpoint.Cert) - if err != nil { - return false, fmt.Errorf("could not dial obfs4 remote: %w", err) - } - - if c.kcp { - c.obfs4Dialer.DialFunc = func(network, address string) (net.Conn, error) { - c.log("Dialing kcp://%s", address) - return kcp.Dial(address) - } - } - - obfs4Conn, err := c.obfs4Dialer.Dial("tcp", obfs4Endpoint.Remote) - if err != nil { - c.error("Could not dial obfs4 remote: %v", err) - } - - c.obfs4Conns = []net.Conn{obfs4Conn} - - c.state = running - - proxyAddr, err := net.ResolveUDPAddr("udp", c.ProxyAddr) - if err != nil { - return false, fmt.Errorf("cannot resolve UDP addr: %w", err) - } - - c.openvpnConn, err = net.ListenUDP("udp", proxyAddr) - if err != nil { - return false, fmt.Errorf("error accepting udp connection: %w", err) - } - - go c.hop() - - go c.readUDPWriteTCP() - - go c.readTCPWriteUDP() - - <-c.ctx.Done() - - return true, nil -} - -// pickRandomRemote returns a random remote from the internal array. -// An obvious improvement to this function is to check the number of failures in c.obfs4Failures and avoid -// a given remote if it failed more than a threshold. A consecuence is that -// we'll have to return an unrecoverable error from hop() if there are no -// more usable remotes. If we ever want to get fancy, an even better heuristic -// can be to avoid IPs that have more failures than the average. -func (c *HopClient) pickRandomEndpoint() *Obfs4Config { - // #nosec G404 - i := rand.Intn(len(c.obfs4Endpoints)) - endpoint := c.obfs4Endpoints[i] - // here we could check if the number of failures is ok-ish. we can also do moving averages etc. - return endpoint -} - -func (c *HopClient) hop() { - for { - select { - case <-c.ctx.Done(): - return - default: - } - - // #nosec G404 - sleepSeconds := rand.Intn(int(c.hopJitter)) + int(c.minHopSeconds) - c.log("Sleeping %d seconds...", sleepSeconds) - time.Sleep(time.Duration(sleepSeconds) * time.Second) - - obfs4Endpoint := c.pickRandomEndpoint() - - host, port, err := net.SplitHostPort(obfs4Endpoint.Remote) - if err != nil { - c.error("Could not split obfs4 remote: %v", err) - continue - } - remoteAddrs, err := net.DefaultResolver.LookupHost(c.ctx, host) - if err != nil { - c.error("Could not lookup obfs4 remote: %v", err) - continue - } - - if len(remoteAddrs) <= 0 { - c.error("Could not lookup obfs4 remote: %v", err) - continue - } - - newRemote := net.JoinHostPort(remoteAddrs[0], port) - - c.log("HOPPING to %+v", newRemote) - - obfs4Dialer, err := obfsvpn.NewDialerFromCert(obfs4Endpoint.Cert) - if err != nil { - c.error("Could not dial obfs4 remote: %v", err) - return - } - - if c.kcp { - obfs4Dialer.DialFunc = func(network, address string) (net.Conn, error) { - c.log("Dialing kcp://%s", address) - return kcp.Dial(address) - } - } - - ctx, cancel := context.WithTimeout(context.Background(), dialGiveUpTime) - defer cancel() - - c.log("Dialing new remote: %v", newRemote) - newObfs4Conn, err := obfs4Dialer.DialContext(ctx, "tcp", newRemote) - - if err != nil { - _, ok := c.obfs4Failures[newRemote] - if ok { - c.obfs4Failures[newRemote] += 1 - } else { - c.obfs4Failures[newRemote] = 1 - } - c.error("Could not dial obfs4 remote: %v (failures: %d)", err, c.obfs4Failures[newRemote]) - } - - if newObfs4Conn == nil { - c.error("Did not get obfs4: %v ", err) - } else { - c.outLock.Lock() - c.obfs4Conns = append([]net.Conn{newObfs4Conn}, c.obfs4Conns...) - c.outLock.Unlock() - - c.newObfs4Conn <- newObfs4Conn - c.log("Dialed new remote") - - // If we wait sleepSeconds here to clean up the previous connection, we can guarantee that the - // connection list will not grow unbounded - go func() { - time.Sleep(time.Duration(sleepSeconds) * time.Second) - - c.cleanupOldConn() - }() - } - } -} - -func (c *HopClient) cleanupOldConn() { - c.outLock.Lock() - defer c.outLock.Unlock() - - if len(c.obfs4Conns) > 1 { - c.log("Connections: %v", len(c.obfs4Conns)) - connToClose := c.obfs4Conns[len(c.obfs4Conns)-1] - if connToClose != nil { - c.log("Cleaning up old connection to %v", connToClose.RemoteAddr()) - - err := connToClose.Close() - if err != nil { - c.log("Error closing obfs4 connection to %v: %v", connToClose.RemoteAddr(), err) - } - } - - // Remove the connection from our tracking list - c.obfs4Conns = c.obfs4Conns[:len(c.obfs4Conns)-1] - } -} - -func (c *HopClient) readUDPWriteTCP() { - datagramBuffer := make([]byte, obfsvpn.MaxUDPLen) - for { - select { - case <-c.ctx.Done(): - return - default: - } - - tcpBuffer, newOpenvpnAddr, err := obfsvpn.ReadUDPFrameTCP(c.openvpnConn, datagramBuffer) - if err != nil { - c.error("Read err from %v: %v", c.openvpnConn.LocalAddr(), err) - continue - } - - if newOpenvpnAddr != c.openvpnAddr { - c.openvpnAddrLock.Lock() - c.openvpnAddr = newOpenvpnAddr - c.openvpnAddrLock.Unlock() - } - - func() { - // Always write to the first connection in our list because it will be most up to date - func() { - conn, err := c.getUsableConnection() - if err != nil { - c.log("Cannot get connection: %s", err) - return - } - _, err = conn.Write(tcpBuffer) - if err != nil { - c.log("Write err from %v to %v: %v", conn.LocalAddr(), conn.RemoteAddr(), err) - return - } - }() - }() - } -} - -func (c *HopClient) getUsableConnection() (net.Conn, error) { - c.outLock.Lock() - defer c.outLock.Unlock() - - if len(c.obfs4Conns) == 0 { - return nil, errors.New("no usable connection") - } else { - return c.obfs4Conns[0], nil - } -} - -func (c *HopClient) readTCPWriteUDP() { - for { - select { - case <-c.ctx.Done(): - return - default: - } - - fromTCP := make(chan []byte, 2048) - - handleObfs4Conn := func(conn net.Conn) { - datagramBuffer := make([]byte, obfsvpn.MaxUDPLen) - lengthBuffer := make([]byte, 2) - for { - udpBuffer, err := obfsvpn.ReadTCPFrameUDP(conn, datagramBuffer, lengthBuffer) - if err != nil { - c.error("Reading/framing error: %v", err) - return - } - - fromTCP <- udpBuffer - } - } - - go func() { - for { - newObfs4Conn := <-c.newObfs4Conn - - go handleObfs4Conn(newObfs4Conn) - } - }() - - go handleObfs4Conn(c.obfs4Conns[0]) - - for { - tcpBytes := <-fromTCP - - c.openvpnAddrLock.RLock() - _, err := c.openvpnConn.WriteToUDP(tcpBytes, c.openvpnAddr) - c.openvpnAddrLock.RUnlock() - if err != nil { - c.error("Write err from %v to %v: %v", c.openvpnConn.LocalAddr(), c.openvpnConn.RemoteAddr(), err) - return - } - } - } -} - -func (c *HopClient) Stop() (bool, error) { - if !c.IsStarted() { - return false, ErrNotRunning - } - - c.stop() - - c.state = stopped - - return true, nil -} - -func (c *HopClient) log(format string, a ...interface{}) { - if c.EventLogger != nil { - c.EventLogger.Log(string(c.state), fmt.Sprintf(format, a...)) - return - } - if format == "" { - log.Println(a...) - return - } - log.Printf(format+"\n", a...) -} - -func (c *HopClient) error(format string, a ...interface{}) { - if c.EventLogger != nil { - c.EventLogger.Error(fmt.Sprintf(format, a...)) - return - } - if format == "" { - log.Println(a...) - return - } - log.Printf(format+"\n", a...) -} - -func (c *HopClient) IsStarted() bool { - return c.state != stopped -} diff --git a/vendor/0xacab.org/leap/obfsvpn/client/interfaces.go b/vendor/0xacab.org/leap/obfsvpn/client/interfaces.go deleted file mode 100644 index 06174b36..00000000 --- a/vendor/0xacab.org/leap/obfsvpn/client/interfaces.go +++ /dev/null @@ -1,7 +0,0 @@ -package client - -type ObfsClient interface { - Start() (bool, error) - Stop() (bool, error) - IsStarted() bool -} diff --git a/vendor/0xacab.org/leap/obfsvpn/obfsvpn/kcp.go b/vendor/0xacab.org/leap/obfsvpn/obfsvpn/kcp.go new file mode 100644 index 00000000..05c92b7b --- /dev/null +++ b/vendor/0xacab.org/leap/obfsvpn/obfsvpn/kcp.go @@ -0,0 +1,56 @@ +package obfsvpn + +import ( + "net" + + "github.com/xtaci/kcp-go/v5" +) + +const ( + DefaultKCPSendWindowSize int = 65535 + DefaultKCPReceiveWindowSize int = 65535 + DefaultKCPReadBuffer int = 16 * 1024 * 1024 + DefaultKCPWriteBuffer int = 16 * 1024 * 1024 +) + +type KCPConfig struct { + Enabled bool + SendWindowSize int + ReceiveWindowSize int + ReadBuffer int + WriteBuffer int +} + +func DefaultKCPConfig() *KCPConfig { + return &KCPConfig{ + Enabled: true, + SendWindowSize: DefaultKCPSendWindowSize, + ReceiveWindowSize: DefaultKCPReceiveWindowSize, + ReadBuffer: DefaultKCPReadBuffer, + WriteBuffer: DefaultKCPWriteBuffer, + } +} + +func GetKCPDialer(kcpConfig KCPConfig, logger func(format string, a ...interface{})) func(network, address string) (net.Conn, error) { + return func(network, address string) (net.Conn, error) { + if logger != nil { + logger("Dialing kcp://%s", address) + } + kcpSession, err := kcp.DialWithOptions(address, nil, 10, 3) + if err != nil { + return nil, err + } + kcpSession.SetStreamMode(true) + kcpSession.SetWindowSize(kcpConfig.SendWindowSize, kcpConfig.ReceiveWindowSize) + if err := kcpSession.SetReadBuffer(kcpConfig.ReadBuffer); err != nil { + return nil, err + } + if err := kcpSession.SetWriteBuffer(kcpConfig.WriteBuffer); err != nil { + return nil, err + } + // https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration + kcpSession.SetNoDelay(1, 10, 2, 1) + return kcpSession, nil + } + +} diff --git a/vendor/0xacab.org/leap/obfsvpn/obfsvpn/listener.go b/vendor/0xacab.org/leap/obfsvpn/obfsvpn/listener.go index 2826cb49..2733430d 100644 --- a/vendor/0xacab.org/leap/obfsvpn/obfsvpn/listener.go +++ b/vendor/0xacab.org/leap/obfsvpn/obfsvpn/listener.go @@ -8,22 +8,19 @@ import ( "log" "net" - "github.com/xtaci/kcp-go" + "github.com/xtaci/kcp-go/v5" "gitlab.com/yawning/obfs4.git/common/ntor" "gitlab.com/yawning/obfs4.git/transports/base" "gitlab.com/yawning/obfs4.git/transports/obfs4" pt "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib" ) -const ( - netKCP = "kcp" -) - // ListenConfig contains options for listening to an address. // If Seed is not set it defaults to a randomized value. // If StateDir is not set the current working directory is used. type ListenConfig struct { ListenConfig net.ListenConfig + KCPConfig KCPConfig NodeID *ntor.NodeID PrivateKey *ntor.PrivateKey @@ -34,7 +31,7 @@ type ListenConfig struct { // NewListenConfig returns a ListenConfig and any error during the initialization. // perhaps this is redundant, but using the same json format than ss for debug. -func NewListenConfig(nodeIDStr, privKeyStr, pubKeyStr, seedStr, stateDir string) (*ListenConfig, error) { +func NewListenConfig(nodeIDStr, privKeyStr, pubKeyStr, seedStr, stateDir string, kcpConfig KCPConfig) (*ListenConfig, error) { var err error var seed [ntor.KeySeedLength]byte var nodeID *ntor.NodeID @@ -62,6 +59,7 @@ func NewListenConfig(nodeIDStr, privKeyStr, pubKeyStr, seedStr, stateDir string) PublicKey: pubKeyStr, Seed: seed, StateDir: stateDir, + KCPConfig: kcpConfig, } return lc, nil } @@ -119,21 +117,26 @@ func NewServerState(stateDir string) error { // Listen listens on the local network address. // See func net.Dial for a description of the network and address parameters. -func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (*Listener, error) { - var ln net.Listener - var err error - switch network { - case netKCP: +func (lc *ListenConfig) Listen(ctx context.Context, kcpConfig KCPConfig, address string) (*Listener, error) { + if kcpConfig.Enabled { log.Println("kcp listen on", address) - ln, err = kcp.Listen(address) + ln, err := kcp.ListenWithOptions(address, nil, 10, 3) if err != nil { return nil, err } - default: - ln, err = lc.ListenConfig.Listen(ctx, network, address) - if err != nil { + + if err := ln.SetReadBuffer(kcpConfig.ReadBuffer); err != nil { + return nil, err + } + if err := ln.SetWriteBuffer(kcpConfig.WriteBuffer); err != nil { return nil, err } + + return lc.Wrap(ctx, ln) + } + ln, err := lc.ListenConfig.Listen(ctx, "tcp", address) + if err != nil { + return nil, err } return lc.Wrap(ctx, ln) } @@ -141,8 +144,9 @@ func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (*L // Listener is a network listener that accepts obfuscated connections and // performs the ntor handshake on them. type Listener struct { - sf base.ServerFactory - ln net.Listener + sf base.ServerFactory + ln net.Listener + kcpConfig KCPConfig } // Accept waits for and returns the next connection to the listener. @@ -151,6 +155,13 @@ func (l *Listener) Accept() (net.Conn, error) { if err != nil { return nil, err } + kcpSession, ok := conn.(*kcp.UDPSession) + if ok { + kcpSession.SetStreamMode(true) + kcpSession.SetWindowSize(l.kcpConfig.SendWindowSize, l.kcpConfig.ReceiveWindowSize) + // https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration + kcpSession.SetNoDelay(1, 10, 2, 1) + } conn, err = l.sf.WrapConn(conn) return conn, err } diff --git a/vendor/github.com/kalikaneko/socks5/.gitignore b/vendor/github.com/kalikaneko/socks5/.gitignore deleted file mode 100644 index acc70b4d..00000000 --- a/vendor/github.com/kalikaneko/socks5/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -main/ -.vscode/ -.idea/ \ No newline at end of file diff --git a/vendor/github.com/kalikaneko/socks5/LICENSE b/vendor/github.com/kalikaneko/socks5/LICENSE deleted file mode 100644 index 0e797b44..00000000 --- a/vendor/github.com/kalikaneko/socks5/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Copyright (c) 2021 haochen233 -Copyright (c) 2022 kali kaneko - -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. diff --git a/vendor/github.com/kalikaneko/socks5/README.md b/vendor/github.com/kalikaneko/socks5/README.md deleted file mode 100644 index dde63b86..00000000 --- a/vendor/github.com/kalikaneko/socks5/README.md +++ /dev/null @@ -1,316 +0,0 @@ -# socks5 - -This is a Golang implementation of the Socks5 protocol library. -To see in this [SOCKS Protocol Version 5](https://www.rfc-editor.org/rfc/rfc1928.html). -This library is also compatible with Socks4 and Socks4a. - -# Contents - -- [Features](#Features) -- [Install](#Installation) -- [Examples](#Examples) - - [Server example](#Server-example) - - [simple (no authentication)](#simple-no-authentication) - - [username/password authentication in memory](#username/password-authentication-in-memory) - - [custom transporter to transmit data between client and remote](#custom-transporter-to-transmit-data-between-client-and-remote) - - [Client example](#Client) - - [CONNECT usage](#CONNECT-usage) - - [UDP_ASSOCIATE usage](#UDP_ASSOCIATE-usage) - - [BIND usage](#BIND-usage) -- [FAQ](#FAQ) - -# Features - -- socks5: - - command: **CONNECT**, **UDP ASSOCIATE**, **BIND**. - - auth methods: - - **Username/Password** authentication. - - No Authentication Required. -- socks4: - - command: **CONNECT**, **BIND**. - - auth: (no support). -- sock4a: same as socks4. -- Custom client and server authenticator. -- Easy to read source code. -- Similar to the Golang standard library experience. - -# Installation - -``` sh -$ go get "github.com/haochen233/socks5"` -``` - -# Examples - -## Server example - -### simple (no authentication): - -```go -package main - -import ( - "log" - "github.com/haochen233/socks5" -) - -func main() { - // create socks server. - srv := &socks5.Server{ - // socks server listen address. - Addr: "127.0.0.1:1080", - // UDP assocaite and bind command listen ip. - // Don't need port, the port will automatically chosen. - BindIP: "127.0.0.1", - // if nil server will provide no authentication required method. - Authenticators: nil, - } - - // start listen - err := srv.ListenAndServe() - if err != nil { - log.Fatal(err) - } -} - - -``` - -### username/password authentication in memory: - -```go -package main - -import ( - "crypto/md5" - "log" - - "github.com/haochen233/socks5" -) - -func main() { - // create a username/password store in memory. - var userStorage socks5.UserPwdStore = socks5.NewMemeryStore(md5.New(), "secret") - // set a pair of username/password. - userStorage.Set("admin", "123456") - - srv := &socks5.Server{ - Addr: "127.0.0.1:1080", - BindIP: "127.0.0.1", - // enable username/password method and authenticator. - Authenticators: map[socks5.METHOD]socks5.Authenticator{ - socks5.USERNAME_PASSWORD: socks5.UserPwdAuth{UserPwdStore: userStorage}, - // There is already an authentication method. - // If want enable no authentication required method. - // you should enable it explicit. - socks5.NO_AUTHENTICATION_REQUIRED: socks5.NoAuth{}, - }, - } - - err := srv.ListenAndServe() - if err != nil { - log.Fatal(err) - } -} -``` - -### custom transporter to transmit data between client and remote. - -```go -package main - -import ( - "log" - "net" - - "github.com/haochen233/socks5" -) - -// simulate to impl socks5.Transporter interface. -// transport encrypted data. -type cryptTransport struct { -} - -func (c *cryptTransport) TransportTCP(client *net.TCPConn, remote *net.TCPConn) <-chan error { - //encrypt data and send to remote - //decrypt data and send to client - return nil -} - -func (c *cryptTransport) TransportUDP(server *socks5.UDPConn, request *socks5.Request) error { - panic("implement me") - return nil -} - -func main() { - server := &socks5.Server{ - Addr: "127.0.0.1:1080", - BindIP: "127.0.0.1", - // replace default Transporter interface - Transporter: &cryptTransport{}, - } - - err := server.ListenAndServe() - if err != nil { - log.Fatal(err) - } -} -``` - -## Client example - -### CONNECT usage: - -```go -package main - -import ( - "log" - - "github.com/haochen233/socks5" -) - -func main() { - // create socks client - clnt := socks5.Client{ - ProxyAddr: "127.0.0.1:1080", - // Authenticator supported by the client. - // It must not be nil. - Auth: map[socks5.METHOD]socks5.Authenticator{ - // If client want send NO_AUTHENTICATION_REQUIRED method to server, must - // add socks5.NoAuth authenticator explicitly - socks5.NO_AUTHENTICATION_REQUIRED: &socks5.NoAuth{}, - }, - } - - // client send CONNECT command and get a tcp connection. - // and use this connection transit data between you and www.google.com:80. - conn, err := clnt.Connect(socks5.Version5, "www.baidu.com:80") - if err != nil { - log.Fatal(err) - } - - // close connection. - conn.Close() -} - -``` - -### UDP_ASSOCIATE usage: - -```go -package main - -import ( - "fmt" - "log" - - "github.com/haochen233/socks5" -) - -func main() { - clnt := socks5.Client{ - ProxyAddr: "127.0.0.1:1080", - // client provide USERNAME_PASSWORD method and - // NO_AUTHENTICATION_REQUIRED. - Auth: map[socks5.METHOD]socks5.Authenticator{ - socks5.NO_AUTHENTICATION_REQUIRED: &socks5.NoAuth{}, - socks5.USERNAME_PASSWORD: &socks5.UserPasswd{Username: "admin", Password: "123456"}, - }, - } - - // client send UDP_ASSOCIATE command and get a udp connection. - // Empty local addr string a local address (127.0.0.1:port) is automatically chosen. - // you can specific a address to tell socks server which client address will - // send udp data. Such as clnt.UDPForward("127.0.0.1:9999"). - conn, err := clnt.UDPForward("") - if err != nil { - log.Fatal(err) - } - defer conn.Close() - - // send every datagram should add UDP request header. - someData := []byte("some data") - // dest addr where are you send to. - destAddr, _ := socks5.ParseAddress("127.0.0.1:9190") - // packing socks5 UDP data with dest addr. - pakcedData, err := socks5.PackUDPData(destAddr, someData) - // final send you data - conn.Write(pakcedData) - - // on the contrary. - // you should unpacked the packet, after received every packedData. - buf := make([]byte, 65507) - conn.Read(buf) - - // unpacking data. - destAddr, unpackedData, err := socks5.UnpackUDPData(buf) - // operate your udp data. - fmt.Println(unpackedData) -} -``` - -### BIND usage: - -```go -package main - -import ( - "encoding/binary" - "github.com/haochen233/socks5" - "log" -) - -func main() { - c := socks5.Client{ - ProxyAddr: "172.16.1.28:1080", - Auth: map[socks5.METHOD]socks5.Authenticator{ - socks5.USERNAME_PASSWORD: &socks5.UserPasswd{"admin", "123456"}, - socks5.NO_AUTHENTICATION_REQUIRED: &socks5.NoAuth{}, - }, - } - - // connect - conn1, err := c.Connect(5, "127.0.0.1:9000") - if err != nil { - log.Fatal(err) - } - - dest := "127.0.0.1:9001" - // bind - bindAddr, errors, conn2, err := c.Bind(4, dest) - if err != nil { - log.Fatal(err) - } - - // An example tell dest about socks server bind address - // via CONNECT proxy connection. - port := make([]byte, 2) - binary.BigEndian.PutUint16(port, bindAddr.Port) - conn1.Write(append(bindAddr.Addr, port...)) - - // wait the second reply. if nil the dest already - // established with socks server. - err = <-errors - if err != nil { - log.Fatal(err) - return - } - - // bind success - _, err = conn2.Write([]byte("hello")) - if err != nil { - return - log.Fatal(err) - } -} -``` - -# FAQ: -- Server default enable socks4. How to disable socks4 support? - when you initialize a socks5 server, you should spefic this flag to disable explicitly. - ```go - server := &socks5.Server{ - DisableSocks4: true, - } - ``` \ No newline at end of file diff --git a/vendor/github.com/kalikaneko/socks5/address.go b/vendor/github.com/kalikaneko/socks5/address.go deleted file mode 100644 index 37f8f334..00000000 --- a/vendor/github.com/kalikaneko/socks5/address.go +++ /dev/null @@ -1,263 +0,0 @@ -package socks5 - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "io" - "net" - "strconv" - "sync" -) - -// Address represents address in socks protocol. -type Address struct { - Addr net.IP - ATYPE - Port uint16 -} - -var bufPool = sync.Pool{New: func() interface{} { - buf := bytes.Buffer{} - return &buf -}} - -// Address return address -// Examples: -// 127.0.0.1:80 -// example.com:443 -// [fe80::1%lo0]:80 -func (a *Address) String() string { - if a.ATYPE == DOMAINNAME { - return net.JoinHostPort(string(a.Addr), strconv.Itoa(int(a.Port))) - } - return net.JoinHostPort(a.Addr.String(), strconv.Itoa(int(a.Port))) -} - -var errDomainMaxLengthLimit = errors.New("domain name out of max length") - -// Bytes return bytes slice of Address by ver param. -// If ver is socks4, the returned socks4 address format as follows: -// +----+----+----+----+----+----+....+----+....+----+ -// | DSTPORT | DSTIP | USERID |NULL| -// +----+----+----+----+----+----+----+----+....+----+ -// If ver is socks4 and address type is domain name, -// the returned socks4 address format as follows: -// +----+----+----+----+----+----+....+----+....+----+....+----+....+----+ -// | DSTPORT | DSTIP | USERID |NULL| HOSTNAME |NULL| -// +----+----+----+----+----+----+----+----+....+----+----+----+....+----+ -// If ver is socks5 -// the returned socks5 address format as follows: -// +------+----------+----------+ -// | ATYP | DST.ADDR | DST.PORT | -// +------+----------+----------+ -// | 1 | Variable | 2 | -// +------+----------+----------+ -// Socks4 call this method return bytes end with NULL, socks4 client use normally, -// Socks4 server should trim terminative NULL. -// Socks4 server should not call this method if server address type is DOMAINNAME -func (a *Address) Bytes(ver VER) ([]byte, error) { - buf := bufPool.Get().(*bytes.Buffer) - defer buf.Reset() - defer bufPool.Put(buf) - - // port - port := make([]byte, 2) - binary.BigEndian.PutUint16(port, a.Port) - - switch ver { - case Version4: - // socks4a - buf.Write(port) - if a.ATYPE == DOMAINNAME { - buf.Write(net.IPv4(0, 0, 0, 1).To4()) - // NULL - buf.WriteByte(NULL) - // hostname - buf.Write(a.Addr) - } else if a.ATYPE == IPV4_ADDRESS { - buf.Write(a.Addr) - } else { - return nil, fmt.Errorf("socks4 unsupported address type: %#x", a.ATYPE) - } - buf.WriteByte(NULL) - case Version5: - // address type - buf.WriteByte(a.ATYPE) - // domain name address type - if a.ATYPE == DOMAINNAME { - if len(a.Addr) > 255 { - return nil, errDomainMaxLengthLimit - } - buf.WriteByte(byte(len(a.Addr))) - buf.Write(a.Addr) - } else if a.ATYPE == IPV4_ADDRESS { - buf.Write(a.Addr.To4()) - } else if a.ATYPE == IPV6_ADDRESS { - buf.Write(a.Addr.To16()) - } - buf.Write(port) - } - - return buf.Bytes(), nil -} - -// readAddress read address info from follows: -// socks5 server's reply. -// socks5 client's request. -// socks5 server's udp reply header. -// socks5 client's udp request header. -// -// socks4 client's request. -// socks4a client's request -// exclude: socks4a server's reply, socks4 server's reply. Please use readSocks4ReplyAddress. -func readAddress(r io.Reader, ver VER) (*Address, REP, error) { - addr := &Address{} - - switch ver { - case Version4: - // DST.PORT - port, err := ReadNBytes(r, 2) - if err != nil { - return nil, Rejected, &OpError{Version5, "read", nil, "client dest port", err} - } - addr.Port = binary.BigEndian.Uint16(port) - // DST.IP - ip, err := ReadNBytes(r, 4) - if err != nil { - return nil, Rejected, &OpError{Version4, "read", nil, "\"process request dest ip\"", err} - } - addr.ATYPE = IPV4_ADDRESS - - //Discard later bytes until read EOF - //Please see socks4 request format at(http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol) - _, err = ReadUntilNULL(r) - if err != nil { - return nil, Rejected, &OpError{Version4, "read", nil, "\"process request useless header \"", err} - } - - //Socks4a extension - // +----+----+----+----+----+----+----+----+----+----++----++-----+-----++----+ - // | VN | CD | DSTPORT | DSTIP | USERID |NULL| HOSTNAME |NULL| - // +----+----+----+----+----+----+----+----+----+----++----++-----+-----++----+ - // 1 1 2 4 variable 1 variable 1 - //The client sets the first three bytes of DSTIP to NULL and - //the last byte to non-zero. The corresponding IP address is - //0.0.0.x, where x is non-zero - if ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && - ip[3] != 0 { - ip, err = ReadUntilNULL(r) - if err != nil { - return nil, Rejected, &OpError{Version4, "read", nil, "\"process socks4a extension request\"", err} - } - addr.ATYPE = DOMAINNAME - } - addr.Addr = ip - return addr, Granted, nil - case Version5: - // ATYP - aType, err := ReadNBytes(r, 1) - if err != nil { - return nil, GENERAL_SOCKS_SERVER_FAILURE, &OpError{Version5, "read", nil, "dest address type", err} - } - addr.ATYPE = aType[0] - - var addrLen int - switch addr.ATYPE { - case IPV4_ADDRESS: - addrLen = 4 - case IPV6_ADDRESS: - addrLen = 16 - case DOMAINNAME: - fqdnLength, err := ReadNBytes(r, 1) - if err != nil { - return nil, GENERAL_SOCKS_SERVER_FAILURE, &OpError{Version5, "read", nil, "\"dest domain name length\"", err} - } - addrLen = int(fqdnLength[0]) - default: - return nil, ADDRESS_TYPE_NOT_SUPPORTED, &OpError{Version5, "", nil, "\"dest address\"", &AtypeError{aType[0]}} - } - - // DST.ADDR - ip, err := ReadNBytes(r, addrLen) - if err != nil { - return nil, GENERAL_SOCKS_SERVER_FAILURE, err - } - addr.Addr = ip - - // DST.PORT - port, err := ReadNBytes(r, 2) - if err != nil { - return nil, GENERAL_SOCKS_SERVER_FAILURE, &OpError{Version5, "read", nil, "client dest port", err} - } - addr.Port = binary.BigEndian.Uint16(port) - return addr, SUCCESSED, nil - default: - return nil, UNASSIGNED, &VersionError{ver} - } -} - -// readSocks4ReplyAddress read socks4 reply address. Why don't use readAddress, -// because socks4 reply not end with NULL, they're not compatible -func readSocks4ReplyAddress(r io.Reader, ver VER) (*Address, REP, error) { - addr := &Address{} - - // DST.PORT - port, err := ReadNBytes(r, 2) - if err != nil { - return nil, Rejected, &OpError{Version5, "read", nil, "client dest port", err} - } - addr.Port = binary.BigEndian.Uint16(port) - // DST.IP - ip, err := ReadNBytes(r, 4) - if err != nil { - return nil, Rejected, &OpError{Version4, "read", nil, "\"process request dest ip\"", err} - } - addr.Addr = ip - addr.ATYPE = IPV4_ADDRESS - - return addr, Granted, nil -} - -// UDPAddr return UDP Address. -func (a *Address) UDPAddr() (*net.UDPAddr, error) { - return net.ResolveUDPAddr("udp", a.String()) -} - -// TCPAddr return TCP Address. -func (a *Address) TCPAddr() (*net.TCPAddr, error) { - return net.ResolveTCPAddr("tcp", a.String()) -} - -// ParseAddress parse address to *Address -// Input Examples: -// 127.0.0.1:80 -// example.com:443 -// [fe80::1%lo0]:80 -func ParseAddress(addr string) (*Address, error) { - Address := new(Address) - - host, port, err := net.SplitHostPort(addr) - if err != nil { - return nil, err - } - - ip := net.ParseIP(host) - if ip == nil { - Address.ATYPE = DOMAINNAME - Address.Addr = []byte(host) - } else if ip.To4() != nil { - Address.ATYPE = IPV4_ADDRESS - Address.Addr = ip.To4() - } else if ip.To16() != nil { - Address.ATYPE = IPV6_ADDRESS - Address.Addr = ip.To16() - } - atoi, err := strconv.Atoi(port) - if err != nil { - return nil, err - } - Address.Port = uint16(atoi) - return Address, nil -} diff --git a/vendor/github.com/kalikaneko/socks5/auth.go b/vendor/github.com/kalikaneko/socks5/auth.go deleted file mode 100644 index d8f0e98b..00000000 --- a/vendor/github.com/kalikaneko/socks5/auth.go +++ /dev/null @@ -1,165 +0,0 @@ -package socks5 - -import ( - "bytes" - "fmt" - "hash" - "io" - "sync" -) - -// Authenticator provides socks5's authentication sub negotiation. -type Authenticator interface { - Authenticate(in io.Reader, out io.Writer) error -} - -// NoAuth NO_AUTHENTICATION_REQUIRED implementation. -type NoAuth struct { -} - -// Authenticate NO_AUTHENTICATION_REQUIRED Authentication for socks5 Server and Client. -func (n NoAuth) Authenticate(in io.Reader, out io.Writer) error { - return nil -} - -// UserPwdAuth provides socks5 Server Username/Password Authenticator. -type UserPwdAuth struct { - UserPwdStore -} - -// Authenticate provide socks5 Server Username/Password authentication. -func (u UserPwdAuth) Authenticate(in io.Reader, out io.Writer) error { - uname, passwd, err := u.ReadUserPwd(in) - if err != nil { - return err - } - - err = u.Validate(string(uname), string(passwd)) - if err != nil { - reply := []byte{1, 1} - _, err1 := out.Write(reply) - if err1 != nil { - return err - } - return err - } - - //authentication successful,then send reply to client - reply := []byte{1, 0} - _, err = out.Write(reply) - if err != nil { - return err - } - - return nil -} - -// ReadUserPwd read Username/Password request from client -// return username and password. -// Username/Password request format is as follows: -// +----+------+----------+------+----------+ -// |VER | ULEN | UNAME | PLEN | PASSWD | -// +----+------+----------+------+----------+ -// | 1 | 1 | 1 to 255 | 1 | 1 to 255 | -// +----+------+----------+------+----------+ -// For standard details, please see (https://www.rfc-editor.org/rfc/rfc1929.html) -func (u UserPwdAuth) ReadUserPwd(in io.Reader) ([]byte, []byte, error) { - - ulen, err := ReadNBytes(in, 2) - if err != nil { - return nil, nil, err - } - - uname, err := ReadNBytes(in, int(ulen[1])) - if err != nil { - return nil, nil, err - } - - plen, err := ReadNBytes(in, 1) - if err != nil { - return nil, nil, err - } - - passwd := make([]byte, plen[0]) - passwd, err = ReadNBytes(in, int(plen[0])) - if err != nil { - return nil, nil, err - } - - return uname, passwd, nil -} - -// UserPwdStore provide username and password storage. -type UserPwdStore interface { - Set(username string, password string) error - Del(username string) error - Validate(username string, password string) error -} - -// MemoryStore store username&password in memory. -// the password is encrypt with hash method. -type MemoryStore struct { - Users map[string][]byte - mu sync.Mutex - hash.Hash - algoSecret string -} - -// NewMemeryStore return a new MemoryStore -func NewMemeryStore(algo hash.Hash, secret string) *MemoryStore { - return &MemoryStore{ - Users: make(map[string][]byte), - Hash: algo, - algoSecret: secret, - } -} - -// Set the mapping of username and password. -func (m *MemoryStore) Set(username string, password string) error { - m.mu.Lock() - defer m.mu.Unlock() - build := bytes.NewBuffer(nil) - build.WriteString(password + m.algoSecret) - cryptPasswd := m.Hash.Sum(build.Bytes()) - m.Users[username] = cryptPasswd - return nil -} - -// UserNotExist the error type used in UserPwdStore.Del() method and -// UserPwdStore.Validate method. -type UserNotExist struct { - username string -} - -func (u UserNotExist) Error() string { - return fmt.Sprintf("user %s don't exist", u.username) -} - -// Del delete by username -func (m *MemoryStore) Del(username string) error { - m.mu.Lock() - defer m.mu.Unlock() - if _, ok := m.Users[username]; !ok { - return UserNotExist{username: username} - } - - delete(m.Users, username) - return nil -} - -// Validate validate username and password. -func (m *MemoryStore) Validate(username string, password string) error { - m.mu.Lock() - defer m.mu.Unlock() - if _, ok := m.Users[username]; !ok { - return UserNotExist{username: username} - } - - build := bytes.NewBuffer(nil) - build.WriteString(password + m.algoSecret) - cryptPasswd := m.Hash.Sum(build.Bytes()) - if !bytes.Equal(cryptPasswd, m.Users[username]) { - return fmt.Errorf("user %s has bad password", username) - } - return nil -} diff --git a/vendor/github.com/kalikaneko/socks5/client.go b/vendor/github.com/kalikaneko/socks5/client.go deleted file mode 100644 index a2f652b1..00000000 --- a/vendor/github.com/kalikaneko/socks5/client.go +++ /dev/null @@ -1,542 +0,0 @@ -package socks5 - -import ( - "errors" - "fmt" - "io" - "log" - "net" - "strconv" - "time" -) - -// Client defines parameters for running socks client. -type Client struct { - // ProxyAddr in the form "host:port". It not be empty. - ProxyAddr string - - // Timeout specifies a time limit for requests made by this - // Client. The timeout includes connection time, reading the response body. - // - // A Timeout of zero means no timeout. - // - // The Client cancels requests to the underlying Transport - // as if the Request's Context ended. - Timeout time.Duration - - // method mapping to the authenticator - Auth map[METHOD]Authenticator - - // ErrorLog specifics an options logger for errors accepting - // If nil, logging is done via log package's standard logger. - ErrorLog *log.Logger - - // DisableSocks4A client disable socks4a client, default enable socks4a extension. - DisableSocks4A bool -} - -// UserPasswd provide socks5 Client Username/Password Authenticator. -type UserPasswd struct { - Username string - Password string -} - -// Authenticate socks5 Client Username/Password Authentication. -func (c *UserPasswd) Authenticate(in io.Reader, out io.Writer) error { - //This begins with the client producing a Username/Password request: - // +----+------+----------+------+----------+ - // |VER | ULEN | UNAME | PLEN | PASSWD | - // +----+------+----------+------+----------+ - // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | - // +----+------+----------+------+----------+ - _, err := out.Write(append(append(append([]byte{0x01, byte(len(c.Username))}, []byte(c.Username)...), byte(len(c.Password))), []byte(c.Password)...)) - if err != nil { - return err - } - //Get reply, the following response: - - // +----+--------+ - // |VER | STATUS | - // +----+--------+ - // | 1 | 1 | - // +----+--------+ - tmp, err := ReadNBytes(in, 2) - if err != nil { - return err - } - if tmp[0] != 0x01 { - return errors.New("not support method") - } - if tmp[1] != SUCCESSED { - return errors.New("user authentication failed") - } - return nil -} - -// handshake socks TCP connect,get a tcp connect and reply addr -func (clt *Client) handshake(request *Request) (conn *net.TCPConn, replyAddr *Address, err error) { - // get Socks server Address - proxyTCPAddr, err := net.ResolveTCPAddr("tcp", clt.ProxyAddr) - if err != nil { - return nil, nil, err - } - - // dial to Socks server. - proxyTCPConn, err := net.DialTCP("tcp", nil, proxyTCPAddr) - if err != nil { - return nil, nil, err - } - if clt.Timeout != 0 { - err = proxyTCPConn.SetDeadline(time.Now().Add(clt.Timeout)) - if err != nil { - return nil, nil, err - } - defer proxyTCPConn.SetDeadline(time.Time{}) - } - - // process handshake by version - if request.VER == Version5 { - replyAddr, err = clt.handShake5(request, proxyTCPConn) - } else if request.VER == Version4 { - if request.ATYPE == DOMAINNAME && clt.DisableSocks4A { - return nil, nil, errors.New("socks4a client had been disabled") - } - replyAddr, err = clt.handshake4(request, proxyTCPConn) - } - - // handshake wrong. - if err != nil { - proxyTCPConn.Close() - return nil, nil, err - } - - return proxyTCPConn, replyAddr, nil -} - -// handShake5 Socks 5 version of the connection handshake -func (clt *Client) handShake5(request *Request, proxyTCPConn net.Conn) (*Address, error) { - err := clt.authentication(proxyTCPConn) - if err != nil { - return nil, err - } - destAddrByte, err := request.Address.Bytes(Version5) - if err != nil { - return nil, err - } - // The SOCKS request is formed as follows: - // +----+-----+-------+------+----------+----------+ - // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | - // +----+-----+-------+------+----------+----------+ - // | 1 | 1 | X'00' | 1 | Variable | 2 | - // +----+-----+-------+------+----------+----------+ - if _, err := proxyTCPConn.Write(append([]byte{request.VER, request.CMD, request.RSV}, destAddrByte...)); err != nil { - return nil, err - } - // reply formed as follows: - // +----+-----+-------+------+----------+----------+ - // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | - // +----+-----+-------+------+----------+----------+ - // | 1 | 1 | X'00' | 1 | Variable | 2 | - // +----+-----+-------+------+----------+----------+ - reply := &Reply{} - tmp, err := ReadNBytes(proxyTCPConn, 3) - if err != nil { - return nil, fmt.Errorf("failed to get reply version and command and reserved, %v", err) - } - reply.VER, reply.REP, reply.RSV = tmp[0], tmp[1], tmp[2] - if reply.VER != Version5 { - return nil, fmt.Errorf("unrecognized SOCKS version[%d]", reply.VER) - } - // read address - serverBoundAddr, _, err := readAddress(proxyTCPConn, request.VER) - if err != nil { - return nil, fmt.Errorf("failed to get reply address, %v", err) - } - reply.Address = serverBoundAddr - if reply.REP != SUCCESSED { - return nil, fmt.Errorf("server refuse client request, %s", rep2Str[reply.REP]) - } - return reply.Address, nil -} - -// authentication -func (clt *Client) authentication(proxyConn net.Conn) error { - var methods []byte - for method := range clt.Auth { - methods = append(methods, method) - } - // The client connects to the server, and sends a version identifier/method selection message: - // +----+----------+----------+ - // |VER | NMETHODS | METHODS | - // +----+----------+----------+ - // | 1 | 1 | 1 to 255 | - // +----+----------+----------+ - _, err := proxyConn.Write(append([]byte{Version5, byte(len(methods))}, methods...)) - if err != nil { - return nil - } - //Get reply, a METHOD selection message: - // +----+--------+ - // |VER | METHOD | - // +----+--------+ - // | 1 | 1 | - // +----+--------+ - reply, err := ReadNBytes(proxyConn, 2) - if err != nil { - return err - } - if reply[0] != Version5 { - return &VersionError{reply[0]} - } - - // Is client has this method? - if _, ok := clt.Auth[reply[1]]; !ok { - return &MethodError{reply[1]} - } - - // process authentication sub negotiation - err = clt.Auth[reply[1]].Authenticate(proxyConn, proxyConn) - if err != nil { - return err - } - - return nil -} - -// handShake4 Socks 4 version of the connection handshake -func (clt *Client) handshake4(request *Request, proxyConn net.Conn) (*Address, error) { - destAddrByte, err := request.Address.Bytes(Version4) - if err != nil { - return nil, err - } - // The client connects to the SOCKS server and sends a CONNECT request when it wants to establish a connection to an application server. - // The client includes in the request packet the IP address and the port number of the destination host, and userid, in the following format. - // +----+----+----+----+----+----+----+----+----+----+....+----+ - // | VN | CD | DSTPORT | DSTIP | USERID |NULL| - // +----+----+----+----+----+----+----+----+----+----+....+----+ - // 1 1 2 4 variable 1 - if _, err := proxyConn.Write(append([]byte{request.VER, request.CMD}, destAddrByte...)); err != nil { - return nil, err - } - // A reply packet is sent to the client when this connection is established,or when the request is rejected or the operation fails. - // +----+----+----+----+----+----+----+----+ - // | VN | CD | DSTPORT | DSTIP | - // +----+----+----+----+----+----+----+----+ - // 1 1 2 4 - tmp, err := ReadNBytes(proxyConn, 2) - if err != nil { - return nil, fmt.Errorf("failed to get reply version and command, %v", err) - } - if tmp[0] != 0 { - return nil, fmt.Errorf("response VN wrong[%d]", tmp[0]) - } - if tmp[1] != Granted { - return nil, errors.New("server refuse client request") - } - // Read address - replyAddr, _, err := readSocks4ReplyAddress(proxyConn, request.VER) - if err != nil { - return nil, fmt.Errorf("failed to get reply address, %v", err) - } - return replyAddr, nil -} - -// Connect send CONNECT Request. Returned a connected proxy connection. -func (clt *Client) Connect(ver VER, dest string) (*net.TCPConn, error) { - if ver != Version4 && ver != Version5 { - return nil, &VersionError{ver} - } - - destAddr, err := ParseAddress(dest) - if err != nil { - return nil, err - } - req := &Request{ - VER: ver, - CMD: CONNECT, - RSV: 0, - Address: destAddr, - } - - conn, _, err := clt.handshake(req) - if err != nil { - return nil, err - } - return conn, nil -} - -// UDPForward send UDP_ASSOCIATE Request. -// The laddr Param specific Client address to send udp datagram. -// If laddr is empty string, a local address (127.0.0.1:port) is automatically chosen. -// If port is occupied will return error. -func (clt *Client) UDPForward(laddr string) (*UDPConn, error) { - if laddr == "" { - laddr = "127.0.0.1:0" - } - - // split laddr to host/port - host, portStr, err := net.SplitHostPort(laddr) - p, err := strconv.Atoi(portStr) - if err != nil { - return nil, err - } - port := uint16(p) - - // zero port, will automatically chosen. - if port == 0 { - err, port = GetRandomPort("udp") - if err != nil { - return nil, errors.New("automatically chosen port fail") - } - laddr = net.JoinHostPort(host, strconv.Itoa(int(port))) - } - - // get addr - addr, err := ParseAddress(laddr) - if err != nil { - return nil, err - } - - req := &Request{ - VER: Version5, - CMD: UDP_ASSOCIATE, - RSV: 0, - Address: addr, - } - - // Handshake base on TCP connection - proxyTCPConn, UDPRelayAddr, err := clt.handshake(req) - if err != nil { - return nil, err - } - - // Get local UDP addr - localUDPAddr, err := addr.UDPAddr() - if err != nil { - return nil, err - } - - // Get udp relay server bind addr. - serverListenUDPAddr, err := UDPRelayAddr.UDPAddr() - if err != nil { - return nil, err - } - - // Dial UDP relay Server - err = IsFreePort("udp", port) - if err != nil { - proxyTCPConn.Close() - return nil, fmt.Errorf("port %d is occupied", port) - } - proxyUDPConn, err := net.DialUDP("udp", localUDPAddr, serverListenUDPAddr) - if err != nil { - proxyTCPConn.Close() - return nil, err - } - return NewUDPConn(proxyUDPConn, proxyTCPConn), nil -} - -// Bind send BIND Request. return 4 params: -// 1. Server bind address. -// 2. a readable chan to recv second reply from socks server. -// 3. A connection that is not immediately available, until read a nil from err chan. -// 4. An error, indicate the first reply result. If nil, successes. -func (clt *Client) Bind(ver VER, destAddr string) (*Address, <-chan error, net.Conn, error) { - dest, err := ParseAddress(destAddr) - if err != nil { - return nil, nil, nil, err - } - - request := &Request{ - Address: dest, - CMD: BIND, - VER: ver, - } - proxyConn, err := net.Dial("tcp", clt.ProxyAddr) - if err != nil { - clt.logf()(err.Error()) - return nil, nil, nil, err - } - if clt.Timeout != 0 { - err = proxyConn.SetDeadline(time.Now().Add(clt.Timeout)) - if err != nil { - clt.logf()(err.Error()) - return nil, nil, nil, err - } - defer proxyConn.SetDeadline(time.Time{}) - } - switch request.VER { - case Version4: - serverBindAddr, secondReply, err := clt.bind4(request, proxyConn) - if err != nil { - proxyConn.Close() - clt.logf()(err.Error()) - return nil, nil, nil, err - } - return serverBindAddr, secondReply, proxyConn, nil - case Version5: - serverBindAddr, secondReply, err := clt.bind5(request, proxyConn) - if err != nil { - proxyConn.Close() - clt.logf()(err.Error()) - return nil, nil, nil, err - } - return serverBindAddr, secondReply, proxyConn, nil - default: - proxyConn.Close() - return nil, nil, nil, &VersionError{request.VER} - } -} - -// bind5 socks5 bind -func (clt *Client) bind5(request *Request, proxyBindConn net.Conn) (*Address, <-chan error, error) { - err := clt.authentication(proxyBindConn) - if err != nil { - return nil, nil, err - } - destAddrByte, err := request.Address.Bytes(Version5) - if err != nil { - return nil, nil, err - } - // The SOCKS request is formed as follows: - // +----+-----+-------+------+----------+----------+ - // // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | - // // +----+-----+-------+------+----------+----------+ - // // | 1 | 1 | X'00' | 1 | Variable | 2 | - // +----+-----+-------+------+----------+----------+ - if _, err := proxyBindConn.Write(append([]byte{request.VER, request.CMD, request.RSV}, destAddrByte...)); err != nil { - return nil, nil, err - } - // reply formed as follows: - // +----+-----+-------+------+----------+----------+ - // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | - // +----+-----+-------+------+----------+----------+ - // | 1 | 1 | X'00' | 1 | Variable | 2 | - // +----+-----+-------+------+----------+----------+ - reply := &Reply{} - tmp, err := ReadNBytes(proxyBindConn, 3) - if err != nil { - return nil, nil, fmt.Errorf("failed to get reply version and command and reserved, %v", err) - } - reply.VER, reply.REP, reply.RSV = tmp[0], tmp[1], tmp[2] - if reply.VER != Version5 { - return nil, nil, fmt.Errorf("unrecognized SOCKS version[%d]", reply.VER) - } - // read address - serverBoundAddr, _, err := readAddress(proxyBindConn, request.VER) - if err != nil { - return nil, nil, fmt.Errorf("failed to get reply address, %v", err) - } - reply.Address = serverBoundAddr - if reply.REP != SUCCESSED { - return nil, nil, fmt.Errorf("server refuse client request, %s,when first time reply", rep2Str[reply.REP]) - } - errorChan := make(chan error) - go func() { - reply2 := &Reply{} - // The second time reply formed as follows: - // +----+-----+-------+------+----------+----------+ - // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | - // +----+-----+-------+------+----------+----------+ - // | 1 | 1 | X'00' | 1 | Variable | 2 | - // +----+-----+-------+------+----------+----------+ - tmp, err := ReadNBytes(proxyBindConn, 3) - if err != nil { - errorChan <- fmt.Errorf("failed to get reply version and command and reserved, %v", err) - proxyBindConn.Close() - } - reply2.VER, reply2.REP, reply2.RSV = tmp[0], tmp[1], tmp[2] - if reply2.VER != Version5 { - errorChan <- fmt.Errorf("unrecognized SOCKS version[%d]", reply.VER) - proxyBindConn.Close() - } - // read address - serverBoundAddr, _, err := readAddress(proxyBindConn, request.VER) - if err != nil { - errorChan <- fmt.Errorf("failed to get reply address, %v", err) - proxyBindConn.Close() - } - reply2.Address = serverBoundAddr - if reply2.REP != SUCCESSED { - errorChan <- errors.New("server refuse client request,when second time reply") - proxyBindConn.Close() - } - errorChan <- nil - }() - return serverBoundAddr, errorChan, nil -} - -// bind4 socks4 bind -func (clt *Client) bind4(request *Request, proxyBindConn net.Conn) (*Address, <-chan error, error) { - destAddrByte, err := request.Address.Bytes(Version4) - if err != nil { - return nil, nil, err - } - // The client connects to the SOCKS server and sends a CONNECT request when it wants to establish a connection to an application server. - // The client includes in the request packet the IP address and the port number of the destination host, and userid, in the following format. - // +----+----+----+----+----+----+----+----+----+----+....+----+ - // | VN | CD | DSTPORT | DSTIP | USERID |NULL| - // +----+----+----+----+----+----+----+----+----+----+....+----+ - // 1 1 2 4 variable 1 - if _, err := proxyBindConn.Write(append([]byte{request.VER, request.CMD}, destAddrByte...)); err != nil { - return nil, nil, err - } - // A reply packet is sent to the client when this connection is established,or when the request is rejected or the operation fails. - // +----+----+----+----+----+----+----+----+ - // | VN | CD | DSTPORT | DSTIP | - // +----+----+----+----+----+----+----+----+ - // 1 1 2 4 - tmp, err := ReadNBytes(proxyBindConn, 2) - if err != nil { - return nil, nil, fmt.Errorf("failed to get reply version and command, %v", err) - } - if tmp[0] != 0 { - return nil, nil, fmt.Errorf("response VN wrong[%d]", tmp[0]) - } - // Read address - serverBoundAddr, _, err := readSocks4ReplyAddress(proxyBindConn, request.VER) - if err != nil { - return nil, nil, fmt.Errorf("failed to get reply address, %v", err) - } - if tmp[1] != Granted { - return nil, nil, errors.New("server refuse client request,when first time reply") - } - errorChan := make(chan error) - go func() { - // A reply packet is sent to the client,or when the request is rejected or the operation fails. - // +----+----+----+----+----+----+----+----+ - // | VN | CD | DSTPORT | DSTIP | - // +----+----+----+----+----+----+----+----+ - // 1 1 2 4 - tmp, err := ReadNBytes(proxyBindConn, 2) - if err != nil { - errorChan <- fmt.Errorf("failed to get reply version and command, %v", err) - proxyBindConn.Close() - } - if tmp[0] != 0 { - errorChan <- fmt.Errorf("response VN wrong[%d]", tmp[0]) - proxyBindConn.Close() - } - // read address - _, _, err = readSocks4ReplyAddress(proxyBindConn, request.VER) - if err != nil { - errorChan <- fmt.Errorf("failed to get reply address, %v", err) - proxyBindConn.Close() - } - - if tmp[1] != Granted { - errorChan <- errors.New("server refuse client request,when second time reply") - proxyBindConn.Close() - } - errorChan <- nil - }() - return serverBoundAddr, errorChan, nil -} - -// logf Logging is done using the client's errorlog -func (clt *Client) logf() func(format string, args ...interface{}) { - if clt.ErrorLog == nil { - return log.Printf - } - return clt.ErrorLog.Printf -} diff --git a/vendor/github.com/kalikaneko/socks5/common.go b/vendor/github.com/kalikaneko/socks5/common.go deleted file mode 100644 index ea64b2f5..00000000 --- a/vendor/github.com/kalikaneko/socks5/common.go +++ /dev/null @@ -1,47 +0,0 @@ -package socks5 - -import ( - "bytes" - "errors" - "io" -) - -var errUnexpectMinusLength = errors.New("arg number should not be minus") - -// ReadNBytes wrap io.ReadFull. read n bytes. -// The error is EOF only if no bytes were read. -// If an EOF happens after reading some but not all the bytes, -// ReadFull returns ErrUnexpectedEOF. -func ReadNBytes(reader io.Reader, n int) ([]byte, error) { - if n < 0 { - return nil, errUnexpectMinusLength - } - data := make([]byte, n) - _, err := io.ReadFull(reader, data) - if err != nil { - return nil, err - } - - return data, nil -} - -// ReadUntilNULL Read all not Null byte. -// Until read first Null byte(all zero bits) -func ReadUntilNULL(reader io.Reader) ([]byte, error) { - data := &bytes.Buffer{} - b := make([]byte, 1) - for { - _, err := reader.Read(b) - if err != nil { - if err == io.EOF { - return nil, nil - } - return nil, err - } - - if b[0] == NULL { - return data.Bytes(), nil - } - data.WriteByte(b[0]) - } -} diff --git a/vendor/github.com/kalikaneko/socks5/conn.go b/vendor/github.com/kalikaneko/socks5/conn.go deleted file mode 100644 index a94aee21..00000000 --- a/vendor/github.com/kalikaneko/socks5/conn.go +++ /dev/null @@ -1,116 +0,0 @@ -package socks5 - -import ( - "io" - "net" - "sync" - "time" -) - -// UDPConn be associated with TCP connections. -// The UDP connection will close immediately, When TCP connection closed, -// UDPConn only use in UDP_ASSOCIATE command. -type UDPConn struct { - mu sync.Mutex - udp *net.UDPConn - tcp *net.TCPConn - closeChan chan struct{} -} - -// NewUDPConn get a *UDPConn through provide a tcp and udp connection. -// the tcp connection is used for socks UDP_ASSOCIATE handshake. -// the udp connection is used for socks udp forwarding. -// -// After UDP_ASSOCIATE handshake, the tcp transit nothing. Its only -// function is udp relay connection still running. -// -// If one of them shuts off, it will close them all. -func NewUDPConn(udp *net.UDPConn, tcp *net.TCPConn) *UDPConn { - if udp == nil || tcp == nil { - return nil - } - - u := &UDPConn{ - udp: udp, - tcp: tcp, - closeChan: make(chan struct{}), - } - - go func() { - // guard tcp connection, if it closed should close tcp relay too. - io.Copy(io.Discard, tcp) - u.Close() - }() - - return u -} - -func (u *UDPConn) LocalAddr() net.Addr { - return u.udp.LocalAddr() -} - -func (u *UDPConn) RemoteAddr() net.Addr { - return u.udp.RemoteAddr() -} - -func (u *UDPConn) SetDeadline(t time.Time) error { - return u.udp.SetDeadline(t) -} - -func (u *UDPConn) SetReadDeadline(t time.Time) error { - return u.udp.SetReadDeadline(t) -} - -func (u *UDPConn) SetWriteDeadline(t time.Time) error { - return u.udp.SetWriteDeadline(t) -} - -func (u *UDPConn) Read(b []byte) (n int, err error) { - return u.udp.Read(b) -} - -func (u *UDPConn) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) { - return u.udp.WriteToUDP(b, addr) -} - -func (u *UDPConn) ReadFromUDP(b []byte) (int, *net.UDPAddr, error) { - return u.udp.ReadFromUDP(b) -} - -func (u *UDPConn) Write(b []byte) (n int, err error) { - return u.udp.Write(b) -} - -func (u *UDPConn) Close() error { - var err error - u.mu.Lock() - defer u.mu.Unlock() - - ch := u.getCloseChanLocked() - select { - case <-ch: - return nil - default: - if err2 := u.udp.Close(); err2 != nil { - err = err2 - } - if err2 := u.tcp.Close(); err2 != nil { - err = err2 - } - close(u.closeChan) - } - return err -} - -func (u *UDPConn) CloseCh() <-chan struct{} { - u.mu.Lock() - defer u.mu.Unlock() - return u.getCloseChanLocked() -} - -func (u *UDPConn) getCloseChanLocked() <-chan struct{} { - if u.closeChan == nil { - u.closeChan = make(chan struct{}) - } - return u.closeChan -} diff --git a/vendor/github.com/kalikaneko/socks5/port.go b/vendor/github.com/kalikaneko/socks5/port.go deleted file mode 100644 index 5e1a8524..00000000 --- a/vendor/github.com/kalikaneko/socks5/port.go +++ /dev/null @@ -1,101 +0,0 @@ -package socks5 - -import ( - "errors" - "net" - "strconv" - "strings" -) - -// GetRandomPort return a random port by specific network. -// The network must be "tcp", "udp". -func GetRandomPort(network string) (err error, port uint16) { - network = strings.ToLower(network) - addr := "0.0.0.0:0" - - switch network { - case "tcp", "tcp4", "tcp6": - tcpAddr, err := net.ResolveTCPAddr(network, addr) - if err != nil { - return err, 0 - } - ln, err := net.ListenTCP(network, tcpAddr) - if err != nil { - return err, 0 - } - - _, portStr, err := net.SplitHostPort(ln.Addr().String()) - p, err := strconv.Atoi(portStr) - port = uint16(p) - - err = ln.Close() - if err != nil { - return err, 0 - } - return err, port - case "udp", "udp4", "udp6": - udpAddr, err := net.ResolveUDPAddr(network, addr) - if err != nil { - return err, 0 - } - ln, err := net.ListenUDP(network, udpAddr) - if err != nil { - return err, 0 - } - - _, portStr, err := net.SplitHostPort(ln.LocalAddr().String()) - p, err := strconv.Atoi(portStr) - port = uint16(p) - - err = ln.Close() - if err != nil { - return err, 0 - } - return err, port - default: - return errors.New("unknown network type " + network), 0 - } -} - -// IsFreePort indicates the port is available. -// The network must be "tcp", "udp". -func IsFreePort(network string, port uint16) error { - network = strings.ToLower(network) - portStr := strconv.Itoa(int(port)) - addr := "0.0.0.0:" + portStr - - switch network { - case "tcp": - tcpAddr, err := net.ResolveTCPAddr(network, addr) - if err != nil { - return err - } - ln, err := net.ListenTCP(network, tcpAddr) - if err != nil { - return err - } - - ln.Close() - if err != nil { - return err - } - return nil - case "udp": - udpAddr, err := net.ResolveUDPAddr(network, addr) - if err != nil { - return err - } - ln, err := net.ListenUDP(network, udpAddr) - if err != nil { - return err - } - - ln.Close() - if err != nil { - return err - } - return nil - default: - return errors.New("unknown network type " + network) - } -} diff --git a/vendor/github.com/kalikaneko/socks5/protocol.go b/vendor/github.com/kalikaneko/socks5/protocol.go deleted file mode 100644 index d0194eaf..00000000 --- a/vendor/github.com/kalikaneko/socks5/protocol.go +++ /dev/null @@ -1,155 +0,0 @@ -package socks5 - -import "fmt" - -type VersionError struct { - VER -} - -func (v *VersionError) Error() string { - return fmt.Sprintf("error socks protocol version: %d", v.VER) -} - -// VER indicates protocol version -type VER = uint8 - -const ( - Version4 = 0x04 - Version5 = 0x05 -) - -type MethodError struct { - METHOD -} - -func (m *MethodError) Error() string { - if _, ok := method2Str[m.METHOD]; ok { - return fmt.Sprintf("don't support this method %s", method2Str[m.METHOD]) - } else { - return fmt.Sprintf("unknown mehotd %#x", m.METHOD) - } -} - -// METHOD Defined authentication methods -type METHOD = uint8 - -const ( - NO_AUTHENTICATION_REQUIRED METHOD = 0x00 - GSSAPI METHOD = 0x01 - USERNAME_PASSWORD METHOD = 0x02 - IANA_ASSIGNED METHOD = 0x03 - NO_ACCEPTABLE_METHODS METHOD = 0xff -) - -var method2Str = map[METHOD]string{ - NO_AUTHENTICATION_REQUIRED: "NO_AUTHENTICATION_REQUIRED", - GSSAPI: "GSSAPI", - USERNAME_PASSWORD: "USERNAME_PASSWORD", - IANA_ASSIGNED: "IANA_ASSIGNED", - NO_ACCEPTABLE_METHODS: "NO_ACCEPTABLE_METHODS", -} - -// CMDError cmd error type -type CMDError struct { - CMD -} - -func (c *CMDError) Error() string { - if _, ok := cmd2Str[c.CMD]; !ok { - return fmt.Sprintf("unknown command:%#x", c.CMD) - } - return fmt.Sprintf("don't support this command:%s", cmd2Str[c.CMD]) -} - -// CMD is one of a field in Socks5 Request -type CMD = uint8 - -const ( - CONNECT CMD = 0x01 - BIND CMD = 0x02 - UDP_ASSOCIATE CMD = 0x03 -) - -var cmd2Str = map[CMD]string{ - CONNECT: "CONNECT", - BIND: "BIND", - UDP_ASSOCIATE: "UDP_ASSOCIATE", - Rejected: "Rejected", - Granted: "Granted", -} - -type REPError struct { - REP -} - -func (r *REPError) Error() string { - if _, ok := cmd2Str[r.REP]; !ok { - return fmt.Sprintf("unknown rep:%#x", r.REP) - } - return fmt.Sprintf("don't support this rep:%s", rep2Str[r.REP]) -} - -// REP is one of a filed in Socks5 Reply -type REP = uint8 - -//socks5 reply -const ( - SUCCESSED REP = 0x00 - GENERAL_SOCKS_SERVER_FAILURE REP = 0x01 - CONNECTION_NOT_ALLOW_BY_RULESET REP = 0x02 - NETWORK_UNREACHABLE REP = 0x03 - HOST_UNREACHABLE REP = 0x04 - CONNECTION_REFUSED REP = 0x05 - TTL_EXPIRED REP = 0x06 - COMMAND_NOT_SUPPORTED REP = 0x07 - ADDRESS_TYPE_NOT_SUPPORTED REP = 0x08 - UNASSIGNED REP = 0x09 -) - -var rep2Str = map[REP]string{ - SUCCESSED: "successes", - GENERAL_SOCKS_SERVER_FAILURE: "general_socks_server_failure", - CONNECTION_NOT_ALLOW_BY_RULESET: "connection_not_allow_by_ruleset", - NETWORK_UNREACHABLE: "network_unreachable", - HOST_UNREACHABLE: "host_unreachable", - CONNECTION_REFUSED: "connection_refused", - TTL_EXPIRED: "ttl_expired", - COMMAND_NOT_SUPPORTED: "command_not_supported", - ADDRESS_TYPE_NOT_SUPPORTED: "address_type_not_supported", - UNASSIGNED: "unassigned", - Granted: "granted", - Rejected: "rejected", -} - -//socks4 reply -const ( - // Granted means server allow client request - Granted = 90 - // Rejected means server refuse client request - Rejected = 91 -) - -type AtypeError struct { - ATYPE -} - -func (a *AtypeError) Error() string { - return fmt.Sprintf("unknown address type:%#x", a.ATYPE) -} - -// ATYPE indicates address type in Request and Reply struct -type ATYPE = uint8 - -const ( - IPV4_ADDRESS ATYPE = 0x01 - DOMAINNAME ATYPE = 0x03 - IPV6_ADDRESS ATYPE = 0x04 -) - -var atype2Str = map[ATYPE]string{ - IPV4_ADDRESS: "IPV4_ADDRESS", - DOMAINNAME: "DOMAINNAME", - IPV6_ADDRESS: "IPV6_ADDRESS", -} - -const NULL byte = 0 diff --git a/vendor/github.com/kalikaneko/socks5/reply.go b/vendor/github.com/kalikaneko/socks5/reply.go deleted file mode 100644 index 4cf3b043..00000000 --- a/vendor/github.com/kalikaneko/socks5/reply.go +++ /dev/null @@ -1,14 +0,0 @@ -package socks5 - -// Reply a reply formed as follows: -// +----+-----+-------+------+----------+----------+ -// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | -// +----+-----+-------+------+----------+----------+ -// | 1 | 1 | X'00' | 1 | Variable | 2 | -// +----+-----+-------+------+----------+----------+ -type Reply struct { - VER - REP - RSV uint8 - *Address -} diff --git a/vendor/github.com/kalikaneko/socks5/request.go b/vendor/github.com/kalikaneko/socks5/request.go deleted file mode 100644 index ecfea039..00000000 --- a/vendor/github.com/kalikaneko/socks5/request.go +++ /dev/null @@ -1,70 +0,0 @@ -package socks5 - -import ( - "bytes" - "errors" -) - -// Request The SOCKS request is formed as follows: -// +----+-----+-------+------+----------+----------+ -// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | -// +----+-----+-------+------+----------+----------+ -// | 1 | 1 | X'00' | 1 | Variable | 2 | -// +----+-----+-------+------+----------+----------+ -type Request struct { - VER - CMD - RSV uint8 - *Address -} - -// UDPHeader Each UDP datagram carries a UDP request -// header with it: -// +----+------+------+----------+----------+----------+ -// |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | -// +----+------+------+----------+----------+----------+ -// | 2 | 1 | 1 | Variable | 2 | Variable | -// +----+------+------+----------+----------+----------+ -type UDPHeader struct { - RSV uint16 - FRAG uint8 - *Address - Data []byte -} - -var errEmptyPayload = errors.New("empty payload") - -// PackUDPData add UDP request header before payload. -func PackUDPData(addr *Address, payload []byte) ([]byte, error) { - if len(payload) == 0 { - return nil, errEmptyPayload - } - if addr == nil { - return nil, errors.New("addr is nil") - } - // RSV, FRAG - data := []byte{0x00, 0x00, 0x00} - dest, err := addr.Bytes(Version5) - if err != nil { - return nil, err - } - // ATYP, DEST.IP, DEST.PORT - data = append(data, dest...) - // DATA - data = append(data, payload...) - return data, nil -} - -// UnpackUDPData split UDP header and payload. -func UnpackUDPData(data []byte) (addr *Address, payload []byte, err error) { - // trim RSV, FRAG - data = data[3:] - buf := bytes.NewBuffer(data) - addr, _, err = readAddress(buf, Version5) - if err != nil { - return nil, nil, err - } - - payload = buf.Bytes() - return -} diff --git a/vendor/github.com/kalikaneko/socks5/server.go b/vendor/github.com/kalikaneko/socks5/server.go deleted file mode 100644 index 33059e2a..00000000 --- a/vendor/github.com/kalikaneko/socks5/server.go +++ /dev/null @@ -1,653 +0,0 @@ -package socks5 - -import ( - "errors" - "io" - "log" - "net" - "strconv" - "sync" - "sync/atomic" - "time" -) - -// Server defines parameters for running socks server. -// The zero value for Server is a valid configuration(tcp listen on :1080). -type Server struct { - // Addr optionally specifies the TCP address for the server to listen on, - // in the form "host:port". If empty, ":1080" (port 1080) is used. - Addr string - - // BindIP specific UDP relay server IP and bind listen IP. - // It shouldn't be ipv4zero like "0,0,0,0" or ipv6zero like [:] - // If empty, localhost used. - BindIP string - - // ReadTimeout is the maximum duration for reading from socks client. - // it's only effective to socks server handshake process. - // - // If zero, there is no timeout. - ReadTimeout time.Duration - - // WriteTimeout is the maximum duration for writing to socks client. - // it's only effective to socks server handshake process. - // - // If zero, there is no timeout. - WriteTimeout time.Duration - - // method mapping to the authenticator - // if nil server provide NO_AUTHENTICATION_REQUIRED method by default - Authenticators map[METHOD]Authenticator - - // The server select method to use policy - //MethodSelector - - Dial DialFunc - - // Server transmit data between client and dest server. - // if nil, DefaultTransport is used. - Transporter - - // ErrorLog specifics an options logger for errors accepting - // connections, unexpected socks protocol handshake process, - // and server to remote connection errors. - // If nil, logging is done via log package's standard logger. - ErrorLog *log.Logger - - // DisableSocks4, disable socks4 server, default enable socks4 compatible. - DisableSocks4 bool - - // 1 indicate server is shutting down. - // 0 indicate server is running. - // Atomic operation must be guaranteed. - isShutdown int32 - - mu sync.Mutex - listeners map[*net.Listener]struct{} - activeConn map[net.Conn]struct{} - doneCh chan struct{} -} - -type DialFunc func(string, string) (net.Conn, error) - -func (srv *Server) getDoneChan() <-chan struct{} { - srv.mu.Lock() - defer srv.mu.Unlock() - return srv.getDoneChanLocked() -} - -func (srv *Server) getDoneChanLocked() chan struct{} { - if srv.doneCh == nil { - srv.doneCh = make(chan struct{}) - } - return srv.doneCh -} - -func (srv *Server) closeDoneChanLocked() { - ch := srv.getDoneChanLocked() - select { - case <-ch: - default: - close(srv.doneCh) - } -} - -func (srv *Server) Close() error { - atomic.StoreInt32(&srv.isShutdown, 1) - srv.mu.Lock() - defer srv.mu.Unlock() - // close all listeners. - err := srv.closeListenerLocked() - if err != nil { - return err - } - // close doneCh to broadcast close message. - srv.closeDoneChanLocked() - // Close all open connections. - for conn, _ := range srv.activeConn { - conn.Close() - } - return nil -} - -func (srv *Server) inShuttingDown() bool { - return atomic.LoadInt32(&srv.isShutdown) != 0 -} - -func (srv *Server) closeListenerLocked() error { - var err error - for ln := range srv.listeners { - if cerr := (*ln).Close(); cerr != nil { - err = cerr - } - } - return err -} - -// trackListener adds or removes a net.Listener to the set of tracked -// listeners. -// -// We store a pointer to interface in the map set, in case the -// net.Listener is not comparable. This is safe because we only call -// trackListener via Serve and can track+defer untrack the same -// pointer to local variable there. We never need to compare a -// Listener from another caller. -// -// It reports whether the server is still up (not Shutdown or Closed). -func (srv *Server) trackListener(l *net.Listener, add bool) bool { - srv.mu.Lock() - defer srv.mu.Unlock() - if srv.listeners == nil { - srv.listeners = make(map[*net.Listener]struct{}) - } - - if add { - if srv.inShuttingDown() { - return false - } - srv.listeners[l] = struct{}{} - } else { - delete(srv.listeners, l) - } - return true -} - -func (srv *Server) trackConn(c net.Conn, add bool) { - srv.mu.Lock() - defer srv.mu.Unlock() - if srv.activeConn == nil { - srv.activeConn = make(map[net.Conn]struct{}) - } - if add { - srv.activeConn[c] = struct{}{} - } else { - delete(srv.activeConn, c) - } -} - -// ListenAndServe listens on the TCP network address srv.Addr and then -// calls serve to handle requests on incoming connections. -// -// If srv.Addr is blank, ":1080" is used. -func (srv *Server) ListenAndServe() error { - if srv.inShuttingDown() { - return ErrServerClosed - } - - addr := srv.Addr - if addr == "" { - addr = "0.0.0.0:1080" - } - - if srv.BindIP == "" { - srv.BindIP = "localhost" - } else if srv.BindIP == net.IPv4zero.String() || srv.BindIP == net.IPv6zero.String() { - return errors.New("socks: server bindIP shouldn't be zero") - } - - ln, err := net.Listen("tcp", addr) - if err != nil { - return err - } - return srv.Serve(ln) -} - -// ErrServerClosed is returned by the Server's Serve, ListenAndServe methods after a call to Shutdown or Close. -var ErrServerClosed = errors.New("socks: Server closed") - -// Serve accepts incoming connections on the Listener l, creating a -// new service goroutine for each. The service goroutine select client -// list methods and reply client. Then process authentication and reply -// to them. At then end of handshake, read socks request from client and -// establish a connection to the target. -func (srv *Server) Serve(l net.Listener) error { - srv.trackListener(&l, true) - defer srv.trackListener(&l, false) - - var tempDelay time.Duration - - for { - client, err := l.Accept() - if err != nil { - select { - case <-srv.getDoneChan(): - return ErrServerClosed - default: - } - if ne, ok := err.(net.Error); ok && ne.Temporary() { - if tempDelay == 0 { - tempDelay = 5 * time.Millisecond - } else { - tempDelay *= 2 - } - if max := time.Second; tempDelay > max { - tempDelay = max - } - srv.logf()("socks: Accept error: %v, retrying in %v", err, tempDelay) - time.Sleep(tempDelay) - continue - } - return err - } - go srv.serveconn(client) - } -} - -func (srv *Server) serveconn(client net.Conn) { - if srv.ReadTimeout != 0 { - client.SetReadDeadline(time.Now().Add(srv.ReadTimeout)) - } - if srv.WriteTimeout != 0 { - client.SetWriteDeadline(time.Now().Add(srv.WriteTimeout)) - } - - // handshake - request, err := srv.handShake(client) - if err != nil { - srv.logf()(err.Error()) - client.Close() - return - } - - // establish connection to remote - remote, err := srv.establish(client, request) - if err != nil { - srv.logf()(err.Error()) - client.Close() - return - } - - // establish over, reset deadline. - client.SetReadDeadline(time.Time{}) - client.SetWriteDeadline(time.Time{}) - - // transport data - switch request.CMD { - case CONNECT, BIND: - srv.trackConn(client, true) - defer srv.trackConn(client, false) - srv.trackConn(remote, true) - defer srv.trackConn(remote, false) - - errCh := srv.transport().TransportTCP(client, remote) - for err := range errCh { - if err != nil { - srv.logf()(err.Error()) - } - } - case UDP_ASSOCIATE: - relay := NewUDPConn(remote.(*net.UDPConn), client.(*net.TCPConn)) - srv.trackConn(relay, true) - defer srv.trackConn(relay, false) - - err = srv.transport().TransportUDP(relay, request) - if err != nil { - srv.logf()(err.Error()) - } - } -} - -func (srv *Server) transport() Transporter { - if srv.Transporter == nil { - return DefaultTransporter - } - return srv.Transporter -} - -func (srv *Server) dialFn() DialFunc { - if srv.Dial == nil { - return net.Dial - } - return srv.Dial -} - -var errDisableSocks4 = errors.New("socks4 server has been disabled") - -// handShake socks protocol handshake process -func (srv *Server) handShake(client net.Conn) (*Request, error) { - //validate socks version message - _, err := checkVersion(client) - - if err != nil { - return nil, &OpError{Version5, "read", client.RemoteAddr(), "\"check version\"", err} - } - - //socks5 protocol authentication - err = srv.authentication(client) - if err != nil { - return nil, err - } - - //handle socks5 request - return srv.readSocks5Request(client) -} - -// authentication socks5 authentication process -func (srv *Server) authentication(client net.Conn) error { - //get nMethods - nMethods, err := ReadNBytes(client, 1) - if err != nil { - return err - } - - //Get methods - methods, err := ReadNBytes(client, int(nMethods[0])) - if err != nil { - return err - } - - return srv.MethodSelect(methods, client) -} - -// readSocks5Request read socks5 protocol client request. -func (srv *Server) readSocks5Request(client net.Conn) (*Request, error) { - reply := &Reply{ - VER: Version5, - Address: &Address{net.IPv4zero, IPV4_ADDRESS, 0}, - } - req := &Request{} - //VER, CMD, RSV - cmd, err := ReadNBytes(client, 3) - if err != nil { - return nil, &OpError{req.VER, "read", client.RemoteAddr(), "\"process request ver,cmd,rsv\"", err} - } - req.VER = cmd[0] - req.CMD = cmd[1] - req.RSV = cmd[2] - // ATYPE, DST.IP, DST.PORT - addr, rep, err := readAddress(client, req.VER) - if err != nil { - reply.REP = rep - err := srv.sendReply(client, reply) - if err != nil { - return nil, &OpError{req.VER, "write", client.RemoteAddr(), "\"process request address\"", err} - } - } - req.Address = addr - - return req, nil -} - -// IsAllowNoAuthRequired return true if server enable NO_AUTHENTICATION_REQUIRED. -// Or the server doesn't has no Authenticator return true. Otherwise return false. -func (srv *Server) IsAllowNoAuthRequired() bool { - if len(srv.Authenticators) == 0 { - return true - } - for method := range srv.Authenticators { - if method == NO_AUTHENTICATION_REQUIRED { - return true - } - } - return false -} - -// establish tcp connection to remote host if command is CONNECT or -// start listen on udp socket when command is UDP_ASSOCIATE. Listen -// and accept host connection when command is BIND. Finally, send -// corresponding reply to client. -func (srv *Server) establish(client net.Conn, req *Request) (dest net.Conn, err error) { - reply := &Reply{ - VER: req.VER, - Address: &Address{net.IPv4zero, IPV4_ADDRESS, 0}, - } - if req.VER != Version5 { - // unknown version - return nil, &VersionError{req.VER} - } - - switch req.CMD { - case CONNECT: - // dial dest host. - dest, err = srv.dialFn()("tcp", req.Address.String()) - if err != nil { - reply.REP = HOST_UNREACHABLE - err2 := srv.sendReply(client, reply) - if err2 != nil { - return nil, &OpError{req.VER, "write", client.RemoteAddr(), "\"process request\"", err2} - - } - return nil, err - } - - // parse remote host address. - remoteAddr, err := ParseAddress(dest.RemoteAddr().String()) - if err != nil { - reply.REP = GENERAL_SOCKS_SERVER_FAILURE - err2 := srv.sendReply(client, reply) - if err2 != nil { - return nil, &OpError{req.VER, "write", client.RemoteAddr(), "\"process request\"", err2} - } - return nil, err - } - reply.Address = remoteAddr - - // success - reply.REP = SUCCESSED - err = srv.sendReply(client, reply) - if err != nil { - return nil, &OpError{req.VER, "write", client.RemoteAddr(), "\"process request\"", err} - } - case UDP_ASSOCIATE: - addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(srv.BindIP, "0")) - if err != nil { - return nil, err - } - - // start udp listening on random port. - dest, err = net.ListenUDP("udp", addr) - if err != nil { - reply.REP = GENERAL_SOCKS_SERVER_FAILURE - err2 := srv.sendReply(client, reply) - if err2 != nil { - return nil, &OpError{req.VER, "write", client.RemoteAddr(), "\"process request\"", err2} - } - return nil, err - } - - // success - reply.REP = SUCCESSED - relayAddr, err := ParseAddress(dest.LocalAddr().String()) - if err != nil { - return nil, err - } - reply.Address = relayAddr - err = srv.sendReply(client, reply) - if err != nil { - return nil, &OpError{req.VER, "write", client.RemoteAddr(), "\"process request\"", err} - } - case BIND: - bindAddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(srv.BindIP, "0")) - if err != nil { - return nil, err - } - - // start tcp listening on random port. - bindServer, err := net.ListenTCP("tcp", bindAddr) - if err != nil { - reply.REP = GENERAL_SOCKS_SERVER_FAILURE - err2 := srv.sendReply(client, reply) - if err2 != nil { - return nil, &OpError{req.VER, "write", client.RemoteAddr(), "\"process request\"", err2} - } - return nil, err - } - defer bindServer.Close() - reply.REP = SUCCESSED - reply.Address, err = ParseAddress(bindServer.Addr().String()) - if err != nil { - return nil, err - } - - // send first reply to client. - err = srv.sendReply(client, reply) - if err != nil { - return nil, &OpError{req.VER, "write", client.RemoteAddr(), "\"process request\"", err} - } - dest, err = bindServer.Accept() - if err != nil { - reply.REP = GENERAL_SOCKS_SERVER_FAILURE - err2 := srv.sendReply(client, reply) - if err2 != nil { - return nil, &OpError{req.VER, "write", client.RemoteAddr(), "\"process request\"", err2} - } - return nil, err - } - - // send second reply to client. - if req.Address.String() == dest.RemoteAddr().String() { - reply.Address, err = ParseAddress(dest.RemoteAddr().String()) - if err != nil { - return nil, err - } - err = srv.sendReply(client, reply) - if err != nil { - return nil, &OpError{req.VER, "write", client.RemoteAddr(), "\"process request\"", err} - } - } else { - reply.REP = GENERAL_SOCKS_SERVER_FAILURE - err = srv.sendReply(client, reply) - if err != nil { - return nil, &OpError{req.VER, "write", client.RemoteAddr(), "\"process request\"", err} - } - } - default: - reply.REP = COMMAND_NOT_SUPPORTED - err = srv.sendReply(client, reply) - if err != nil { - return nil, &OpError{req.VER, "write", client.RemoteAddr(), "\"process request\"", err} - } - - return nil, &OpError{Version5, "", client.RemoteAddr(), "\"process request\"", &CMDError{req.CMD}} - } - return -} - -// sendReply The server send socks protocol reply to client -func (srv *Server) sendReply(out io.Writer, r *Reply) error { - var reply []byte - var err error - - if r.VER == Version4 { - if r.Address.ATYPE != IPV4_ADDRESS { - return errErrorATPE - } - addr, err := r.Address.Bytes(r.VER) - if err != nil { - return err - } - reply = append(reply, 0, r.REP) - // Remove NULL at the end. Please see Address.Bytes() Method. - reply = append(reply, addr[:len(addr)-1]...) - } else if r.VER == Version5 { - addr, err := r.Address.Bytes(r.VER) - if err != nil { - return err - } - reply = append(reply, r.VER, r.REP, r.RSV) - reply = append(reply, addr...) - } else { - return &VersionError{r.VER} - } - - _, err = out.Write(reply) - return err -} - -// MethodSelect select authentication method and reply to client. -func (srv *Server) MethodSelect(methods []CMD, client net.Conn) error { - // Select method to authenticate, then send selected method to client. - for _, method := range methods { - //Preferred to use NO_AUTHENTICATION_REQUIRED method - if method == NO_AUTHENTICATION_REQUIRED && srv.IsAllowNoAuthRequired() { - reply := []byte{Version5, NO_AUTHENTICATION_REQUIRED} - _, err := client.Write(reply) - if err != nil { - return &OpError{Version5, "write", client.RemoteAddr(), "authentication", err} - } - return nil - } - for m := range srv.Authenticators { - //Select the first matched method to authenticate - if m == method { - reply := []byte{Version5, method} - _, err := client.Write(reply) - if err != nil { - return &OpError{Version5, "write", client.RemoteAddr(), "authentication", err} - } - - err = srv.Authenticators[m].Authenticate(client, client) - if err != nil { - return &OpError{Version5, "", client.RemoteAddr(), "authentication", err} - } - return nil - } - } - } - - // There are no Methods can use - reply := []byte{Version5, NO_ACCEPTABLE_METHODS} - _, err := client.Write(reply) - if err != nil { - return &OpError{Version5, "write", client.RemoteAddr(), "authentication", err} - } - return &OpError{Version5, "", client.RemoteAddr(), "authentication", &MethodError{methods[0]}} -} - -func (srv *Server) logf() func(format string, args ...interface{}) { - if srv.ErrorLog == nil { - return log.Printf - } - return srv.ErrorLog.Printf -} - -// checkVersion check version is 4 or 5. -func checkVersion(in io.Reader) (VER, error) { - version, err := ReadNBytes(in, 1) - if err != nil { - return 0, err - } - - if (version[0] != Version5) && (version[0] != Version4) { - return 0, &VersionError{version[0]} - } - return version[0], nil -} - -// OpError is the error type usually returned by functions in the socks5 -// package. It describes the socks version, operation, client address, -// and address of an error. -type OpError struct { - // VER describe the socks server version on process. - VER - - // Op is the operation which caused the error, such as - // "read", "write". - Op string - - // Addr define client's address which caused the error. - Addr net.Addr - - // Step is the client's current connection stage, such as - // "check version", "authentication", "process request", - Step string - - // Err is the error that occurred during the operation. - // The Error method panics if the error is nil. - Err error -} - -func (o *OpError) Error() string { - str := "socks" + strconv.Itoa(int(o.VER)) - str += " " + o.Op - if o.Addr == nil { - str += " " - } else { - str += " " + o.Addr.String() - } - str += " " + o.Step - str += ":" + o.Err.Error() - return str -} - -var errErrorATPE = errors.New("socks4 server bind address type should be ipv4") diff --git a/vendor/github.com/kalikaneko/socks5/transport.go b/vendor/github.com/kalikaneko/socks5/transport.go deleted file mode 100644 index fc7a739c..00000000 --- a/vendor/github.com/kalikaneko/socks5/transport.go +++ /dev/null @@ -1,122 +0,0 @@ -package socks5 - -import ( - "io" - "net" - "strings" - "sync" -) - -// Transporter transmit data between client and dest server. -type Transporter interface { - TransportTCP(client net.Conn, remote net.Conn) <-chan error - TransportUDP(server *UDPConn, request *Request) error -} - -type transport struct { -} - -const maxLenOfDatagram = 65507 - -var transportPool = &sync.Pool{ - New: func() interface{} { - return make([]byte, maxLenOfDatagram) - }, -} - -// TransportTCP use io.CopyBuffer transmit data. -func (t *transport) TransportTCP(client net.Conn, remote net.Conn) <-chan error { - errCh := make(chan error) - var wg = sync.WaitGroup{} - - f := func(dst net.Conn, src net.Conn) { - defer wg.Done() - buf := transportPool.Get().([]byte) - defer transportPool.Put(buf) - _, err := io.CopyBuffer(dst, src, buf) - errCh <- err - } - - wg.Add(2) - go func() { - wg.Wait() - defer client.Close() - defer remote.Close() - close(errCh) - }() - - go f(remote, client) - go f(client, remote) - - return errCh -} - -// TransportUDP forwarding UDP packet between client and dest. -func (t *transport) TransportUDP(server *UDPConn, request *Request) error { - // Client udp address, limit access to the association. - clientAddr, err := request.Address.UDPAddr() - if err != nil { - return err - } - - // Record dest address, limit access to the association. - forwardAddr := make(map[string]struct{}) - buf := transportPool.Get().([]byte) - defer transportPool.Put(buf) - - defer server.Close() - for { - select { - default: - // Receive data from remote. - n, addr, err := server.ReadFromUDP(buf) - if err != nil { - return err - } - - // Should unpack data when data from client. - if strings.EqualFold(clientAddr.String(), addr.String()) { - destAddr, payload, err := UnpackUDPData(buf[:n]) - if err != nil { - return err - } - - destUDPAddr, err := destAddr.UDPAddr() - if err != nil { - return err - } - forwardAddr[destUDPAddr.String()] = struct{}{} - - // send payload to dest address - _, err = server.WriteToUDP(payload, destUDPAddr) - if err != nil { - return err - } - } - - // Should pack data when data from dest host - if _, ok := forwardAddr[addr.String()]; ok { - address, err := ParseAddress(addr.String()) - if err != nil { - return err - } - - // packed Data - packedData, err := PackUDPData(address, buf[:n]) - if err != nil { - return err - } - - // send payload to client - _, err = server.WriteToUDP(packedData, clientAddr) - if err != nil { - return err - } - } - case <-server.CloseCh(): - return nil - } - } -} - -var DefaultTransporter Transporter = &transport{} diff --git a/vendor/github.com/xtaci/kcp-go/v5/.travis.yml b/vendor/github.com/xtaci/kcp-go/v5/.travis.yml index 0e7c4ac1..6754ef67 100644 --- a/vendor/github.com/xtaci/kcp-go/v5/.travis.yml +++ b/vendor/github.com/xtaci/kcp-go/v5/.travis.yml @@ -1,4 +1,8 @@ +arch: + - amd64 + - ppc64le language: go + go: - 1.11.x - 1.12.x diff --git a/vendor/github.com/xtaci/kcp-go/v5/README.md b/vendor/github.com/xtaci/kcp-go/v5/README.md index d2273f89..f68406d9 100644 --- a/vendor/github.com/xtaci/kcp-go/v5/README.md +++ b/vendor/github.com/xtaci/kcp-go/v5/README.md @@ -277,9 +277,3 @@ A: Yes, for the safety of protocol, even if the upper layer has encrypted. 1. https://github.com/xtaci/libkcp -- FEC enhanced KCP session library for iOS/Android in C++ 1. https://github.com/skywind3000/kcp -- A Fast and Reliable ARQ Protocol 1. https://github.com/klauspost/reedsolomon -- Reed-Solomon Erasure Coding in Go - -## Consulting - -WeChat(付费技术咨询) - -<img src="wechat_donate.jpg" alt="kcptun" height="120px" /> diff --git a/vendor/github.com/xtaci/kcp-go/v5/donate.png b/vendor/github.com/xtaci/kcp-go/v5/donate.png deleted file mode 100644 index 0f353d96e902936aed15a7312a6e8b4b332fe960..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4420 zcmaJ_dpy&9`~QrjyVB^S(~Wo{Bt@vOsT-AJrJ3YZj@g`V$ziNiL?=p;GVwHrvN_Ii zo9>EOHDcy8Q)n@3j2WB5GtX1MU-#?vyq-V4uYJF-eO}k~`CQlae!s8lbMvIbF@@FJ zRs#SOP8_#94FE*?3IVHDN`KtK3;h64T64nk$eGJecq|NB>lmhalHdI?ER*(h=ryWl z)f?@dEhFzXKxM9V!qv>K%Nv!%Ttu|D#wO#JHa=L{p0wf><cPu%wVl^Csx(-yzTJx0 z_}uKkZ+Z&_d6hP_#N+x0u5BzbuZg5XZ|W;I&rBroXe>IF9TIiP?^4vI0sYa%(uDd7 zZycw>w4$QI+Z6>-EGWb4g4{zZfZ~1`kh}!~EN=mzZY|xX2EZmO0gE@QfZ7G=-(^iQ zUcs&`d`7>XdU4TVpqE>f4z>I6&Cxh)!c|rcp0{|kGS1C1V5V{9^UD6G1!Lbo5JMad z7AEV~dDwy{W_jVyear87L$1^tIx?QRw)A)2a)?uXj{?ITWBGZ*>l|jVTc7(HIA(0# z6{Tq?Ugz07xfx_<$b0=k<3$$-I~wqw8y$jT=R}1lByuE)K$E-hu)v0^R2x5L+k3Gu zd>e=^{=OlN9dT9@s}SfOf3ILHB;C#w<^54c&5;qNRQm}L4a8!W8G@;m1^bq_PYbfo zq(o)CFJt!Zt}IHAl{=PX8kSlYindy`vLp~)-ehPw37;0(QQJv-yC9)Dh>%Y<w{-I= z8&KiS32$d+EdplP`HrdYXw{S)TSipnI|WVO-D%q$$mht>?9Ayz<H)i#*!(~r`!|I$ zFKj$&6Rek#7-3h|W%Zlg=M;%=BsY-9<564zb_XI~u#@Z|>~@CSgYQK7RwL94Gz}Nx zvWFn<4}HYHt7liY*|y^DRlT|35i|ncIUyD=T}y{&J!!|4#OzViJb`Dp6+2u^yJ^rT zwR<Xk6!l^U?d$5?PDsW>q`jZ`eCiNr&w4*isnr-$Dj6wWpY7=+JeVPqJH9LBv_Nto zKKF6UR?KckMqcu`UazrZ2EpK58@mFys${>P#tgK|tKaj9>G!c~d=LhPi(GXo+znpn zn-JGwJx<^eS&HwRglE!6Q|(#_;c}$~0f|0I<AjrYv5?AlRP8jRGTN{I`~05}W6i@L z)JLa-IN82w-)gKGf@amXCv{vvNr5Qt+$BifeCPl=+YC`~-_Fy8WY?jK{p%{!M||si z7O;8rewt?ECO2-N-4Urgh`d9S9Zc&0E*NXVrL!tn71ytpwdXuLE`sqTlsTu|447LF zFXZ`lA;O-^s^OfgE1Fc>Uiw%k9CBogmj($m#_mWzUfM-#B7w<P^qSnAHZ*(nF9*Sc zhQh$>pz~O2L(Mp;VY}l01cR5Xg?0k>pTd0Mgq*TH*XfK$+Q5+Tp}(Y-H<|Kiao%dA zudDeXZ^_^3ssaErKQZ)i1(pkQVPRbRV0FAnw}=HvX-P;&f@%;W3jnV>Kaj^ogF^2q zKg{gw4IYv=Rv2sP>L6Sv2^wsAw^6j^9Z6g{&d=9=Dz*Ifhvoe;YKo+YNq2Up35#7- z$1(bI)8C-16V7TDvW``g=^>J--iMAjHuM*`clCe?^QgWVSi?nJbzwu^f;+v+#GEoZ z*+4|Lwbw4-4{rw`<tG_D{S8~k42U2*#HaP*GNa6H42sEeq$#!1XNC~axZ{W4&1g^= zw6}(DG>2~9;YP0#vHXb=R$|#|V*t8;a*Nb1f%8duasiFy@={??9*oRoAKUXccdu4T zWA-En#}IDA5SSAcm;vf&6qFRCVBII^RqvL@?e34btuY3VDfFOo6mIPVg}ug0GW9sz zT*RIcDF>=jkC#d}r}ATS(gPR9vSOM18C2|oee()CE=LR_Uz{s5({1Yk;N(vZ<l2E6 z4-H~roD)SdPin4aQ8M|_Tky^L>uqsVq`wTn9{Lf+mxhqR7zH?+FMd%^AC19W%@g!3 zeupvnCsrYTUALzj?Bf~s!q_3_aCOYj)th2eYraP*-O%~ZK?2}<>Ul!ahSU75goiN? z1};~?Y~&(8EB-EZ<5CJaDZ)gsQG!TDm(<7Yw?~?_>oI9Ai}1vM7a0~?<UM7NJYs1% z{rM*T1vX_B7PyNB)8_-iN=5^pJiqgS_Kch_xL-XURHw7AQ<`+LA5J@yUSC}_84~SN zl<<@k|FKT;jexV?hq-%nw=@mzjEA(v_|CHEI@v2be+WX}a{g}<V-;Xd<jgTj-R8uv zknZpW^fj(f@u-r$G`+tR@~np}z0%By(+@Z4C5{EvluNSiY+5Te?<GVHO6KCbWI<c> z3XA3k=6^6Fl>u*Ac%(s}&5`gWF!EHb;D$HEst-v%y5#^gH;8`}9iz~M|5FoyM;hR8 zE8dYEYcz4a8CO=a&NT&CJcGx~{@H=RYDsHmw4OD#m;HH*WeMg(2NhG68E!9Wi=@`m zMQo>Ya@)Z-em;_)Zc6Cw5mjWyMVyKci@|(bthc6z9NXaO8R~3O35B#N`C2b5ad<-< z3}&ucd8Di$XU#P@yvF?C<{aMeO=$KiY~yXuT%XVQz)ao}W+>vyM28l0IK@1lgIR2z zUN9wAK-#7;BkF{)YQ}6rd8H;2UV0|3AA(#>zU^r_tdf+!M50pbaE!uOmE%s*c<;X) z6VVtvwkX$v(`KrWf}!z)txv<5RrNQ5UqBt--Bo4|_sXXx%AHpSGNg!zMgn>W&eT#d z*T7w#zSV!}t;5`KWMbmr<H`04q{3Z&BF&fW`qKGq<$8!>O*XX}UA0u&#O8itY52b$ zge!LqZ-R#TD%N?)Hj3O5oa{^FCnn|tN4)3xrcKqDkns?XuP`cwzq?`n3x}|_Yq}|Z z;Q>Y;J1SPN!evuXrMj3Hr&lY1@%(tuPzHWsk$Fzkx~ty$qhz3-LfwNB*M?m!hi<&B z;{n~QBctY3;rR01^p)S6Btq27sNdllo^x+oquGWEGS`y8Di<-IW`OZZ`u2K5_-E$k z;nAwkEV*6kT+FtqR|ziZ$8bC5>&i;hW-Ag+M~DP%xF2>#igz^UOCKd<K;$-EPM2wz zQN=w-AfKoA9GXw8lTG~iMY0rsDn8n8c6v~6+2Qg7Cx^N1r3U=-vV54x1B=Hs^T)Ov zDTQDa*zhWy%y`r(m4v<~Y);Ea4CB}h;_OUQNh5c!N``8~O%s`<Ti_cOr%y~hY^-lf zjVv4Ax;;KmJg`J;<t~n%&}v@z{MWXj6<4IC>BDC~T^b%BoI)JjJGlCYBL3qti^aDu z;>W(d?qbZo2wm87of`$8`NG12!%6=puoc#-IRQLdvu{gDTHPSjyt-i&)}M<jkV<i; z`vG-$Y;iwXQs-6`C5e$yPn3Kluo*~EJ;+{+4cpZXl6$<fb>}KaY_Nt2HLI}L_6e_4 zsaYx0?H(JdSJ4P%C85X2AZSF~bIV0CPyWHNJf`S<$8^U%d-zj%lVvfHLWeI|#QR$? zBWS5(Lz1;2t-`}%JSMKg|4>X1rG8)M{;OWH0Bin}_$nZS5=tp=GLEP<Sbul?F`vV8 zXTm#S3B->p0d~1{T<$EF$ty~=_s^v|<|`3Kb$}Fah31IsDR)(SW7Z?2cm(4=^<|mx zYv(A;EMly^X&(&D60tB$>i6iK&sP1a9a3Rsi`?DWNE1D3K&%r57ZPQz-x!HA@03ha z{$}v$Z-7Z3>YK@OC+4AI8$3u{x$cJ{t~uQO*Yk#&4IFcS)vc~9sWg13uEU9<+;h0m z3KZO3Vk|i^k5_L%{M$mQz;$9&Nu#iGu8~Y?O-{2{{a4Qpu10q-+gS<<5c~7w{xXe` zs_#W^vv|n_sZSKx*S~~sMjvheFOn}Rf;x8gHcyc?bGiHF`=Tc2zKUnvc~hb>GyuE* zzboHVRYFI_vInLwlSZ;0hQyi;7`CbYP4ELRNCrShBsU0WwQoeK&*V5btuY3Gygz=l z_L0UQj?NRYnh&t3Jk&pW@BB7P(N_&r9dP!S1s2OGTOJ0averhbog=%km8<pGLJnRy z#qt*|d~YP&+WLcr|1%|ARlt&juiWTCBG$2)g<2GaX&OpS*!^qw^V8-}ab0c#vQ{)+ zO>SsIT7jBpCcS~$&t)xi=kTAxOI<TS=eO;?CVPGd-nxRcsx`x;Kp|xCuejp){DAB- z|B-+mfsK44C02MkXS3a1Sh4l+A2DQf7I^<MFe~k<Uk%@be1>FJkcf?t(x#S`$JcT@ zz&R;6o%!~V0?-S8=Nk>?4ORoa@;u8+v&Cz%ufCoZ3fE_wqfX;b0XyGvLr32LRXEO> zu2~_LxgQSM*r856TWO!(GG*6#U|c3Z&q_lvrNs)1iYO8vuhpw+zfgkM#;r@fwYE>q zF~gz!pz|5XSgsY!CD_og2_o&F4>+b~UV|Mfff*USSn8~d103Cs0ZYqtJE-}7(GV}( zUAtB|d9JhFmfxOAxuc{;NJ%jg$0w0moFb`ofv%i|fv(2f^u#L*r8INhay6UV$;RAF zpxELhgegV)X9e9qyEX0o{1yfQRCb*{pd0E0)8o>L9v{3n`U$!llx5p8I99{v&!Y-2 zr<e509XBXxow2`^`kvD<X4vvF-jgu|DU-JH50c0C){{SA>ls@b@3@zTj9ff~2=v~? z|KKF__`dEz)R!*Xk6b_A()7~p2MclBiW0tpxDMGvmuEzFFDA~sy-^$WLRVO~!=#53 zoiB*X_I8zPoaIc%#am*Kl?{|}ST<4tbpoHIzt4ht10%K8<=7w75<^|t#h)r)83f;b z(2h$*RG08K$n-53AmpvB;)CmOrv)ssXXl7RqpXX`Uq05<XK3W$4$gp$S<W6BLx3Rr zoy#O>cetdGit^hPR^Kck(iI@+gOf>P2=9CWUA;WE^1{~phGGShFf@F#@mA=!Auq}~ zXWBX)(rssfypy4L;PBnE&-XKWR&mW}=jK^PfiDAJu!oCvdE*BC1}jeH2Ilt$&RSta z#m(9sR)wE#H+t=DZhtcK+T@Qxh*ER~E!^9$&my&LccS2A`o)4E+MI=3`e^lgc3u+_ z*{W}s{3^wIgeC}^qxVo@G<A=!pDu5`iR*r}Xq(x1-*|2DCL{DVim7y*`ci}@R)0yi z(>-OAk!5^~*P3&_uj^gFeDFUizF@YImtrSfxjs`nADJPzf64B@9-}Xjw=W)Di0d|A zID%Y@Xxh?h9I7dc$fLs;$n(L}B3G@UCz6^kEoaPM3og9gWc2Vn4qB(>Ng6-j*VDPC z?_yd;k=^!^;UOFEipiLS!DGSBrs-=&UaJcnP8l%lO5SUy8QL><hHGULEY7;x!gRuy zhz_(QC<Z}1;I5jsnY=x8N?DMZy@WlTZb!`>J^ZNraRc84B?BxL{%<n*d96^W?3CZG e^UF=cI5V@eXr0`?YDMYo2smNoU`aUYb@gA$>~r7% diff --git a/vendor/github.com/xtaci/kcp-go/v5/fec.go b/vendor/github.com/xtaci/kcp-go/v5/fec.go index 0a203ee3..88f5a9c4 100644 --- a/vendor/github.com/xtaci/kcp-go/v5/fec.go +++ b/vendor/github.com/xtaci/kcp-go/v5/fec.go @@ -41,9 +41,6 @@ type fecDecoder struct { decodeCache [][]byte flagCache []bool - // zeros - zeros []byte - // RS decoder codec reedsolomon.Encoder @@ -68,7 +65,6 @@ func newFECDecoder(dataShards, parityShards int) *fecDecoder { dec.codec = codec dec.decodeCache = make([][]byte, dec.shardSize) dec.flagCache = make([]bool, dec.shardSize) - dec.zeros = make([]byte, mtuLimit) return dec } @@ -199,7 +195,7 @@ func (dec *fecDecoder) decode(in fecPacket) (recovered [][]byte) { if shards[k] != nil { dlen := len(shards[k]) shards[k] = shards[k][:maxlen] - copy(shards[k][dlen:], dec.zeros) + clear(shards[k][dlen:]) } else if k < dec.dataShards { shards[k] = xmitBuf.Get().([]byte)[:0] } @@ -279,9 +275,6 @@ type ( shardCache [][]byte encodeCache [][]byte - // zeros - zeros []byte - // RS encoder codec reedsolomon.Encoder } @@ -311,7 +304,6 @@ func newFECEncoder(dataShards, parityShards, offset int) *fecEncoder { for k := range enc.shardCache { enc.shardCache[k] = make([]byte, mtuLimit) } - enc.zeros = make([]byte, mtuLimit) return enc } @@ -341,7 +333,7 @@ func (enc *fecEncoder) encode(b []byte) (ps [][]byte) { for i := 0; i < enc.dataShards; i++ { shard := enc.shardCache[i] slen := len(shard) - copy(shard[slen:enc.maxSize], enc.zeros) + clear(shard[slen:enc.maxSize]) } // construct equal-sized slice with stripped header diff --git a/vendor/github.com/xtaci/kcp-go/v5/sess.go b/vendor/github.com/xtaci/kcp-go/v5/sess.go index 2dedd745..35e7b804 100644 --- a/vendor/github.com/xtaci/kcp-go/v5/sess.go +++ b/vendor/github.com/xtaci/kcp-go/v5/sess.go @@ -195,6 +195,16 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn // Read implements net.Conn func (s *UDPSession) Read(b []byte) (n int, err error) { + var timeout *time.Timer + // deadline for current reading operation + var c <-chan time.Time + if !s.rd.IsZero() { + delay := time.Until(s.rd) + timeout = time.NewTimer(delay) + c = timeout.C + defer timeout.Stop() + } + for { s.mu.Lock() if len(s.bufptr) > 0 { // copy from buffer into b @@ -228,27 +238,11 @@ func (s *UDPSession) Read(b []byte) (n int, err error) { return n, nil } - // deadline for current reading operation - var timeout *time.Timer - var c <-chan time.Time - if !s.rd.IsZero() { - if time.Now().After(s.rd) { - s.mu.Unlock() - return 0, errors.WithStack(errTimeout) - } - - delay := time.Until(s.rd) - timeout = time.NewTimer(delay) - c = timeout.C - } s.mu.Unlock() // wait for read event or timeout or error select { case <-s.chReadEvent: - if timeout != nil { - timeout.Stop() - } case <-c: return 0, errors.WithStack(errTimeout) case <-s.chSocketReadError: @@ -264,6 +258,15 @@ func (s *UDPSession) Write(b []byte) (n int, err error) { return s.WriteBuffers( // WriteBuffers write a vector of byte slices to the underlying connection func (s *UDPSession) WriteBuffers(v [][]byte) (n int, err error) { + var timeout *time.Timer + var c <-chan time.Time + if !s.wd.IsZero() { + delay := time.Until(s.wd) + timeout = time.NewTimer(delay) + c = timeout.C + defer timeout.Stop() + } + for { select { case <-s.chSocketWriteError: @@ -301,24 +304,10 @@ func (s *UDPSession) WriteBuffers(v [][]byte) (n int, err error) { return n, nil } - var timeout *time.Timer - var c <-chan time.Time - if !s.wd.IsZero() { - if time.Now().After(s.wd) { - s.mu.Unlock() - return 0, errors.WithStack(errTimeout) - } - delay := time.Until(s.wd) - timeout = time.NewTimer(delay) - c = timeout.C - } s.mu.Unlock() select { case <-s.chWriteEvent: - if timeout != nil { - timeout.Stop() - } case <-c: return 0, errors.WithStack(errTimeout) case <-s.chSocketWriteError: diff --git a/vendor/github.com/xtaci/kcp-go/v5/wechat_donate.jpg b/vendor/github.com/xtaci/kcp-go/v5/wechat_donate.jpg deleted file mode 100644 index ad72505ce56882faf47353fbc27c54826df29526..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35307 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 F|JmoHbYK7g diff --git a/vendor/modules.txt b/vendor/modules.txt index ffa7d6a2..807ebea4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -7,8 +7,8 @@ 0xacab.org/leap/bitmask-core/pkg/introducer 0xacab.org/leap/bitmask-core/pkg/models 0xacab.org/leap/bitmask-core/pkg/storage -# 0xacab.org/leap/obfsvpn v0.0.0-20240422180703-83037b24d5cc -## explicit; go 1.20 +# 0xacab.org/leap/obfsvpn v1.0.1-0.20240625123757-59f234eea051 +## explicit; go 1.22 0xacab.org/leap/obfsvpn/client 0xacab.org/leap/obfsvpn/obfsvpn # filippo.io/edwards25519 v1.1.0 @@ -126,9 +126,6 @@ github.com/josharian/intern # github.com/jtolds/gls v4.20.0+incompatible ## explicit github.com/jtolds/gls -# github.com/kalikaneko/socks5 v1.0.1 -## explicit; go 1.16 -github.com/kalikaneko/socks5 # github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 ## explicit github.com/kardianos/osext @@ -328,8 +325,8 @@ github.com/tjfoc/gmsm/sm4 # github.com/xtaci/kcp-go v5.4.20+incompatible ## explicit github.com/xtaci/kcp-go -# github.com/xtaci/kcp-go/v5 v5.6.1 -## explicit; go 1.13 +# github.com/xtaci/kcp-go/v5 v5.6.3 +## explicit; go 1.21 github.com/xtaci/kcp-go/v5 # github.com/xtaci/smux v1.5.24 ## explicit; go 1.13 -- GitLab