Skip to content

Commit

Permalink
merge client reverse+socks PR by @aus (closes #78)
Browse files Browse the repository at this point in the history
- @aus's branch also includes PR for
custom headers, thanks @AkeemMcLennon  (closes #90)
- support client connections via socks
- add more architectures to the built releases
- remove vendor (go.sum will enforce correct deps)
  • Loading branch information
jpillora committed May 20, 2020
2 parents d9c45c6 + f360e46 commit 68050d0
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 103 deletions.
11 changes: 11 additions & 0 deletions .github/goreleaser.yml
Expand Up @@ -12,6 +12,17 @@ builds:
- amd64
- arm
- arm64
- ppc64
- ppc64le
- mips
- mipsle
- mips64
- mips64le
- mips64p32
- mips64p32le
- ppc
- s390
- s390x
goarm:
- 6
- 7
Expand Down
9 changes: 7 additions & 2 deletions README.md
Expand Up @@ -212,9 +212,14 @@ $ 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
--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).
Expand Down
99 changes: 73 additions & 26 deletions client/client.go
Expand Up @@ -11,10 +11,13 @@ import (
"strings"
"time"

"github.com/armon/go-socks5"
"github.com/gorilla/websocket"
"github.com/jpillora/backoff"
chshare "github.com/jpillora/chisel/share"

"golang.org/x/crypto/ssh"
"golang.org/x/net/proxy"
)

//Config represents a client configuration
Expand All @@ -26,22 +29,24 @@ type Config struct {
MaxRetryCount int
MaxRetryInterval time.Duration
Server string
HTTPProxy string
Proxy string
Remotes []string
HostHeader string
Headers http.Header
DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
}

//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
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
Expand All @@ -68,11 +73,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
Expand All @@ -85,8 +94,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)
}
Expand All @@ -102,6 +111,14 @@ func NewClient(config *Config) (*Client, error) {
Timeout: 30 * time.Second,
}

if createSocksServer {
socksConfig := &socks5.Config{}
client.socksServer, err = socks5.New(socksConfig)
if err != nil {
return nil, err
}
}

return client, nil
}

Expand Down Expand Up @@ -129,8 +146,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 {
Expand Down Expand Up @@ -192,20 +209,40 @@ func (c *Client) connectionLoop() {
WriteBufferSize: 1024,
HandshakeTimeout: 45 * time.Second,
Subprotocols: []string{chshare.ProtocolVersion},
NetDialContext: c.config.DialContext,
}
//optionally CONNECT proxy
if c.httpProxyURL != nil {
d.Proxy = func(*http.Request) (*url.URL, error) {
return c.httpProxyURL, nil
}
}
wsHeaders := http.Header{}
if c.config.HostHeader != "" {
wsHeaders = http.Header{
"Host": {c.config.HostHeader},
//optionally proxy
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()
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
}
}
}
wsConn, _, err := d.Dial(c.server, wsHeaders)
wsConn, _, err := d.Dial(c.server, c.config.Headers)
if err != nil {
connerr = err
continue
Expand Down Expand Up @@ -272,13 +309,23 @@ func (c *Client) Close() error {
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)
continue
}
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)
}
}
}
38 changes: 38 additions & 0 deletions client/client_test.go
@@ -0,0 +1,38 @@
package chclient

import (
"log"
"net/http"
"net/http/httptest"
"testing"
"time"
)

func TestCustomHeaders(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get("Foo") != "Bar" {
t.Fatal("expected header Foo to be '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,
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)
}
}
6 changes: 3 additions & 3 deletions go.mod
Expand Up @@ -12,7 +12,7 @@ require (
github.com/jpillora/requestlog v1.0.0
github.com/jpillora/sizestr v1.0.0
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect
)
30 changes: 6 additions & 24 deletions go.sum
Expand Up @@ -2,48 +2,30 @@ 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/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/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jpillora/ansi v0.0.0-20170202005112-f496b27cd669 h1:l5rH/CnVVu+HPxjtxjM90nHrm4nov3j3RF9/62UjgLs=
github.com/jpillora/ansi v0.0.0-20170202005112-f496b27cd669/go.mod h1:kOeLNvjNBGSV3uYtFjvb72+fnZCMFJF1XDvRIjdom0g=
github.com/jpillora/ansi v1.0.2 h1:+Ei5HCAH0xsrQRCT2PDr4mq9r4Gm4tg+arNdXRkB22s=
github.com/jpillora/ansi v1.0.2/go.mod h1:D2tT+6uzJvN1nBVQILYWkIdq7zG+b5gcFN5WI/VyjMY=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7 h1:K//n/AqR5HjG3qxbrBCL4vJPW0MVFSs9CPK1OOJdRME=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jpillora/requestlog v0.0.0-20181015073026-df8817be5f82 h1:7ufdyC3aMxFcCv+ABZy/dmIVGKFoGNBCqOgLYPIckD8=
github.com/jpillora/requestlog v0.0.0-20181015073026-df8817be5f82/go.mod h1:w8buj+yNfmLEP0ENlbG/FRnK6bVmuhqXnukYCs9sDvY=
github.com/jpillora/requestlog v1.0.0 h1:bg++eJ74T7DYL3DlIpiwknrtfdUA9oP/M4fL+PpqnyA=
github.com/jpillora/requestlog v1.0.0/go.mod h1:HTWQb7QfDc2jtHnWe2XEIEeJB7gJPnVdpNn52HXPvy8=
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/jpillora/sizestr v1.0.0 h1:4tr0FLxs1Mtq3TnsLDV+GYUWG7Q26a6s+tV5Zfw2ygw=
github.com/jpillora/sizestr v1.0.0/go.mod h1:bUhLv4ctkknatr6gR42qPxirmd5+ds1u7mzD+MZ33f0=
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=
golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20181017193950-04a2e542c03f h1:4pRM7zYwpBjCnfA1jRmhItLxYJkaEnsmuAcRtA347DA=
golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8 h1:R91KX5nmbbvEd7w370cbVzKC+EzCTGqZq63Zad5IcLM=
golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

0 comments on commit 68050d0

Please sign in to comment.