Skip to content

Commit 591f958

Browse files
committed
merge client stdio support (closes jpillora#166 thanks @BoleynSu!)
2 parents 5271d7b + a16c592 commit 591f958

File tree

7 files changed

+85
-7
lines changed

7 files changed

+85
-7
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Chisel is a fast TCP tunnel, transported over HTTP, secured via SSH. Single exec
1919
- Server optionally doubles as a [reverse proxy](http://golang.org/pkg/net/http/httputil/#NewSingleHostReverseProxy)
2020
- Server optionally allows [SOCKS5](https://en.wikipedia.org/wiki/SOCKS) connections (See [guide below](#socks5-guide))
2121
- Clients optionally allow [SOCKS5](https://en.wikipedia.org/wiki/SOCKS) connections from a reversed port forward
22+
- Client connections over stdio which supports `ssh -o ProxyCommand` providing SSH over HTTP
2223

2324
### Install
2425

@@ -178,6 +179,7 @@ $ chisel client --help
178179
R:2222:localhost:22
179180
R:socks
180181
R:5000:socks
182+
stdio:example.com:22
181183
182184
When the chisel server has --socks5 enabled, remotes can
183185
specify "socks" in place of remote-host and remote-port.
@@ -193,6 +195,13 @@ $ chisel client --help
193195
default socks port (1080) and terminate the connection at the
194196
client's internal SOCKS5 proxy.
195197
198+
When stdio is used as local-host, the tunnel will connect standard
199+
input/output of this program with the remote. This is useful when
200+
combined with ssh ProxyCommand. You can use
201+
ssh -o ProxyCommand='chisel client chiselserver stdio:%h:%p' \
202+
203+
to connect to an SSH server through the tunnel.
204+
196205
Options:
197206
198207
--fingerprint, A *strongly recommended* fingerprint string

client/client.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package chclient
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"io"
78
"net"
@@ -74,6 +75,7 @@ func NewClient(config *Config) (*Client, error) {
7475
u.Scheme = strings.Replace(u.Scheme, "http", "ws", 1)
7576
shared := &chshare.Config{}
7677
createSocksServer := false
78+
hasStdio := false
7779
for _, s := range config.Remotes {
7880
r, err := chshare.DecodeRemote(s)
7981
if err != nil {
@@ -82,6 +84,12 @@ func NewClient(config *Config) (*Client, error) {
8284
if r.Socks && r.Reverse {
8385
createSocksServer = true
8486
}
87+
if r.Stdio {
88+
if hasStdio {
89+
return nil, errors.New("Only one stdio is allowed")
90+
}
91+
hasStdio = true
92+
}
8593
shared.Remotes = append(shared.Remotes, r)
8694
}
8795
config.shared = shared

main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ var clientHelp = `
255255
R:2222:localhost:22
256256
R:socks
257257
R:5000:socks
258+
stdio:example.com:22
258259
259260
When the chisel server has --socks5 enabled, remotes can
260261
specify "socks" in place of remote-host and remote-port.
@@ -270,6 +271,13 @@ var clientHelp = `
270271
default socks port (1080) and terminate the connection at the
271272
client's internal SOCKS5 proxy.
272273
274+
When stdio is used as local-host, the tunnel will connect standard
275+
input/output of this program with the remote. This is useful when
276+
combined with ssh ProxyCommand. You can use
277+
ssh -o ProxyCommand='chisel client chiselserver stdio:%h:%p' \
278+
279+
to connect to an SSH server through the tunnel.
280+
273281
Options:
274282
275283
--fingerprint, A *strongly recommended* fingerprint string

share/logger.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"os"
77
)
88

9-
//Logger is ...
9+
//Logger is pkg/log Logger with prefixing and 2 log levels
1010
type Logger struct {
1111
prefix string
1212
logger *log.Logger
@@ -20,7 +20,7 @@ func NewLogger(prefix string) *Logger {
2020
func NewLoggerFlag(prefix string, flag int) *Logger {
2121
l := &Logger{
2222
prefix: prefix,
23-
logger: log.New(os.Stdout, "", flag),
23+
logger: log.New(os.Stderr, "", flag),
2424
Info: false,
2525
Debug: false,
2626
}

share/proxy.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,31 @@ func NewTCPProxy(logger *Logger, ssh GetSSHConn, index int, remote *Remote) *TCP
3131
}
3232

3333
func (p *TCPProxy) Start(ctx context.Context) error {
34+
if p.remote.Stdio {
35+
go p.listenStdio(ctx)
36+
return nil
37+
}
3438
l, err := net.Listen("tcp4", p.remote.LocalHost+":"+p.remote.LocalPort)
3539
if err != nil {
3640
return fmt.Errorf("%s: %s", p.Logger.Prefix(), err)
3741
}
38-
go p.listen(ctx, l)
42+
go p.listenNet(ctx, l)
3943
return nil
4044
}
4145

42-
func (p *TCPProxy) listen(ctx context.Context, l net.Listener) {
46+
func (p *TCPProxy) listenStdio(ctx context.Context) {
47+
for {
48+
p.accept(Stdio)
49+
select {
50+
case <-ctx.Done():
51+
return
52+
default:
53+
// the connection is not ready yet, keep waiting
54+
}
55+
}
56+
}
57+
58+
func (p *TCPProxy) listenNet(ctx context.Context, l net.Listener) {
4359
p.Infof("Listening")
4460
done := make(chan struct{})
4561
go func() {

share/remote.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,16 @@ import (
2020
// 192.168.0.1:3000:google.com:80 ->
2121
// local 192.168.0.1:3000
2222
// remote google.com:80
23+
// 127.0.0.1:1080:socks
24+
// local 127.0.0.1:1080
25+
// remote socks
26+
// stdio:example.com:22
27+
// local stdio
28+
// remote example.com:22
2329

2430
type Remote struct {
2531
LocalHost, LocalPort, RemoteHost, RemotePort string
26-
Socks, Reverse bool
32+
Socks, Reverse, Stdio bool
2733
}
2834

2935
const revPrefix = "R:"
@@ -41,11 +47,16 @@ func DecodeRemote(s string) (*Remote, error) {
4147
r := &Remote{Reverse: reverse}
4248
for i := len(parts) - 1; i >= 0; i-- {
4349
p := parts[i]
44-
//last part "socks"?
50+
//remote portion is socks?
4551
if i == len(parts)-1 && p == "socks" {
4652
r.Socks = true
4753
continue
4854
}
55+
//local portion is stdio?
56+
if i == 0 && p == "stdio" {
57+
r.Stdio = true
58+
continue
59+
}
4960
if isPort(p) {
5061
if !r.Socks && r.RemotePort == "" {
5162
r.RemotePort = p
@@ -80,6 +91,9 @@ func DecodeRemote(s string) (*Remote, error) {
8091
if !r.Socks && r.RemoteHost == "" {
8192
r.RemoteHost = "0.0.0.0"
8293
}
94+
if r.Stdio && r.Reverse {
95+
return nil, errors.New("stdio cannot be reversed")
96+
}
8397
return r, nil
8498
}
8599

@@ -106,7 +120,14 @@ func (r *Remote) String() string {
106120
if r.Reverse {
107121
tag = revPrefix
108122
}
109-
return tag + r.LocalHost + ":" + r.LocalPort + "=>" + r.Remote()
123+
return tag + r.Local() + "=>" + r.Remote()
124+
}
125+
126+
func (r *Remote) Local() string {
127+
if r.Stdio {
128+
return "stdio"
129+
}
130+
return r.LocalHost + ":" + r.LocalPort
110131
}
111132

112133
func (r *Remote) Remote() string {

share/stdio.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package chshare
2+
3+
import (
4+
"io"
5+
"io/ioutil"
6+
"os"
7+
)
8+
9+
//Stdio as a ReadWriteCloser
10+
var Stdio = &struct {
11+
io.ReadCloser
12+
io.Writer
13+
}{
14+
ioutil.NopCloser(os.Stdin),
15+
os.Stdout,
16+
}

0 commit comments

Comments
 (0)