From 7eba7410fd3b6e77b23521dbdbb96b1ac0e8bb49 Mon Sep 17 00:00:00 2001 From: aus Date: Sat, 2 Feb 2019 22:06:16 -0600 Subject: [PATCH 01/16] add client-outbound SOCKS support --- client/client.go | 24 ++++++++++++++++++++++++ main.go | 10 +++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/client/client.go b/client/client.go index e278e99d..9127092c 100644 --- a/client/client.go +++ b/client/client.go @@ -4,13 +4,17 @@ import ( "context" "fmt" "io" + "io/ioutil" "net" + "log" "net/http" "net/url" + "os" "regexp" "strings" "time" + socks5 "github.com/armon/go-socks5" "github.com/gorilla/websocket" "github.com/jpillora/backoff" "github.com/jpillora/chisel/share" @@ -29,6 +33,7 @@ type Config struct { HTTPProxy string Remotes []string HostHeader string + Socks5 bool } //Client represents a client instance @@ -42,6 +47,7 @@ type Client struct { running bool runningc chan error connStats chshare.ConnStats + socksServer *socks5.Server } //NewClient creates a new client instance @@ -102,6 +108,20 @@ func NewClient(config *Config) (*Client, error) { Timeout: 30 * time.Second, } + if config.Socks5 { + socksConfig := &socks5.Config{} + if client.Debug { + socksConfig.Logger = log.New(os.Stdout, "[client socks]", log.Ldate|log.Ltime) + } else { + socksConfig.Logger = log.New(ioutil.Discard, "", 0) + } + client.socksServer, err = socks5.New(socksConfig) + if err != nil { + return nil, err + } + client.Infof("client-side SOCKS5 server enabled") + } + return client, nil } @@ -141,6 +161,10 @@ func (c *Client) Start(ctx context.Context) error { } } } + // start socks server + if c.socksServer != nil { + go c.socksServer.ListenAndServe("tcp", "127.0.0.1:1081") + } c.Infof("Connecting to %s%s\n", c.server, via) //optional keepalive loop if c.config.KeepAlive > 0 { diff --git a/main.go b/main.go index 570b508e..b6c02faf 100644 --- a/main.go +++ b/main.go @@ -124,7 +124,7 @@ var serverHelp = ` chisel receives a normal HTTP request. Useful for hiding chisel in plain sight. - --socks5, Allow clients to access the internal SOCKS5 proxy. See + --socks5, Allow clients to access the server-outbound SOCKS5 proxy. See chisel client --help for more information. --reverse, Allow clients to specify reverse port forwarding remotes @@ -266,6 +266,12 @@ var clientHelp = ` --hostname, Optionally set the 'Host' header (defaults to the host found in the server url). + + --socks5, Start a client-outbound SOCKS5 server on 127.0.0.1:1081. + Combine this option with a reverse port forward remote to expose the + client network to the chisel server. For example, the following + remote R:127.0.0.1:5000:127.0.0.1:1081 would allow the sever to + access the client network via socks5://127.0.0.1:5000. ` + commonHelp func client(args []string) { @@ -280,6 +286,7 @@ func client(args []string) { proxy := flags.String("proxy", "", "") pid := flags.Bool("pid", false, "") hostname := flags.String("hostname", "", "") + socks5 := flags.Bool("socks5", false, "") verbose := flags.Bool("v", false, "") flags.Usage = func() { fmt.Print(clientHelp) @@ -304,6 +311,7 @@ func client(args []string) { Server: args[0], Remotes: args[1:], HostHeader: *hostname, + Socks5: *socks5, }) if err != nil { log.Fatal(err) From 7497a3acca7dc21f29b686ade96346e2fbe47953 Mon Sep 17 00:00:00 2001 From: Meteorite Date: Sat, 9 Feb 2019 22:58:05 +0300 Subject: [PATCH 02/16] add support for connecting to chisel server via socks5 proxy in client --- client/client.go | 40 ++- main.go | 8 +- .../golang.org/x/net/internal/socks/client.go | 168 ++++++++++ .../x/net/internal/socks/dial_test.go | 170 ++++++++++ .../golang.org/x/net/internal/socks/socks.go | 317 ++++++++++++++++++ vendor/golang.org/x/net/proxy/direct.go | 18 + vendor/golang.org/x/net/proxy/per_host.go | 140 ++++++++ .../golang.org/x/net/proxy/per_host_test.go | 55 +++ vendor/golang.org/x/net/proxy/proxy.go | 139 ++++++++ vendor/golang.org/x/net/proxy/proxy_test.go | 125 +++++++ vendor/golang.org/x/net/proxy/socks5.go | 36 ++ 11 files changed, 1203 insertions(+), 13 deletions(-) create mode 100644 vendor/golang.org/x/net/internal/socks/client.go create mode 100644 vendor/golang.org/x/net/internal/socks/dial_test.go create mode 100644 vendor/golang.org/x/net/internal/socks/socks.go create mode 100644 vendor/golang.org/x/net/proxy/direct.go create mode 100644 vendor/golang.org/x/net/proxy/per_host.go create mode 100644 vendor/golang.org/x/net/proxy/per_host_test.go create mode 100644 vendor/golang.org/x/net/proxy/proxy.go create mode 100644 vendor/golang.org/x/net/proxy/proxy_test.go create mode 100644 vendor/golang.org/x/net/proxy/socks5.go diff --git a/client/client.go b/client/client.go index e278e99d..b3100495 100644 --- a/client/client.go +++ b/client/client.go @@ -15,6 +15,7 @@ import ( "github.com/jpillora/backoff" "github.com/jpillora/chisel/share" "golang.org/x/crypto/ssh" + "golang.org/x/net/proxy" ) //Config represents a client configuration @@ -26,7 +27,7 @@ type Config struct { MaxRetryCount int MaxRetryInterval time.Duration Server string - HTTPProxy string + Proxy string Remotes []string HostHeader string } @@ -37,7 +38,7 @@ type Client struct { config *Config sshConfig *ssh.ClientConfig sshConn ssh.Conn - httpProxyURL *url.URL + proxyURL *url.URL server string running bool runningc chan error @@ -85,8 +86,8 @@ func NewClient(config *Config) (*Client, error) { } client.Info = true - if p := config.HTTPProxy; p != "" { - client.httpProxyURL, err = url.Parse(p) + if p := config.Proxy; p != "" { + client.proxyURL, err = url.Parse(p) if err != nil { return nil, fmt.Errorf("Invalid proxy URL (%s)", err) } @@ -129,8 +130,8 @@ func (c *Client) verifyServer(hostname string, remote net.Addr, key ssh.PublicKe //Start client and does not block func (c *Client) Start(ctx context.Context) error { via := "" - if c.httpProxyURL != nil { - via = " via " + c.httpProxyURL.String() + if c.proxyURL != nil { + via = " via " + c.proxyURL.String() } //prepare non-reverse proxies for i, r := range c.config.shared.Remotes { @@ -193,10 +194,29 @@ func (c *Client) connectionLoop() { HandshakeTimeout: 45 * time.Second, Subprotocols: []string{chshare.ProtocolVersion}, } - //optionally CONNECT proxy - if c.httpProxyURL != nil { - d.Proxy = func(*http.Request) (*url.URL, error) { - return c.httpProxyURL, nil + //optionally proxy + if c.proxyURL != nil { + if strings.HasPrefix(c.proxyURL.Scheme, "socks") { + // SOCKS5 proxy + var auth *proxy.Auth = nil + if c.proxyURL.User != nil { + pass, _ := c.proxyURL.User.Password() + auth = &proxy.Auth { + User: c.proxyURL.User.Username(), + Password: pass, + } + } + socksDialer, err := proxy.SOCKS5("tcp", c.proxyURL.Host, auth, proxy.Direct) + if err != nil { + connerr = err + continue + } + d.NetDial = socksDialer.Dial + } else { + // CONNECT proxy + d.Proxy = func(*http.Request) (*url.URL, error) { + return c.proxyURL, nil + } } } wsHeaders := http.Header{} diff --git a/main.go b/main.go index 570b508e..74b97de7 100644 --- a/main.go +++ b/main.go @@ -260,9 +260,11 @@ var clientHelp = ` --max-retry-interval, Maximum wait time before retrying after a disconnection. Defaults to 5 minutes. - --proxy, An optional HTTP CONNECT proxy which will be used reach - the chisel server. Authentication can be specified inside the URL. + --proxy, An optional HTTP CONNECT or SOCKS5 proxy which will be + used to reach the chisel server. Authentication can be specified + inside the URL. For example, http://admin:password@my-server.com:8081 + or: socks://admin:password@my-server.com:1080 --hostname, Optionally set the 'Host' header (defaults to the host found in the server url). @@ -300,7 +302,7 @@ func client(args []string) { KeepAlive: *keepalive, MaxRetryCount: *maxRetryCount, MaxRetryInterval: *maxRetryInterval, - HTTPProxy: *proxy, + Proxy: *proxy, Server: args[0], Remotes: args[1:], HostHeader: *hostname, diff --git a/vendor/golang.org/x/net/internal/socks/client.go b/vendor/golang.org/x/net/internal/socks/client.go new file mode 100644 index 00000000..3d6f516a --- /dev/null +++ b/vendor/golang.org/x/net/internal/socks/client.go @@ -0,0 +1,168 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package socks + +import ( + "context" + "errors" + "io" + "net" + "strconv" + "time" +) + +var ( + noDeadline = time.Time{} + aLongTimeAgo = time.Unix(1, 0) +) + +func (d *Dialer) connect(ctx context.Context, c net.Conn, address string) (_ net.Addr, ctxErr error) { + host, port, err := splitHostPort(address) + if err != nil { + return nil, err + } + if deadline, ok := ctx.Deadline(); ok && !deadline.IsZero() { + c.SetDeadline(deadline) + defer c.SetDeadline(noDeadline) + } + if ctx != context.Background() { + errCh := make(chan error, 1) + done := make(chan struct{}) + defer func() { + close(done) + if ctxErr == nil { + ctxErr = <-errCh + } + }() + go func() { + select { + case <-ctx.Done(): + c.SetDeadline(aLongTimeAgo) + errCh <- ctx.Err() + case <-done: + errCh <- nil + } + }() + } + + b := make([]byte, 0, 6+len(host)) // the size here is just an estimate + b = append(b, Version5) + if len(d.AuthMethods) == 0 || d.Authenticate == nil { + b = append(b, 1, byte(AuthMethodNotRequired)) + } else { + ams := d.AuthMethods + if len(ams) > 255 { + return nil, errors.New("too many authentication methods") + } + b = append(b, byte(len(ams))) + for _, am := range ams { + b = append(b, byte(am)) + } + } + if _, ctxErr = c.Write(b); ctxErr != nil { + return + } + + if _, ctxErr = io.ReadFull(c, b[:2]); ctxErr != nil { + return + } + if b[0] != Version5 { + return nil, errors.New("unexpected protocol version " + strconv.Itoa(int(b[0]))) + } + am := AuthMethod(b[1]) + if am == AuthMethodNoAcceptableMethods { + return nil, errors.New("no acceptable authentication methods") + } + if d.Authenticate != nil { + if ctxErr = d.Authenticate(ctx, c, am); ctxErr != nil { + return + } + } + + b = b[:0] + b = append(b, Version5, byte(d.cmd), 0) + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + b = append(b, AddrTypeIPv4) + b = append(b, ip4...) + } else if ip6 := ip.To16(); ip6 != nil { + b = append(b, AddrTypeIPv6) + b = append(b, ip6...) + } else { + return nil, errors.New("unknown address type") + } + } else { + if len(host) > 255 { + return nil, errors.New("FQDN too long") + } + b = append(b, AddrTypeFQDN) + b = append(b, byte(len(host))) + b = append(b, host...) + } + b = append(b, byte(port>>8), byte(port)) + if _, ctxErr = c.Write(b); ctxErr != nil { + return + } + + if _, ctxErr = io.ReadFull(c, b[:4]); ctxErr != nil { + return + } + if b[0] != Version5 { + return nil, errors.New("unexpected protocol version " + strconv.Itoa(int(b[0]))) + } + if cmdErr := Reply(b[1]); cmdErr != StatusSucceeded { + return nil, errors.New("unknown error " + cmdErr.String()) + } + if b[2] != 0 { + return nil, errors.New("non-zero reserved field") + } + l := 2 + var a Addr + switch b[3] { + case AddrTypeIPv4: + l += net.IPv4len + a.IP = make(net.IP, net.IPv4len) + case AddrTypeIPv6: + l += net.IPv6len + a.IP = make(net.IP, net.IPv6len) + case AddrTypeFQDN: + if _, err := io.ReadFull(c, b[:1]); err != nil { + return nil, err + } + l += int(b[0]) + default: + return nil, errors.New("unknown address type " + strconv.Itoa(int(b[3]))) + } + if cap(b) < l { + b = make([]byte, l) + } else { + b = b[:l] + } + if _, ctxErr = io.ReadFull(c, b); ctxErr != nil { + return + } + if a.IP != nil { + copy(a.IP, b) + } else { + a.Name = string(b[:len(b)-2]) + } + a.Port = int(b[len(b)-2])<<8 | int(b[len(b)-1]) + return &a, nil +} + +func splitHostPort(address string) (string, int, error) { + host, port, err := net.SplitHostPort(address) + if err != nil { + return "", 0, err + } + portnum, err := strconv.Atoi(port) + if err != nil { + return "", 0, err + } + if 1 > portnum || portnum > 0xffff { + return "", 0, errors.New("port number out of range " + port) + } + return host, portnum, nil +} diff --git a/vendor/golang.org/x/net/internal/socks/dial_test.go b/vendor/golang.org/x/net/internal/socks/dial_test.go new file mode 100644 index 00000000..3a7a31bd --- /dev/null +++ b/vendor/golang.org/x/net/internal/socks/dial_test.go @@ -0,0 +1,170 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package socks_test + +import ( + "context" + "io" + "math/rand" + "net" + "os" + "testing" + "time" + + "golang.org/x/net/internal/socks" + "golang.org/x/net/internal/sockstest" +) + +func TestDial(t *testing.T) { + t.Run("Connect", func(t *testing.T) { + ss, err := sockstest.NewServer(sockstest.NoAuthRequired, sockstest.NoProxyRequired) + if err != nil { + t.Fatal(err) + } + defer ss.Close() + d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String()) + d.AuthMethods = []socks.AuthMethod{ + socks.AuthMethodNotRequired, + socks.AuthMethodUsernamePassword, + } + d.Authenticate = (&socks.UsernamePassword{ + Username: "username", + Password: "password", + }).Authenticate + c, err := d.DialContext(context.Background(), ss.TargetAddr().Network(), ss.TargetAddr().String()) + if err != nil { + t.Fatal(err) + } + c.(*socks.Conn).BoundAddr() + c.Close() + }) + t.Run("ConnectWithConn", func(t *testing.T) { + ss, err := sockstest.NewServer(sockstest.NoAuthRequired, sockstest.NoProxyRequired) + if err != nil { + t.Fatal(err) + } + defer ss.Close() + c, err := net.Dial(ss.Addr().Network(), ss.Addr().String()) + if err != nil { + t.Fatal(err) + } + defer c.Close() + d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String()) + d.AuthMethods = []socks.AuthMethod{ + socks.AuthMethodNotRequired, + socks.AuthMethodUsernamePassword, + } + d.Authenticate = (&socks.UsernamePassword{ + Username: "username", + Password: "password", + }).Authenticate + a, err := d.DialWithConn(context.Background(), c, ss.TargetAddr().Network(), ss.TargetAddr().String()) + if err != nil { + t.Fatal(err) + } + if _, ok := a.(*socks.Addr); !ok { + t.Fatalf("got %+v; want socks.Addr", a) + } + }) + t.Run("Cancel", func(t *testing.T) { + ss, err := sockstest.NewServer(sockstest.NoAuthRequired, blackholeCmdFunc) + if err != nil { + t.Fatal(err) + } + defer ss.Close() + d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String()) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + dialErr := make(chan error) + go func() { + c, err := d.DialContext(ctx, ss.TargetAddr().Network(), ss.TargetAddr().String()) + if err == nil { + c.Close() + } + dialErr <- err + }() + time.Sleep(100 * time.Millisecond) + cancel() + err = <-dialErr + if perr, nerr := parseDialError(err); perr != context.Canceled && nerr == nil { + t.Fatalf("got %v; want context.Canceled or equivalent", err) + } + }) + t.Run("Deadline", func(t *testing.T) { + ss, err := sockstest.NewServer(sockstest.NoAuthRequired, blackholeCmdFunc) + if err != nil { + t.Fatal(err) + } + defer ss.Close() + d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String()) + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(100*time.Millisecond)) + defer cancel() + c, err := d.DialContext(ctx, ss.TargetAddr().Network(), ss.TargetAddr().String()) + if err == nil { + c.Close() + } + if perr, nerr := parseDialError(err); perr != context.DeadlineExceeded && nerr == nil { + t.Fatalf("got %v; want context.DeadlineExceeded or equivalent", err) + } + }) + t.Run("WithRogueServer", func(t *testing.T) { + ss, err := sockstest.NewServer(sockstest.NoAuthRequired, rogueCmdFunc) + if err != nil { + t.Fatal(err) + } + defer ss.Close() + d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String()) + for i := 0; i < 2*len(rogueCmdList); i++ { + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(100*time.Millisecond)) + defer cancel() + c, err := d.DialContext(ctx, ss.TargetAddr().Network(), ss.TargetAddr().String()) + if err == nil { + t.Log(c.(*socks.Conn).BoundAddr()) + c.Close() + t.Error("should fail") + } + } + }) +} + +func blackholeCmdFunc(rw io.ReadWriter, b []byte) error { + if _, err := sockstest.ParseCmdRequest(b); err != nil { + return err + } + var bb [1]byte + for { + if _, err := rw.Read(bb[:]); err != nil { + return err + } + } +} + +func rogueCmdFunc(rw io.ReadWriter, b []byte) error { + if _, err := sockstest.ParseCmdRequest(b); err != nil { + return err + } + rw.Write(rogueCmdList[rand.Intn(len(rogueCmdList))]) + return nil +} + +var rogueCmdList = [][]byte{ + {0x05}, + {0x06, 0x00, 0x00, 0x01, 192, 0, 2, 1, 0x17, 0x4b}, + {0x05, 0x00, 0xff, 0x01, 192, 0, 2, 2, 0x17, 0x4b}, + {0x05, 0x00, 0x00, 0x01, 192, 0, 2, 3}, + {0x05, 0x00, 0x00, 0x03, 0x04, 'F', 'Q', 'D', 'N'}, +} + +func parseDialError(err error) (perr, nerr error) { + if e, ok := err.(*net.OpError); ok { + err = e.Err + nerr = e + } + if e, ok := err.(*os.SyscallError); ok { + err = e.Err + } + perr = err + return +} diff --git a/vendor/golang.org/x/net/internal/socks/socks.go b/vendor/golang.org/x/net/internal/socks/socks.go new file mode 100644 index 00000000..6929a9fd --- /dev/null +++ b/vendor/golang.org/x/net/internal/socks/socks.go @@ -0,0 +1,317 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package socks provides a SOCKS version 5 client implementation. +// +// SOCKS protocol version 5 is defined in RFC 1928. +// Username/Password authentication for SOCKS version 5 is defined in +// RFC 1929. +package socks + +import ( + "context" + "errors" + "io" + "net" + "strconv" +) + +// A Command represents a SOCKS command. +type Command int + +func (cmd Command) String() string { + switch cmd { + case CmdConnect: + return "socks connect" + case cmdBind: + return "socks bind" + default: + return "socks " + strconv.Itoa(int(cmd)) + } +} + +// An AuthMethod represents a SOCKS authentication method. +type AuthMethod int + +// A Reply represents a SOCKS command reply code. +type Reply int + +func (code Reply) String() string { + switch code { + case StatusSucceeded: + return "succeeded" + case 0x01: + return "general SOCKS server failure" + case 0x02: + return "connection not allowed by ruleset" + case 0x03: + return "network unreachable" + case 0x04: + return "host unreachable" + case 0x05: + return "connection refused" + case 0x06: + return "TTL expired" + case 0x07: + return "command not supported" + case 0x08: + return "address type not supported" + default: + return "unknown code: " + strconv.Itoa(int(code)) + } +} + +// Wire protocol constants. +const ( + Version5 = 0x05 + + AddrTypeIPv4 = 0x01 + AddrTypeFQDN = 0x03 + AddrTypeIPv6 = 0x04 + + CmdConnect Command = 0x01 // establishes an active-open forward proxy connection + cmdBind Command = 0x02 // establishes a passive-open forward proxy connection + + AuthMethodNotRequired AuthMethod = 0x00 // no authentication required + AuthMethodUsernamePassword AuthMethod = 0x02 // use username/password + AuthMethodNoAcceptableMethods AuthMethod = 0xff // no acceptable authentication methods + + StatusSucceeded Reply = 0x00 +) + +// An Addr represents a SOCKS-specific address. +// Either Name or IP is used exclusively. +type Addr struct { + Name string // fully-qualified domain name + IP net.IP + Port int +} + +func (a *Addr) Network() string { return "socks" } + +func (a *Addr) String() string { + if a == nil { + return "" + } + port := strconv.Itoa(a.Port) + if a.IP == nil { + return net.JoinHostPort(a.Name, port) + } + return net.JoinHostPort(a.IP.String(), port) +} + +// A Conn represents a forward proxy connection. +type Conn struct { + net.Conn + + boundAddr net.Addr +} + +// BoundAddr returns the address assigned by the proxy server for +// connecting to the command target address from the proxy server. +func (c *Conn) BoundAddr() net.Addr { + if c == nil { + return nil + } + return c.boundAddr +} + +// A Dialer holds SOCKS-specific options. +type Dialer struct { + cmd Command // either CmdConnect or cmdBind + proxyNetwork string // network between a proxy server and a client + proxyAddress string // proxy server address + + // ProxyDial specifies the optional dial function for + // establishing the transport connection. + ProxyDial func(context.Context, string, string) (net.Conn, error) + + // AuthMethods specifies the list of request authention + // methods. + // If empty, SOCKS client requests only AuthMethodNotRequired. + AuthMethods []AuthMethod + + // Authenticate specifies the optional authentication + // function. It must be non-nil when AuthMethods is not empty. + // It must return an error when the authentication is failed. + Authenticate func(context.Context, io.ReadWriter, AuthMethod) error +} + +// DialContext connects to the provided address on the provided +// network. +// +// The returned error value may be a net.OpError. When the Op field of +// net.OpError contains "socks", the Source field contains a proxy +// server address and the Addr field contains a command target +// address. +// +// See func Dial of the net package of standard library for a +// description of the network and address parameters. +func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + if err := d.validateTarget(network, address); err != nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + if ctx == nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")} + } + var err error + var c net.Conn + if d.ProxyDial != nil { + c, err = d.ProxyDial(ctx, d.proxyNetwork, d.proxyAddress) + } else { + var dd net.Dialer + c, err = dd.DialContext(ctx, d.proxyNetwork, d.proxyAddress) + } + if err != nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + a, err := d.connect(ctx, c, address) + if err != nil { + c.Close() + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + return &Conn{Conn: c, boundAddr: a}, nil +} + +// DialWithConn initiates a connection from SOCKS server to the target +// network and address using the connection c that is already +// connected to the SOCKS server. +// +// It returns the connection's local address assigned by the SOCKS +// server. +func (d *Dialer) DialWithConn(ctx context.Context, c net.Conn, network, address string) (net.Addr, error) { + if err := d.validateTarget(network, address); err != nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + if ctx == nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")} + } + a, err := d.connect(ctx, c, address) + if err != nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + return a, nil +} + +// Dial connects to the provided address on the provided network. +// +// Unlike DialContext, it returns a raw transport connection instead +// of a forward proxy connection. +// +// Deprecated: Use DialContext or DialWithConn instead. +func (d *Dialer) Dial(network, address string) (net.Conn, error) { + if err := d.validateTarget(network, address); err != nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + var err error + var c net.Conn + if d.ProxyDial != nil { + c, err = d.ProxyDial(context.Background(), d.proxyNetwork, d.proxyAddress) + } else { + c, err = net.Dial(d.proxyNetwork, d.proxyAddress) + } + if err != nil { + proxy, dst, _ := d.pathAddrs(address) + return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} + } + if _, err := d.DialWithConn(context.Background(), c, network, address); err != nil { + c.Close() + return nil, err + } + return c, nil +} + +func (d *Dialer) validateTarget(network, address string) error { + switch network { + case "tcp", "tcp6", "tcp4": + default: + return errors.New("network not implemented") + } + switch d.cmd { + case CmdConnect, cmdBind: + default: + return errors.New("command not implemented") + } + return nil +} + +func (d *Dialer) pathAddrs(address string) (proxy, dst net.Addr, err error) { + for i, s := range []string{d.proxyAddress, address} { + host, port, err := splitHostPort(s) + if err != nil { + return nil, nil, err + } + a := &Addr{Port: port} + a.IP = net.ParseIP(host) + if a.IP == nil { + a.Name = host + } + if i == 0 { + proxy = a + } else { + dst = a + } + } + return +} + +// NewDialer returns a new Dialer that dials through the provided +// proxy server's network and address. +func NewDialer(network, address string) *Dialer { + return &Dialer{proxyNetwork: network, proxyAddress: address, cmd: CmdConnect} +} + +const ( + authUsernamePasswordVersion = 0x01 + authStatusSucceeded = 0x00 +) + +// UsernamePassword are the credentials for the username/password +// authentication method. +type UsernamePassword struct { + Username string + Password string +} + +// Authenticate authenticates a pair of username and password with the +// proxy server. +func (up *UsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, auth AuthMethod) error { + switch auth { + case AuthMethodNotRequired: + return nil + case AuthMethodUsernamePassword: + if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) == 0 || len(up.Password) > 255 { + return errors.New("invalid username/password") + } + b := []byte{authUsernamePasswordVersion} + b = append(b, byte(len(up.Username))) + b = append(b, up.Username...) + b = append(b, byte(len(up.Password))) + b = append(b, up.Password...) + // TODO(mikio): handle IO deadlines and cancelation if + // necessary + if _, err := rw.Write(b); err != nil { + return err + } + if _, err := io.ReadFull(rw, b[:2]); err != nil { + return err + } + if b[0] != authUsernamePasswordVersion { + return errors.New("invalid username/password version") + } + if b[1] != authStatusSucceeded { + return errors.New("username/password authentication failed") + } + return nil + } + return errors.New("unsupported authentication method " + strconv.Itoa(int(auth))) +} diff --git a/vendor/golang.org/x/net/proxy/direct.go b/vendor/golang.org/x/net/proxy/direct.go new file mode 100644 index 00000000..4c5ad88b --- /dev/null +++ b/vendor/golang.org/x/net/proxy/direct.go @@ -0,0 +1,18 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "net" +) + +type direct struct{} + +// Direct is a direct proxy: one that makes network connections directly. +var Direct = direct{} + +func (direct) Dial(network, addr string) (net.Conn, error) { + return net.Dial(network, addr) +} diff --git a/vendor/golang.org/x/net/proxy/per_host.go b/vendor/golang.org/x/net/proxy/per_host.go new file mode 100644 index 00000000..0689bb6a --- /dev/null +++ b/vendor/golang.org/x/net/proxy/per_host.go @@ -0,0 +1,140 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "net" + "strings" +) + +// A PerHost directs connections to a default Dialer unless the host name +// requested matches one of a number of exceptions. +type PerHost struct { + def, bypass Dialer + + bypassNetworks []*net.IPNet + bypassIPs []net.IP + bypassZones []string + bypassHosts []string +} + +// NewPerHost returns a PerHost Dialer that directs connections to either +// defaultDialer or bypass, depending on whether the connection matches one of +// the configured rules. +func NewPerHost(defaultDialer, bypass Dialer) *PerHost { + return &PerHost{ + def: defaultDialer, + bypass: bypass, + } +} + +// Dial connects to the address addr on the given network through either +// defaultDialer or bypass. +func (p *PerHost) Dial(network, addr string) (c net.Conn, err error) { + host, _, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + + return p.dialerForRequest(host).Dial(network, addr) +} + +func (p *PerHost) dialerForRequest(host string) Dialer { + if ip := net.ParseIP(host); ip != nil { + for _, net := range p.bypassNetworks { + if net.Contains(ip) { + return p.bypass + } + } + for _, bypassIP := range p.bypassIPs { + if bypassIP.Equal(ip) { + return p.bypass + } + } + return p.def + } + + for _, zone := range p.bypassZones { + if strings.HasSuffix(host, zone) { + return p.bypass + } + if host == zone[1:] { + // For a zone ".example.com", we match "example.com" + // too. + return p.bypass + } + } + for _, bypassHost := range p.bypassHosts { + if bypassHost == host { + return p.bypass + } + } + return p.def +} + +// AddFromString parses a string that contains comma-separated values +// specifying hosts that should use the bypass proxy. Each value is either an +// IP address, a CIDR range, a zone (*.example.com) or a host name +// (localhost). A best effort is made to parse the string and errors are +// ignored. +func (p *PerHost) AddFromString(s string) { + hosts := strings.Split(s, ",") + for _, host := range hosts { + host = strings.TrimSpace(host) + if len(host) == 0 { + continue + } + if strings.Contains(host, "/") { + // We assume that it's a CIDR address like 127.0.0.0/8 + if _, net, err := net.ParseCIDR(host); err == nil { + p.AddNetwork(net) + } + continue + } + if ip := net.ParseIP(host); ip != nil { + p.AddIP(ip) + continue + } + if strings.HasPrefix(host, "*.") { + p.AddZone(host[1:]) + continue + } + p.AddHost(host) + } +} + +// AddIP specifies an IP address that will use the bypass proxy. Note that +// this will only take effect if a literal IP address is dialed. A connection +// to a named host will never match an IP. +func (p *PerHost) AddIP(ip net.IP) { + p.bypassIPs = append(p.bypassIPs, ip) +} + +// AddNetwork specifies an IP range that will use the bypass proxy. Note that +// this will only take effect if a literal IP address is dialed. A connection +// to a named host will never match. +func (p *PerHost) AddNetwork(net *net.IPNet) { + p.bypassNetworks = append(p.bypassNetworks, net) +} + +// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of +// "example.com" matches "example.com" and all of its subdomains. +func (p *PerHost) AddZone(zone string) { + if strings.HasSuffix(zone, ".") { + zone = zone[:len(zone)-1] + } + if !strings.HasPrefix(zone, ".") { + zone = "." + zone + } + p.bypassZones = append(p.bypassZones, zone) +} + +// AddHost specifies a host name that will use the bypass proxy. +func (p *PerHost) AddHost(host string) { + if strings.HasSuffix(host, ".") { + host = host[:len(host)-1] + } + p.bypassHosts = append(p.bypassHosts, host) +} diff --git a/vendor/golang.org/x/net/proxy/per_host_test.go b/vendor/golang.org/x/net/proxy/per_host_test.go new file mode 100644 index 00000000..a7d80957 --- /dev/null +++ b/vendor/golang.org/x/net/proxy/per_host_test.go @@ -0,0 +1,55 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "errors" + "net" + "reflect" + "testing" +) + +type recordingProxy struct { + addrs []string +} + +func (r *recordingProxy) Dial(network, addr string) (net.Conn, error) { + r.addrs = append(r.addrs, addr) + return nil, errors.New("recordingProxy") +} + +func TestPerHost(t *testing.T) { + var def, bypass recordingProxy + perHost := NewPerHost(&def, &bypass) + perHost.AddFromString("localhost,*.zone,127.0.0.1,10.0.0.1/8,1000::/16") + + expectedDef := []string{ + "example.com:123", + "1.2.3.4:123", + "[1001::]:123", + } + expectedBypass := []string{ + "localhost:123", + "zone:123", + "foo.zone:123", + "127.0.0.1:123", + "10.1.2.3:123", + "[1000::]:123", + } + + for _, addr := range expectedDef { + perHost.Dial("tcp", addr) + } + for _, addr := range expectedBypass { + perHost.Dial("tcp", addr) + } + + if !reflect.DeepEqual(expectedDef, def.addrs) { + t.Errorf("Hosts which went to the default proxy didn't match. Got %v, want %v", def.addrs, expectedDef) + } + if !reflect.DeepEqual(expectedBypass, bypass.addrs) { + t.Errorf("Hosts which went to the bypass proxy didn't match. Got %v, want %v", bypass.addrs, expectedBypass) + } +} diff --git a/vendor/golang.org/x/net/proxy/proxy.go b/vendor/golang.org/x/net/proxy/proxy.go new file mode 100644 index 00000000..f6026b90 --- /dev/null +++ b/vendor/golang.org/x/net/proxy/proxy.go @@ -0,0 +1,139 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package proxy provides support for a variety of protocols to proxy network +// data. +package proxy // import "golang.org/x/net/proxy" + +import ( + "errors" + "net" + "net/url" + "os" + "sync" +) + +// A Dialer is a means to establish a connection. +type Dialer interface { + // Dial connects to the given address via the proxy. + Dial(network, addr string) (c net.Conn, err error) +} + +// Auth contains authentication parameters that specific Dialers may require. +type Auth struct { + User, Password string +} + +// FromEnvironment returns the dialer specified by the proxy related variables in +// the environment. +func FromEnvironment() Dialer { + allProxy := allProxyEnv.Get() + if len(allProxy) == 0 { + return Direct + } + + proxyURL, err := url.Parse(allProxy) + if err != nil { + return Direct + } + proxy, err := FromURL(proxyURL, Direct) + if err != nil { + return Direct + } + + noProxy := noProxyEnv.Get() + if len(noProxy) == 0 { + return proxy + } + + perHost := NewPerHost(proxy, Direct) + perHost.AddFromString(noProxy) + return perHost +} + +// proxySchemes is a map from URL schemes to a function that creates a Dialer +// from a URL with such a scheme. +var proxySchemes map[string]func(*url.URL, Dialer) (Dialer, error) + +// RegisterDialerType takes a URL scheme and a function to generate Dialers from +// a URL with that scheme and a forwarding Dialer. Registered schemes are used +// by FromURL. +func RegisterDialerType(scheme string, f func(*url.URL, Dialer) (Dialer, error)) { + if proxySchemes == nil { + proxySchemes = make(map[string]func(*url.URL, Dialer) (Dialer, error)) + } + proxySchemes[scheme] = f +} + +// FromURL returns a Dialer given a URL specification and an underlying +// Dialer for it to make network requests. +func FromURL(u *url.URL, forward Dialer) (Dialer, error) { + var auth *Auth + if u.User != nil { + auth = new(Auth) + auth.User = u.User.Username() + if p, ok := u.User.Password(); ok { + auth.Password = p + } + } + + switch u.Scheme { + case "socks5", "socks5h": + addr := u.Hostname() + port := u.Port() + if port == "" { + port = "1080" + } + return SOCKS5("tcp", net.JoinHostPort(addr, port), auth, forward) + } + + // If the scheme doesn't match any of the built-in schemes, see if it + // was registered by another package. + if proxySchemes != nil { + if f, ok := proxySchemes[u.Scheme]; ok { + return f(u, forward) + } + } + + return nil, errors.New("proxy: unknown scheme: " + u.Scheme) +} + +var ( + allProxyEnv = &envOnce{ + names: []string{"ALL_PROXY", "all_proxy"}, + } + noProxyEnv = &envOnce{ + names: []string{"NO_PROXY", "no_proxy"}, + } +) + +// envOnce looks up an environment variable (optionally by multiple +// names) once. It mitigates expensive lookups on some platforms +// (e.g. Windows). +// (Borrowed from net/http/transport.go) +type envOnce struct { + names []string + once sync.Once + val string +} + +func (e *envOnce) Get() string { + e.once.Do(e.init) + return e.val +} + +func (e *envOnce) init() { + for _, n := range e.names { + e.val = os.Getenv(n) + if e.val != "" { + return + } + } +} + +// reset is used by tests +func (e *envOnce) reset() { + e.once = sync.Once{} + e.val = "" +} diff --git a/vendor/golang.org/x/net/proxy/proxy_test.go b/vendor/golang.org/x/net/proxy/proxy_test.go new file mode 100644 index 00000000..d260d699 --- /dev/null +++ b/vendor/golang.org/x/net/proxy/proxy_test.go @@ -0,0 +1,125 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "bytes" + "fmt" + "net/url" + "os" + "strings" + "testing" + + "golang.org/x/net/internal/socks" + "golang.org/x/net/internal/sockstest" +) + +type proxyFromEnvTest struct { + allProxyEnv string + noProxyEnv string + wantTypeOf Dialer +} + +func (t proxyFromEnvTest) String() string { + var buf bytes.Buffer + space := func() { + if buf.Len() > 0 { + buf.WriteByte(' ') + } + } + if t.allProxyEnv != "" { + fmt.Fprintf(&buf, "all_proxy=%q", t.allProxyEnv) + } + if t.noProxyEnv != "" { + space() + fmt.Fprintf(&buf, "no_proxy=%q", t.noProxyEnv) + } + return strings.TrimSpace(buf.String()) +} + +func TestFromEnvironment(t *testing.T) { + ResetProxyEnv() + + type dummyDialer struct { + direct + } + + RegisterDialerType("irc", func(_ *url.URL, _ Dialer) (Dialer, error) { + return dummyDialer{}, nil + }) + + proxyFromEnvTests := []proxyFromEnvTest{ + {allProxyEnv: "127.0.0.1:8080", noProxyEnv: "localhost, 127.0.0.1", wantTypeOf: direct{}}, + {allProxyEnv: "ftp://example.com:8000", noProxyEnv: "localhost, 127.0.0.1", wantTypeOf: direct{}}, + {allProxyEnv: "socks5://example.com:8080", noProxyEnv: "localhost, 127.0.0.1", wantTypeOf: &PerHost{}}, + {allProxyEnv: "socks5h://example.com", wantTypeOf: &socks.Dialer{}}, + {allProxyEnv: "irc://example.com:8000", wantTypeOf: dummyDialer{}}, + {noProxyEnv: "localhost, 127.0.0.1", wantTypeOf: direct{}}, + {wantTypeOf: direct{}}, + } + + for _, tt := range proxyFromEnvTests { + os.Setenv("ALL_PROXY", tt.allProxyEnv) + os.Setenv("NO_PROXY", tt.noProxyEnv) + ResetCachedEnvironment() + + d := FromEnvironment() + if got, want := fmt.Sprintf("%T", d), fmt.Sprintf("%T", tt.wantTypeOf); got != want { + t.Errorf("%v: got type = %T, want %T", tt, d, tt.wantTypeOf) + } + } +} + +func TestFromURL(t *testing.T) { + ss, err := sockstest.NewServer(sockstest.NoAuthRequired, sockstest.NoProxyRequired) + if err != nil { + t.Fatal(err) + } + defer ss.Close() + url, err := url.Parse("socks5://user:password@" + ss.Addr().String()) + if err != nil { + t.Fatal(err) + } + proxy, err := FromURL(url, nil) + if err != nil { + t.Fatal(err) + } + c, err := proxy.Dial("tcp", "fqdn.doesnotexist:5963") + if err != nil { + t.Fatal(err) + } + c.Close() +} + +func TestSOCKS5(t *testing.T) { + ss, err := sockstest.NewServer(sockstest.NoAuthRequired, sockstest.NoProxyRequired) + if err != nil { + t.Fatal(err) + } + defer ss.Close() + proxy, err := SOCKS5("tcp", ss.Addr().String(), nil, nil) + if err != nil { + t.Fatal(err) + } + c, err := proxy.Dial("tcp", ss.TargetAddr().String()) + if err != nil { + t.Fatal(err) + } + c.Close() +} + +func ResetProxyEnv() { + for _, env := range []*envOnce{allProxyEnv, noProxyEnv} { + for _, v := range env.names { + os.Setenv(v, "") + } + } + ResetCachedEnvironment() +} + +func ResetCachedEnvironment() { + allProxyEnv.reset() + noProxyEnv.reset() +} diff --git a/vendor/golang.org/x/net/proxy/socks5.go b/vendor/golang.org/x/net/proxy/socks5.go new file mode 100644 index 00000000..56345ec8 --- /dev/null +++ b/vendor/golang.org/x/net/proxy/socks5.go @@ -0,0 +1,36 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "context" + "net" + + "golang.org/x/net/internal/socks" +) + +// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given +// address with an optional username and password. +// See RFC 1928 and RFC 1929. +func SOCKS5(network, address string, auth *Auth, forward Dialer) (Dialer, error) { + d := socks.NewDialer(network, address) + if forward != nil { + d.ProxyDial = func(_ context.Context, network string, address string) (net.Conn, error) { + return forward.Dial(network, address) + } + } + if auth != nil { + up := socks.UsernamePassword{ + Username: auth.User, + Password: auth.Password, + } + d.AuthMethods = []socks.AuthMethod{ + socks.AuthMethodNotRequired, + socks.AuthMethodUsernamePassword, + } + d.Authenticate = up.Authenticate + } + return d, nil +} From f5e0bce0cd9f111d3d516812c918cb77931466f9 Mon Sep 17 00:00:00 2001 From: Meteorite Date: Sat, 9 Feb 2019 23:41:12 +0300 Subject: [PATCH 03/16] update go.mod, vendor consistent golang.org/x/net parts --- go.mod | 2 +- .../x/net/internal/socks/dial_test.go | 170 ------------------ .../golang.org/x/net/proxy/per_host_test.go | 55 ------ vendor/golang.org/x/net/proxy/proxy.go | 9 +- vendor/golang.org/x/net/proxy/proxy_test.go | 125 ------------- vendor/modules.txt | 2 + 6 files changed, 5 insertions(+), 358 deletions(-) delete mode 100644 vendor/golang.org/x/net/internal/socks/dial_test.go delete mode 100644 vendor/golang.org/x/net/proxy/per_host_test.go delete mode 100644 vendor/golang.org/x/net/proxy/proxy_test.go diff --git a/go.mod b/go.mod index 72fdfaba..0880d6d1 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,6 @@ require ( github.com/jpillora/sizestr v0.0.0-20160130011556-e2ea2fa42fb9 github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e - golang.org/x/net v0.0.0-20181017193950-04a2e542c03f // indirect + golang.org/x/net v0.0.0-20181017193950-04a2e542c03f golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8 // indirect ) diff --git a/vendor/golang.org/x/net/internal/socks/dial_test.go b/vendor/golang.org/x/net/internal/socks/dial_test.go deleted file mode 100644 index 3a7a31bd..00000000 --- a/vendor/golang.org/x/net/internal/socks/dial_test.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package socks_test - -import ( - "context" - "io" - "math/rand" - "net" - "os" - "testing" - "time" - - "golang.org/x/net/internal/socks" - "golang.org/x/net/internal/sockstest" -) - -func TestDial(t *testing.T) { - t.Run("Connect", func(t *testing.T) { - ss, err := sockstest.NewServer(sockstest.NoAuthRequired, sockstest.NoProxyRequired) - if err != nil { - t.Fatal(err) - } - defer ss.Close() - d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String()) - d.AuthMethods = []socks.AuthMethod{ - socks.AuthMethodNotRequired, - socks.AuthMethodUsernamePassword, - } - d.Authenticate = (&socks.UsernamePassword{ - Username: "username", - Password: "password", - }).Authenticate - c, err := d.DialContext(context.Background(), ss.TargetAddr().Network(), ss.TargetAddr().String()) - if err != nil { - t.Fatal(err) - } - c.(*socks.Conn).BoundAddr() - c.Close() - }) - t.Run("ConnectWithConn", func(t *testing.T) { - ss, err := sockstest.NewServer(sockstest.NoAuthRequired, sockstest.NoProxyRequired) - if err != nil { - t.Fatal(err) - } - defer ss.Close() - c, err := net.Dial(ss.Addr().Network(), ss.Addr().String()) - if err != nil { - t.Fatal(err) - } - defer c.Close() - d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String()) - d.AuthMethods = []socks.AuthMethod{ - socks.AuthMethodNotRequired, - socks.AuthMethodUsernamePassword, - } - d.Authenticate = (&socks.UsernamePassword{ - Username: "username", - Password: "password", - }).Authenticate - a, err := d.DialWithConn(context.Background(), c, ss.TargetAddr().Network(), ss.TargetAddr().String()) - if err != nil { - t.Fatal(err) - } - if _, ok := a.(*socks.Addr); !ok { - t.Fatalf("got %+v; want socks.Addr", a) - } - }) - t.Run("Cancel", func(t *testing.T) { - ss, err := sockstest.NewServer(sockstest.NoAuthRequired, blackholeCmdFunc) - if err != nil { - t.Fatal(err) - } - defer ss.Close() - d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String()) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - dialErr := make(chan error) - go func() { - c, err := d.DialContext(ctx, ss.TargetAddr().Network(), ss.TargetAddr().String()) - if err == nil { - c.Close() - } - dialErr <- err - }() - time.Sleep(100 * time.Millisecond) - cancel() - err = <-dialErr - if perr, nerr := parseDialError(err); perr != context.Canceled && nerr == nil { - t.Fatalf("got %v; want context.Canceled or equivalent", err) - } - }) - t.Run("Deadline", func(t *testing.T) { - ss, err := sockstest.NewServer(sockstest.NoAuthRequired, blackholeCmdFunc) - if err != nil { - t.Fatal(err) - } - defer ss.Close() - d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String()) - ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(100*time.Millisecond)) - defer cancel() - c, err := d.DialContext(ctx, ss.TargetAddr().Network(), ss.TargetAddr().String()) - if err == nil { - c.Close() - } - if perr, nerr := parseDialError(err); perr != context.DeadlineExceeded && nerr == nil { - t.Fatalf("got %v; want context.DeadlineExceeded or equivalent", err) - } - }) - t.Run("WithRogueServer", func(t *testing.T) { - ss, err := sockstest.NewServer(sockstest.NoAuthRequired, rogueCmdFunc) - if err != nil { - t.Fatal(err) - } - defer ss.Close() - d := socks.NewDialer(ss.Addr().Network(), ss.Addr().String()) - for i := 0; i < 2*len(rogueCmdList); i++ { - ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(100*time.Millisecond)) - defer cancel() - c, err := d.DialContext(ctx, ss.TargetAddr().Network(), ss.TargetAddr().String()) - if err == nil { - t.Log(c.(*socks.Conn).BoundAddr()) - c.Close() - t.Error("should fail") - } - } - }) -} - -func blackholeCmdFunc(rw io.ReadWriter, b []byte) error { - if _, err := sockstest.ParseCmdRequest(b); err != nil { - return err - } - var bb [1]byte - for { - if _, err := rw.Read(bb[:]); err != nil { - return err - } - } -} - -func rogueCmdFunc(rw io.ReadWriter, b []byte) error { - if _, err := sockstest.ParseCmdRequest(b); err != nil { - return err - } - rw.Write(rogueCmdList[rand.Intn(len(rogueCmdList))]) - return nil -} - -var rogueCmdList = [][]byte{ - {0x05}, - {0x06, 0x00, 0x00, 0x01, 192, 0, 2, 1, 0x17, 0x4b}, - {0x05, 0x00, 0xff, 0x01, 192, 0, 2, 2, 0x17, 0x4b}, - {0x05, 0x00, 0x00, 0x01, 192, 0, 2, 3}, - {0x05, 0x00, 0x00, 0x03, 0x04, 'F', 'Q', 'D', 'N'}, -} - -func parseDialError(err error) (perr, nerr error) { - if e, ok := err.(*net.OpError); ok { - err = e.Err - nerr = e - } - if e, ok := err.(*os.SyscallError); ok { - err = e.Err - } - perr = err - return -} diff --git a/vendor/golang.org/x/net/proxy/per_host_test.go b/vendor/golang.org/x/net/proxy/per_host_test.go deleted file mode 100644 index a7d80957..00000000 --- a/vendor/golang.org/x/net/proxy/per_host_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proxy - -import ( - "errors" - "net" - "reflect" - "testing" -) - -type recordingProxy struct { - addrs []string -} - -func (r *recordingProxy) Dial(network, addr string) (net.Conn, error) { - r.addrs = append(r.addrs, addr) - return nil, errors.New("recordingProxy") -} - -func TestPerHost(t *testing.T) { - var def, bypass recordingProxy - perHost := NewPerHost(&def, &bypass) - perHost.AddFromString("localhost,*.zone,127.0.0.1,10.0.0.1/8,1000::/16") - - expectedDef := []string{ - "example.com:123", - "1.2.3.4:123", - "[1001::]:123", - } - expectedBypass := []string{ - "localhost:123", - "zone:123", - "foo.zone:123", - "127.0.0.1:123", - "10.1.2.3:123", - "[1000::]:123", - } - - for _, addr := range expectedDef { - perHost.Dial("tcp", addr) - } - for _, addr := range expectedBypass { - perHost.Dial("tcp", addr) - } - - if !reflect.DeepEqual(expectedDef, def.addrs) { - t.Errorf("Hosts which went to the default proxy didn't match. Got %v, want %v", def.addrs, expectedDef) - } - if !reflect.DeepEqual(expectedBypass, bypass.addrs) { - t.Errorf("Hosts which went to the bypass proxy didn't match. Got %v, want %v", bypass.addrs, expectedBypass) - } -} diff --git a/vendor/golang.org/x/net/proxy/proxy.go b/vendor/golang.org/x/net/proxy/proxy.go index f6026b90..553ead7c 100644 --- a/vendor/golang.org/x/net/proxy/proxy.go +++ b/vendor/golang.org/x/net/proxy/proxy.go @@ -79,13 +79,8 @@ func FromURL(u *url.URL, forward Dialer) (Dialer, error) { } switch u.Scheme { - case "socks5", "socks5h": - addr := u.Hostname() - port := u.Port() - if port == "" { - port = "1080" - } - return SOCKS5("tcp", net.JoinHostPort(addr, port), auth, forward) + case "socks5": + return SOCKS5("tcp", u.Host, auth, forward) } // If the scheme doesn't match any of the built-in schemes, see if it diff --git a/vendor/golang.org/x/net/proxy/proxy_test.go b/vendor/golang.org/x/net/proxy/proxy_test.go deleted file mode 100644 index d260d699..00000000 --- a/vendor/golang.org/x/net/proxy/proxy_test.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proxy - -import ( - "bytes" - "fmt" - "net/url" - "os" - "strings" - "testing" - - "golang.org/x/net/internal/socks" - "golang.org/x/net/internal/sockstest" -) - -type proxyFromEnvTest struct { - allProxyEnv string - noProxyEnv string - wantTypeOf Dialer -} - -func (t proxyFromEnvTest) String() string { - var buf bytes.Buffer - space := func() { - if buf.Len() > 0 { - buf.WriteByte(' ') - } - } - if t.allProxyEnv != "" { - fmt.Fprintf(&buf, "all_proxy=%q", t.allProxyEnv) - } - if t.noProxyEnv != "" { - space() - fmt.Fprintf(&buf, "no_proxy=%q", t.noProxyEnv) - } - return strings.TrimSpace(buf.String()) -} - -func TestFromEnvironment(t *testing.T) { - ResetProxyEnv() - - type dummyDialer struct { - direct - } - - RegisterDialerType("irc", func(_ *url.URL, _ Dialer) (Dialer, error) { - return dummyDialer{}, nil - }) - - proxyFromEnvTests := []proxyFromEnvTest{ - {allProxyEnv: "127.0.0.1:8080", noProxyEnv: "localhost, 127.0.0.1", wantTypeOf: direct{}}, - {allProxyEnv: "ftp://example.com:8000", noProxyEnv: "localhost, 127.0.0.1", wantTypeOf: direct{}}, - {allProxyEnv: "socks5://example.com:8080", noProxyEnv: "localhost, 127.0.0.1", wantTypeOf: &PerHost{}}, - {allProxyEnv: "socks5h://example.com", wantTypeOf: &socks.Dialer{}}, - {allProxyEnv: "irc://example.com:8000", wantTypeOf: dummyDialer{}}, - {noProxyEnv: "localhost, 127.0.0.1", wantTypeOf: direct{}}, - {wantTypeOf: direct{}}, - } - - for _, tt := range proxyFromEnvTests { - os.Setenv("ALL_PROXY", tt.allProxyEnv) - os.Setenv("NO_PROXY", tt.noProxyEnv) - ResetCachedEnvironment() - - d := FromEnvironment() - if got, want := fmt.Sprintf("%T", d), fmt.Sprintf("%T", tt.wantTypeOf); got != want { - t.Errorf("%v: got type = %T, want %T", tt, d, tt.wantTypeOf) - } - } -} - -func TestFromURL(t *testing.T) { - ss, err := sockstest.NewServer(sockstest.NoAuthRequired, sockstest.NoProxyRequired) - if err != nil { - t.Fatal(err) - } - defer ss.Close() - url, err := url.Parse("socks5://user:password@" + ss.Addr().String()) - if err != nil { - t.Fatal(err) - } - proxy, err := FromURL(url, nil) - if err != nil { - t.Fatal(err) - } - c, err := proxy.Dial("tcp", "fqdn.doesnotexist:5963") - if err != nil { - t.Fatal(err) - } - c.Close() -} - -func TestSOCKS5(t *testing.T) { - ss, err := sockstest.NewServer(sockstest.NoAuthRequired, sockstest.NoProxyRequired) - if err != nil { - t.Fatal(err) - } - defer ss.Close() - proxy, err := SOCKS5("tcp", ss.Addr().String(), nil, nil) - if err != nil { - t.Fatal(err) - } - c, err := proxy.Dial("tcp", ss.TargetAddr().String()) - if err != nil { - t.Fatal(err) - } - c.Close() -} - -func ResetProxyEnv() { - for _, env := range []*envOnce{allProxyEnv, noProxyEnv} { - for _, v := range env.names { - os.Setenv(v, "") - } - } - ResetCachedEnvironment() -} - -func ResetCachedEnvironment() { - allProxyEnv.reset() - noProxyEnv.reset() -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6815094d..d1d5bb6b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -26,5 +26,7 @@ golang.org/x/crypto/ed25519/internal/edwards25519 golang.org/x/crypto/internal/subtle # golang.org/x/net v0.0.0-20181017193950-04a2e542c03f golang.org/x/net/context +golang.org/x/net/internal/socks +golang.org/x/net/proxy # golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8 golang.org/x/sys/unix From d6686776b526871f8b57e09483f0f716c4c86ffa Mon Sep 17 00:00:00 2001 From: Meteorite Date: Sun, 10 Feb 2019 11:01:58 +0300 Subject: [PATCH 04/16] add -proxy socks:// to README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 822451e4..f222e849 100644 --- a/README.md +++ b/README.md @@ -212,9 +212,11 @@ $ chisel client --help --max-retry-interval, Maximum wait time before retrying after a disconnection. Defaults to 5 minutes. - --proxy, An optional HTTP CONNECT proxy which will be used reach - the chisel server. Authentication can be specified inside the URL. + --proxy, An optional HTTP CONNECT or SOCKS5 proxy which will be + used to reach the chisel server. Authentication can be specified + inside the URL. For example, http://admin:password@my-server.com:8081 + or: socks://admin:password@my-server.com:1080 --hostname, Optionally set the 'Host' header (defaults to the host defined in the endpoint url). From 622272bf2ec1a6bac41e5a6e4f05c83fb97f8d41 Mon Sep 17 00:00:00 2001 From: Meteorite Date: Sun, 10 Feb 2019 12:35:23 +0300 Subject: [PATCH 05/16] format recent changes with go fmt --- client/client.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/client.go b/client/client.go index b3100495..ddd99cfe 100644 --- a/client/client.go +++ b/client/client.go @@ -35,14 +35,14 @@ type Config struct { //Client represents a client instance type Client struct { *chshare.Logger - config *Config - sshConfig *ssh.ClientConfig - sshConn ssh.Conn - proxyURL *url.URL - server string - running bool - runningc chan error - connStats chshare.ConnStats + config *Config + sshConfig *ssh.ClientConfig + sshConn ssh.Conn + proxyURL *url.URL + server string + running bool + runningc chan error + connStats chshare.ConnStats } //NewClient creates a new client instance @@ -201,7 +201,7 @@ func (c *Client) connectionLoop() { var auth *proxy.Auth = nil if c.proxyURL.User != nil { pass, _ := c.proxyURL.User.Password() - auth = &proxy.Auth { + auth = &proxy.Auth{ User: c.proxyURL.User.Username(), Password: pass, } From 8607fa10b41614e309e622bad5dedaaa705a47ea Mon Sep 17 00:00:00 2001 From: aus Date: Sat, 23 Feb 2019 15:09:25 -0600 Subject: [PATCH 06/16] rework to support remote R:socks syntax --- client/client.go | 30 +++++++++++++----------------- main.go | 10 +--------- server/handler.go | 16 +--------------- share/remote.go | 5 ----- share/ssh.go | 15 +++++++++++++++ 5 files changed, 30 insertions(+), 46 deletions(-) diff --git a/client/client.go b/client/client.go index 9127092c..30f1211a 100644 --- a/client/client.go +++ b/client/client.go @@ -4,17 +4,14 @@ import ( "context" "fmt" "io" - "io/ioutil" "net" - "log" "net/http" "net/url" - "os" "regexp" "strings" "time" - socks5 "github.com/armon/go-socks5" + "github.com/armon/go-socks5" "github.com/gorilla/websocket" "github.com/jpillora/backoff" "github.com/jpillora/chisel/share" @@ -33,7 +30,6 @@ type Config struct { HTTPProxy string Remotes []string HostHeader string - Socks5 bool } //Client represents a client instance @@ -74,11 +70,15 @@ func NewClient(config *Config) (*Client, error) { //swap to websockets scheme u.Scheme = strings.Replace(u.Scheme, "http", "ws", 1) shared := &chshare.Config{} + createSocksServer := false for _, s := range config.Remotes { r, err := chshare.DecodeRemote(s) if err != nil { return nil, fmt.Errorf("Failed to decode remote '%s': %s", s, err) } + if r.Socks && r.Reverse { + createSocksServer = true + } shared.Remotes = append(shared.Remotes, r) } config.shared = shared @@ -108,18 +108,12 @@ func NewClient(config *Config) (*Client, error) { Timeout: 30 * time.Second, } - if config.Socks5 { + if createSocksServer { socksConfig := &socks5.Config{} - if client.Debug { - socksConfig.Logger = log.New(os.Stdout, "[client socks]", log.Ldate|log.Ltime) - } else { - socksConfig.Logger = log.New(ioutil.Discard, "", 0) - } client.socksServer, err = socks5.New(socksConfig) if err != nil { return nil, err } - client.Infof("client-side SOCKS5 server enabled") } return client, nil @@ -161,10 +155,6 @@ func (c *Client) Start(ctx context.Context) error { } } } - // start socks server - if c.socksServer != nil { - go c.socksServer.ListenAndServe("tcp", "127.0.0.1:1081") - } c.Infof("Connecting to %s%s\n", c.server, via) //optional keepalive loop if c.config.KeepAlive > 0 { @@ -296,6 +286,7 @@ func (c *Client) Close() error { func (c *Client) connectStreams(chans <-chan ssh.NewChannel) { for ch := range chans { remote := string(ch.ExtraData()) + socks := remote == "socks" stream, reqs, err := ch.Accept() if err != nil { c.Debugf("Failed to accept stream: %s", err) @@ -303,6 +294,11 @@ func (c *Client) connectStreams(chans <-chan ssh.NewChannel) { } go ssh.DiscardRequests(reqs) l := c.Logger.Fork("conn#%d", c.connStats.New()) - go chshare.HandleTCPStream(l, &c.connStats, stream, remote) + if socks { + go chshare.HandleSocksStream(l, c.socksServer, &c.connStats, stream) + } else { + go chshare.HandleTCPStream(l, &c.connStats, stream, remote) + } + } } diff --git a/main.go b/main.go index b6c02faf..570b508e 100644 --- a/main.go +++ b/main.go @@ -124,7 +124,7 @@ var serverHelp = ` chisel receives a normal HTTP request. Useful for hiding chisel in plain sight. - --socks5, Allow clients to access the server-outbound SOCKS5 proxy. See + --socks5, Allow clients to access the internal SOCKS5 proxy. See chisel client --help for more information. --reverse, Allow clients to specify reverse port forwarding remotes @@ -266,12 +266,6 @@ var clientHelp = ` --hostname, Optionally set the 'Host' header (defaults to the host found in the server url). - - --socks5, Start a client-outbound SOCKS5 server on 127.0.0.1:1081. - Combine this option with a reverse port forward remote to expose the - client network to the chisel server. For example, the following - remote R:127.0.0.1:5000:127.0.0.1:1081 would allow the sever to - access the client network via socks5://127.0.0.1:5000. ` + commonHelp func client(args []string) { @@ -286,7 +280,6 @@ func client(args []string) { proxy := flags.String("proxy", "", "") pid := flags.Bool("pid", false, "") hostname := flags.String("hostname", "", "") - socks5 := flags.Bool("socks5", false, "") verbose := flags.Bool("v", false, "") flags.Usage = func() { fmt.Print(clientHelp) @@ -311,7 +304,6 @@ func client(args []string) { Server: args[0], Remotes: args[1:], HostHeader: *hostname, - Socks5: *socks5, }) if err != nil { log.Fatal(err) diff --git a/server/handler.go b/server/handler.go index 290f5a6d..b45af566 100644 --- a/server/handler.go +++ b/server/handler.go @@ -2,7 +2,6 @@ package chserver import ( "context" - "io" "net/http" "strings" "sync/atomic" @@ -179,22 +178,9 @@ func (s *Server) handleSSHChannels(clientLog *chshare.Logger, chans <-chan ssh.N //handle stream type connID := s.connStats.New() if socks { - go s.handleSocksStream(clientLog.Fork("socksconn#%d", connID), stream) + go chshare.HandleSocksStream(clientLog.Fork("socksconn#%d", connID), s.socksServer, &s.connStats, stream) } else { go chshare.HandleTCPStream(clientLog.Fork("conn#%d", connID), &s.connStats, stream, remote) } } } - -func (s *Server) handleSocksStream(l *chshare.Logger, src io.ReadWriteCloser) { - conn := chshare.NewRWCConn(src) - s.connStats.Open() - l.Debugf("%s Opening", s.connStats) - err := s.socksServer.ServeConn(conn) - s.connStats.Close() - if err != nil && !strings.HasSuffix(err.Error(), "EOF") { - l.Debugf("%s: Closed (error: %s)", s.connStats, err) - } else { - l.Debugf("%s: Closed", s.connStats) - } -} diff --git a/share/remote.go b/share/remote.go index 3aad30e1..dce3a8e8 100644 --- a/share/remote.go +++ b/share/remote.go @@ -43,11 +43,6 @@ func DecodeRemote(s string) (*Remote, error) { p := parts[i] //last part "socks"? if i == len(parts)-1 && p == "socks" { - if reverse { - // TODO allow reverse+socks by having client - // automatically start local SOCKS5 server - return nil, errors.New("'socks' incompatible with reverse port forwarding") - } r.Socks = true continue } diff --git a/share/ssh.go b/share/ssh.go index 610c59bf..c31d0ce5 100644 --- a/share/ssh.go +++ b/share/ssh.go @@ -12,6 +12,7 @@ import ( "net" "strings" + "github.com/armon/go-socks5" "github.com/jpillora/sizestr" "golang.org/x/crypto/ssh" ) @@ -56,3 +57,17 @@ func HandleTCPStream(l *Logger, connStats *ConnStats, src io.ReadWriteCloser, re connStats.Close() l.Debugf("%s: Close (sent %s received %s)", connStats, sizestr.ToString(s), sizestr.ToString(r)) } + +func HandleSocksStream(l *Logger, server *socks5.Server, connStats *ConnStats, src io.ReadWriteCloser) { + conn := NewRWCConn(src) + connStats.Open() + l.Debugf("%s Opening", connStats) + err := server.ServeConn(conn) + connStats.Close() + + if err != nil && !strings.HasSuffix(err.Error(), "EOF") { + l.Debugf("%s: Closed (error: %s)", connStats, err) + } else { + l.Debugf("%s: Closed", connStats) + } +} From cac0114b949a2798249c2cded17f4d5790b4a31f Mon Sep 17 00:00:00 2001 From: aus Date: Sat, 23 Feb 2019 22:52:32 -0600 Subject: [PATCH 07/16] update usage for R:socks info --- main.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main.go b/main.go index 570b508e..990e34d5 100644 --- a/main.go +++ b/main.go @@ -224,6 +224,8 @@ var clientHelp = ` socks 5000:socks R:2222:localhost:22 + R:socks + R:5000:socks When the chisel server has --socks5 enabled, remotes can specify "socks" in place of remote-host and remote-port. @@ -235,6 +237,8 @@ var clientHelp = ` be prefixed with R to denote that they are reversed. That is, the server will listen and accept connections, and they will be proxied through the client which specified the remote. + Reverse remotes specifying "R:socks" will terminate a connection + at the client's internal SOCKS5 proxy. Options: From 0791425d444e63f9f53442fd7938a263f22ac1f8 Mon Sep 17 00:00:00 2001 From: aus Date: Sun, 24 Feb 2019 17:47:24 -0600 Subject: [PATCH 08/16] add nil check for c.socksServer --- client/client.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/client.go b/client/client.go index 30f1211a..34df064b 100644 --- a/client/client.go +++ b/client/client.go @@ -287,6 +287,11 @@ func (c *Client) connectStreams(chans <-chan ssh.NewChannel) { for ch := range chans { remote := string(ch.ExtraData()) socks := remote == "socks" + if socks && c.socksServer == nil { + c.Debugf("Denied socks request, please enable client socks remote.") + ch.Reject(ssh.Prohibited, "SOCKS5 is not enabled on the client") + continue + } stream, reqs, err := ch.Accept() if err != nil { c.Debugf("Failed to accept stream: %s", err) From 078007ee1360db214a64becccdfd92a442cfc942 Mon Sep 17 00:00:00 2001 From: Akeem McLennon Date: Wed, 27 Mar 2019 19:07:34 -0500 Subject: [PATCH 09/16] Add support for custom headers --- README.md | 3 +++ client/client.go | 12 +++------ client/client_test.go | 40 +++++++++++++++++++++++++++ go.mod | 1 + go.sum | 7 +++++ main.go | 63 ++++++++++++++++++++++++++++++++++++------- main_test.go | 37 +++++++++++++++++++++++++ 7 files changed, 145 insertions(+), 18 deletions(-) create mode 100644 client/client_test.go create mode 100644 main_test.go diff --git a/README.md b/README.md index 822451e4..cc095ed5 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,9 @@ $ chisel client --help the chisel server. Authentication can be specified inside the URL. For example, http://admin:password@my-server.com:8081 + --header, Set a custom header in the form "HeaderName: HeaderContent". + Can be used multiple times. (e.g --header "Foo: Bar" --header "Hello: World") + --hostname, Optionally set the 'Host' header (defaults to the host defined in the endpoint url). diff --git a/client/client.go b/client/client.go index e278e99d..1fc5d031 100644 --- a/client/client.go +++ b/client/client.go @@ -13,7 +13,7 @@ import ( "github.com/gorilla/websocket" "github.com/jpillora/backoff" - "github.com/jpillora/chisel/share" + chshare "github.com/jpillora/chisel/share" "golang.org/x/crypto/ssh" ) @@ -28,7 +28,7 @@ type Config struct { Server string HTTPProxy string Remotes []string - HostHeader string + Headers http.Header } //Client represents a client instance @@ -199,13 +199,7 @@ func (c *Client) connectionLoop() { return c.httpProxyURL, nil } } - wsHeaders := http.Header{} - if c.config.HostHeader != "" { - wsHeaders = http.Header{ - "Host": {c.config.HostHeader}, - } - } - wsConn, _, err := d.Dial(c.server, wsHeaders) + wsConn, _, err := d.Dial(c.server, c.config.Headers) if err != nil { connerr = err continue diff --git a/client/client_test.go b/client/client_test.go new file mode 100644 index 00000000..d58f8e4e --- /dev/null +++ b/client/client_test.go @@ -0,0 +1,40 @@ +package chclient + +import ( + "log" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestCustomHeaders(t *testing.T) { + assert := assert.New(t) + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + assert.Equal(req.Header.Get("Foo"), "Bar") + })) + // Close the server when test finishes + defer server.Close() + headers := http.Header{} + headers.Set("Foo", "Bar") + config := Config{ + Fingerprint: "", + Auth: "", + KeepAlive: time.Second, + MaxRetryCount: 0, + MaxRetryInterval: time.Second, + HTTPProxy: "", + Server: server.URL, + Remotes: []string{"socks"}, + Headers: headers, + } + c, err := NewClient(&config) + if err != nil { + log.Fatal(err) + } + if err = c.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/go.mod b/go.mod index 72fdfaba..364d16db 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7 github.com/jpillora/requestlog v0.0.0-20181015073026-df8817be5f82 github.com/jpillora/sizestr v0.0.0-20160130011556-e2ea2fa42fb9 + github.com/stretchr/testify v1.3.0 github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e golang.org/x/net v0.0.0-20181017193950-04a2e542c03f // indirect diff --git a/go.sum b/go.sum index e5008864..8b7e51e3 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 h1:axBiC50cNZ github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2/go.mod h1:jnzFpU88PccN/tPPhCpnNU8mZphvKxYM9lLNkd8e+os= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= @@ -14,6 +16,11 @@ github.com/jpillora/requestlog v0.0.0-20181015073026-df8817be5f82 h1:7ufdyC3aMxF github.com/jpillora/requestlog v0.0.0-20181015073026-df8817be5f82/go.mod h1:w8buj+yNfmLEP0ENlbG/FRnK6bVmuhqXnukYCs9sDvY= github.com/jpillora/sizestr v0.0.0-20160130011556-e2ea2fa42fb9 h1:0c9jcgBtHRtDU//jTrcCgWG6UHjMZytiq/3WhraNgUM= github.com/jpillora/sizestr v0.0.0-20160130011556-e2ea2fa42fb9/go.mod h1:1ffp+CRe0eAwwRb0/BownUAjMBsmTLwgAvRbfj9dRwE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e h1:IzypfodbhbnViNUO/MEh0FzCUooG97cIGfdggUrUSyU= diff --git a/main.go b/main.go index 570b508e..d83f869e 100644 --- a/main.go +++ b/main.go @@ -5,11 +5,13 @@ import ( "fmt" "io/ioutil" "log" + "net/http" "os" "strconv" + "strings" - "github.com/jpillora/chisel/client" - "github.com/jpillora/chisel/server" + chclient "github.com/jpillora/chisel/client" + chserver "github.com/jpillora/chisel/server" chshare "github.com/jpillora/chisel/share" ) @@ -192,6 +194,32 @@ func server(args []string) { } } +type headerFlags struct { + http.Header +} + +func (flag *headerFlags) String() string { + out := "" + for k, v := range flag.Header { + out += fmt.Sprintf("%s: %s\n", k, v) + } + return out +} + +func (flag *headerFlags) Set(arg string) error { + index := strings.Index(arg, ":") + if index < 0 { + return fmt.Errorf(`Invalid header (%s). Should be in the format "HeaderName: HeaderContent"`, arg) + } + if flag.Header == nil { + flag.Header = http.Header{} + } + key := arg[0:index] + value := arg[index+1 : len(arg)] + flag.Header.Set(key, strings.TrimSpace(value)) + return nil +} + var clientHelp = ` Usage: chisel client [options] [remote] [remote] ... @@ -263,13 +291,15 @@ var clientHelp = ` --proxy, An optional HTTP CONNECT proxy which will be used reach the chisel server. Authentication can be specified inside the URL. For example, http://admin:password@my-server.com:8081 + + --header, Set a custom header in the form "HeaderName: HeaderContent". + Can be used multiple times. (e.g --header "Foo: Bar" --header "Hello: World") --hostname, Optionally set the 'Host' header (defaults to the host found in the server url). ` + commonHelp -func client(args []string) { - +func parseClientFlags(args []string) (config chclient.Config, pid *bool, verbose *bool) { flags := flag.NewFlagSet("client", flag.ContinueOnError) fingerprint := flags.String("fingerprint", "", "") @@ -278,9 +308,13 @@ func client(args []string) { maxRetryCount := flags.Int("max-retry-count", -1, "") maxRetryInterval := flags.Duration("max-retry-interval", 0, "") proxy := flags.String("proxy", "", "") - pid := flags.Bool("pid", false, "") + pid = flags.Bool("pid", false, "") hostname := flags.String("hostname", "", "") - verbose := flags.Bool("v", false, "") + headers := headerFlags{ + Header: http.Header{}, + } + flags.Var(&headers, "header", "") + verbose = flags.Bool("v", false, "") flags.Usage = func() { fmt.Print(clientHelp) os.Exit(1) @@ -294,7 +328,11 @@ func client(args []string) { if *auth == "" { *auth = os.Getenv("AUTH") } - c, err := chclient.NewClient(&chclient.Config{ + hostHeader := *hostname + if hostHeader != "" { + headers.Header.Set("Host", hostHeader) + } + config = chclient.Config{ Fingerprint: *fingerprint, Auth: *auth, KeepAlive: *keepalive, @@ -303,8 +341,15 @@ func client(args []string) { HTTPProxy: *proxy, Server: args[0], Remotes: args[1:], - HostHeader: *hostname, - }) + Headers: headers.Header, + } + return +} + +func client(args []string) { + config, pid, verbose := parseClientFlags(args) + c, err := chclient.NewClient(&config) + if err != nil { log.Fatal(err) } diff --git a/main_test.go b/main_test.go new file mode 100644 index 00000000..f57e4fdf --- /dev/null +++ b/main_test.go @@ -0,0 +1,37 @@ +package main + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestParseClientFlag(t *testing.T) { + assert := assert.New(t) + args := []string{ + "-fingerprint", "FINGERPRINT", + "-auth", "AUTH-VALUE", + "-hostname", "HOSTNAME", + "-keepalive", "30s", + "-header", "Header1: Foo", + "-header", "Header2: Bar", + "-max-retry-count", "2", + "-max-retry-interval", "12s", + "SERVER", + "REMOTE", + } + config, pid, verbose := parseClientFlags(args) + assert.Equal(config.Fingerprint, "FINGERPRINT") + assert.Equal(config.Headers.Get("Header1"), "Foo") + assert.Equal(config.Headers.Get("Header2"), "Bar") + assert.Equal(config.Headers.Get("Host"), "HOSTNAME") + assert.Equal(config.KeepAlive, 30*time.Second) + assert.Equal(config.MaxRetryInterval, 12*time.Second) + assert.Equal(config.MaxRetryCount, 2) + assert.Equal(config.Auth, "AUTH-VALUE") + assert.Equal(config.Server, "SERVER") + assert.Equal(config.Remotes, []string{"REMOTE"}) + assert.Equal(*pid, false) + assert.Equal(*verbose, false) +} From fc1a0c0622a065d6806c1e4ff8a67e6b66f53e9e Mon Sep 17 00:00:00 2001 From: Meteorite Date: Tue, 27 Aug 2019 22:01:56 +0300 Subject: [PATCH 10/16] say explicitly that only socks5h:// socks proxy variant is supported, and socks:// defaults to socks5h:// --- client/client.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/client.go b/client/client.go index ddd99cfe..e1decd4f 100644 --- a/client/client.go +++ b/client/client.go @@ -198,6 +198,12 @@ func (c *Client) connectionLoop() { if c.proxyURL != nil { if strings.HasPrefix(c.proxyURL.Scheme, "socks") { // SOCKS5 proxy + if c.proxyURL.Scheme != "socks" && c.proxyURL.Scheme != "socks5h" { + c.Infof( + "unsupported socks proxy type: %s:// (only socks5h:// or socks:// is supported)", + c.proxyURL.Scheme) + break + } var auth *proxy.Auth = nil if c.proxyURL.User != nil { pass, _ := c.proxyURL.User.Password() From c9b6bf910c416687bbc829aae7d7e22b721f8015 Mon Sep 17 00:00:00 2001 From: aus <549081+aus@users.noreply.github.com> Date: Wed, 28 Aug 2019 11:44:25 -0500 Subject: [PATCH 11/16] change import url to aus/chisel --- Dockerfile | 2 +- client/client.go | 2 +- go.mod | 2 +- main.go | 6 +++--- server/handler.go | 2 +- server/server.go | 2 +- test/main.go | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 668c20c9..5aa90cb8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ADD . /src WORKDIR /src RUN go build \ -mod vendor \ - -ldflags "-X github.com/jpillora/chisel/share.BuildVersion=$(git describe --abbrev=0 --tags)" \ + -ldflags "-X github.com/aus/chisel/share.BuildVersion=$(git describe --abbrev=0 --tags)" \ -o chisel # container stage FROM alpine diff --git a/client/client.go b/client/client.go index e278e99d..f967757c 100644 --- a/client/client.go +++ b/client/client.go @@ -13,7 +13,7 @@ import ( "github.com/gorilla/websocket" "github.com/jpillora/backoff" - "github.com/jpillora/chisel/share" + "github.com/aus/chisel/share" "golang.org/x/crypto/ssh" ) diff --git a/go.mod b/go.mod index 72fdfaba..f324278f 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/jpillora/chisel +module github.com/aus/chisel require ( github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect diff --git a/main.go b/main.go index 570b508e..bfb3fdf5 100644 --- a/main.go +++ b/main.go @@ -8,9 +8,9 @@ import ( "os" "strconv" - "github.com/jpillora/chisel/client" - "github.com/jpillora/chisel/server" - chshare "github.com/jpillora/chisel/share" + "github.com/aus/chisel/client" + "github.com/aus/chisel/server" + chshare "github.com/aus/chisel/share" ) var help = ` diff --git a/server/handler.go b/server/handler.go index 290f5a6d..5f8f8800 100644 --- a/server/handler.go +++ b/server/handler.go @@ -10,7 +10,7 @@ import ( "golang.org/x/crypto/ssh" - "github.com/jpillora/chisel/share" + "github.com/aus/chisel/share" ) // handleClientHandler is the main http websocket handler for the chisel server diff --git a/server/server.go b/server/server.go index 4c68eb1a..ba44cb6d 100644 --- a/server/server.go +++ b/server/server.go @@ -15,7 +15,7 @@ import ( "github.com/jpillora/requestlog" "golang.org/x/crypto/ssh" - "github.com/jpillora/chisel/share" + "github.com/aus/chisel/share" ) // Config is the configuration for the chisel service diff --git a/test/main.go b/test/main.go index 30a867a9..e73e7035 100644 --- a/test/main.go +++ b/test/main.go @@ -25,7 +25,7 @@ import ( "path" "strconv" - "github.com/jpillora/chisel/share" + "github.com/aus/chisel/share" "time" ) From c95714667e4e0272ab24bac631f515475abc9758 Mon Sep 17 00:00:00 2001 From: aus <549081+aus@users.noreply.github.com> Date: Wed, 28 Aug 2019 12:01:32 -0500 Subject: [PATCH 12/16] fix Client struct --- client/client.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/client.go b/client/client.go index abf45026..ea567282 100644 --- a/client/client.go +++ b/client/client.go @@ -36,15 +36,15 @@ type Config struct { //Client represents a client instance type Client struct { *chshare.Logger - config *Config - sshConfig *ssh.ClientConfig - sshConn ssh.Conn - httpProxyURL *url.URL - server string - running bool - runningc chan error - connStats chshare.ConnStats - socksServer *socks5.Server + config *Config + sshConfig *ssh.ClientConfig + sshConn ssh.Conn + proxyURL *url.URL + server string + running bool + runningc chan error + connStats chshare.ConnStats + socksServer *socks5.Server } //NewClient creates a new client instance From fc9f50a1a6ba5103bd3cd0929dcc65b223645638 Mon Sep 17 00:00:00 2001 From: aus <549081+aus@users.noreply.github.com> Date: Wed, 28 Aug 2019 12:15:23 -0500 Subject: [PATCH 13/16] expose NetDialContext --- client/client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/client.go b/client/client.go index ea567282..dbcd1be4 100644 --- a/client/client.go +++ b/client/client.go @@ -31,6 +31,7 @@ type Config struct { Proxy string Remotes []string HostHeader string + DialContext func(ctx context.Context, network, addr string) (net.Conn, error) } //Client represents a client instance @@ -207,6 +208,7 @@ func (c *Client) connectionLoop() { WriteBufferSize: 1024, HandshakeTimeout: 45 * time.Second, Subprotocols: []string{chshare.ProtocolVersion}, + NetDialContext: c.config.DialContext, } //optionally proxy if c.proxyURL != nil { From 3f2899c7894d05126a0b3a57a291b934d0fa4304 Mon Sep 17 00:00:00 2001 From: aus <549081+aus@users.noreply.github.com> Date: Wed, 28 Aug 2019 17:04:35 -0500 Subject: [PATCH 14/16] derp --- client/client.go | 4 ---- main.go | 10 ++-------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/client/client.go b/client/client.go index b68dbf12..3d336b65 100644 --- a/client/client.go +++ b/client/client.go @@ -15,10 +15,6 @@ import ( chshare "github.com/aus/chisel/share" "github.com/gorilla/websocket" "github.com/jpillora/backoff" -<<<<<<< HEAD -======= - chshare "github.com/jpillora/chisel/share" ->>>>>>> butterflyfx/custom-headers "golang.org/x/crypto/ssh" "golang.org/x/net/proxy" ) diff --git a/main.go b/main.go index c13cdc60..3c4681aa 100644 --- a/main.go +++ b/main.go @@ -10,15 +10,9 @@ import ( "strconv" "strings" -<<<<<<< HEAD - "github.com/aus/chisel/client" - "github.com/aus/chisel/server" + chclient "github.com/aus/chisel/client" + chserver "github.com/aus/chisel/server" chshare "github.com/aus/chisel/share" -======= - chclient "github.com/jpillora/chisel/client" - chserver "github.com/jpillora/chisel/server" - chshare "github.com/jpillora/chisel/share" ->>>>>>> butterflyfx/custom-headers ) var help = ` From 4a52b79310d2fb496164b7ea5f74974735e463f3 Mon Sep 17 00:00:00 2001 From: aus <549081+aus@users.noreply.github.com> Date: Tue, 29 Oct 2019 14:30:52 -0500 Subject: [PATCH 15/16] enable real ip logging --- server/server.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index ba44cb6d..ef62750f 100644 --- a/server/server.go +++ b/server/server.go @@ -145,7 +145,9 @@ func (s *Server) Start(host, port string) error { s.Infof("Listening on %s:%s...", host, port) h := http.Handler(http.HandlerFunc(s.handleClientHandler)) if s.Debug { - h = requestlog.Wrap(h) + o := requestlog.DefaultOptions + o.TrustProxy = true + h = requestlog.WrapWith(h, o) } return s.httpServer.GoListenAndServe(host+":"+port, h) } From f360e4627421bf9c40dade5663957e3e21449562 Mon Sep 17 00:00:00 2001 From: aus <549081+aus@users.noreply.github.com> Date: Tue, 29 Oct 2019 16:18:22 -0500 Subject: [PATCH 16/16] Delete main_test.go --- main_test.go | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 main_test.go diff --git a/main_test.go b/main_test.go deleted file mode 100644 index f57e4fdf..00000000 --- a/main_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestParseClientFlag(t *testing.T) { - assert := assert.New(t) - args := []string{ - "-fingerprint", "FINGERPRINT", - "-auth", "AUTH-VALUE", - "-hostname", "HOSTNAME", - "-keepalive", "30s", - "-header", "Header1: Foo", - "-header", "Header2: Bar", - "-max-retry-count", "2", - "-max-retry-interval", "12s", - "SERVER", - "REMOTE", - } - config, pid, verbose := parseClientFlags(args) - assert.Equal(config.Fingerprint, "FINGERPRINT") - assert.Equal(config.Headers.Get("Header1"), "Foo") - assert.Equal(config.Headers.Get("Header2"), "Bar") - assert.Equal(config.Headers.Get("Host"), "HOSTNAME") - assert.Equal(config.KeepAlive, 30*time.Second) - assert.Equal(config.MaxRetryInterval, 12*time.Second) - assert.Equal(config.MaxRetryCount, 2) - assert.Equal(config.Auth, "AUTH-VALUE") - assert.Equal(config.Server, "SERVER") - assert.Equal(config.Remotes, []string{"REMOTE"}) - assert.Equal(*pid, false) - assert.Equal(*verbose, false) -}