document udp, log client connection failures, expose more settings via env-vars

This commit is contained in:
Jaime Pillora
2020-11-17 04:52:45 +11:00
parent f04afd2285
commit 6ddc09d281
12 changed files with 92 additions and 25 deletions
+5 -3
View File
@@ -2,7 +2,7 @@
[![GoDoc](https://godoc.org/github.com/jpillora/chisel?status.svg)](https://godoc.org/github.com/jpillora/chisel) [![CI](https://github.com/jpillora/chisel/workflows/CI/badge.svg)](https://github.com/jpillora/chisel/actions?workflow=CI)
Chisel is a fast TCP tunnel, transported over HTTP, secured via SSH. Single executable including both client and server. Written in Go (golang). Chisel is mainly useful for passing through firewalls, though it can also be used to provide a secure endpoint into your network.
Chisel is a fast TCP/UDP tunnel, transported over HTTP, secured via SSH. Single executable including both client and server. Written in Go (golang). Chisel is mainly useful for passing through firewalls, though it can also be used to provide a secure endpoint into your network.
![overview](https://docs.google.com/drawings/d/1p53VWxzGNfy8rjr-mW8pvisJmhkoLl82vAgctO_6f1w/pub?w=960&h=720)
@@ -193,17 +193,18 @@ $ chisel client --help
<remote>s are remote connections tunneled through the server, each of
which come in the form:
<local-host>:<local-port>:<remote-host>:<remote-port>
<local-host>:<local-port>:<remote-host>:<remote-port>/<protocol>
■ local-host defaults to 0.0.0.0 (all interfaces).
■ local-port defaults to remote-port.
■ remote-port is required*.
■ remote-host defaults to 0.0.0.0 (server localhost).
■ protocol defaults to tcp.
which shares <remote-host>:<remote-port> from the server to the client
as <local-host>:<local-port>, or:
R:<local-interface>:<local-port>:<remote-host>:<remote-port>
R:<local-interface>:<local-port>:<remote-host>:<remote-port>/<protocol>
which does reverse port forwarding, sharing <remote-host>:<remote-port>
from the client to the server's <local-interface>:<local-port>.
@@ -220,6 +221,7 @@ $ chisel client --help
R:socks
R:5000:socks
stdio:example.com:22
1.1.1.1:53/udp
When the chisel server has --socks5 enabled, remotes can
specify "socks" in place of remote-host and remote-port.
+1 -1
View File
@@ -172,7 +172,7 @@ func NewClient(c *Config) (*Client, error) {
Auth: []ssh.AuthMethod{ssh.Password(pass)},
ClientVersion: "SSH-" + chshare.ProtocolVersion + "-client",
HostKeyCallback: client.verifyServer,
Timeout: 30 * time.Second,
Timeout: settings.EnvDuration("SSH_TIMEOUT", 30*time.Second),
}
//prepare client tunnel
client.tunnel = tunnel.New(tunnel.Config{
+11 -4
View File
@@ -30,8 +30,12 @@ func (c *Client) connectionLoop(ctx context.Context) error {
//connection error
attempt := int(b.Attempt())
maxAttempt := c.config.MaxRetryCount
//dont print closed-connection errors
if strings.HasSuffix(err.Error(), "use of closed network connection") {
err = io.EOF
}
//show error message and attempt counts (excluding disconnects)
if err != nil && err != io.EOF && !strings.HasSuffix(err.Error(), "use of closed network connection") {
if err != nil && err != io.EOF {
msg := fmt.Sprintf("Connection error: %s", err)
if attempt > 0 {
msg += fmt.Sprintf(" (Attempt: %d", attempt)
@@ -40,10 +44,11 @@ func (c *Client) connectionLoop(ctx context.Context) error {
}
msg += ")"
}
c.Debugf(msg)
c.Infof(msg)
}
//give up?
if !retry || (maxAttempt >= 0 && attempt >= maxAttempt) {
c.Infof("Give up")
break
}
d := b.Duration()
@@ -65,7 +70,7 @@ func (c *Client) connectionOnce(ctx context.Context) (connected, retry bool, err
//already closed?
select {
case <-ctx.Done():
return false, false, ctx.Err()
return false, false, errors.New("Cancelled")
default:
//still open
}
@@ -73,9 +78,11 @@ func (c *Client) connectionOnce(ctx context.Context) (connected, retry bool, err
defer cancel()
//prepare dialer
d := websocket.Dialer{
HandshakeTimeout: 45 * time.Second,
HandshakeTimeout: settings.EnvDuration("WS_TIMEOUT", 45*time.Second),
Subprotocols: []string{chshare.ProtocolVersion},
TLSClientConfig: c.tlsConfig,
ReadBufferSize: settings.EnvInt("WS_BUFF_SIZE", 0),
WriteBufferSize: settings.EnvInt("WS_BUFF_SIZE", 0),
}
//optional proxy
if p := c.proxyURL; p != nil {
+4 -2
View File
@@ -277,17 +277,18 @@ var clientHelp = `
<remote>s are remote connections tunneled through the server, each of
which come in the form:
<local-host>:<local-port>:<remote-host>:<remote-port>
<local-host>:<local-port>:<remote-host>:<remote-port>/<protocol>
■ local-host defaults to 0.0.0.0 (all interfaces).
■ local-port defaults to remote-port.
■ remote-port is required*.
■ remote-host defaults to 0.0.0.0 (server localhost).
■ protocol defaults to tcp.
which shares <remote-host>:<remote-port> from the server to the client
as <local-host>:<local-port>, or:
R:<local-interface>:<local-port>:<remote-host>:<remote-port>
R:<local-interface>:<local-port>:<remote-host>:<remote-port>/<protocol>
which does reverse port forwarding, sharing <remote-host>:<remote-port>
from the client to the server's <local-interface>:<local-port>.
@@ -304,6 +305,7 @@ var clientHelp = `
R:socks
R:5000:socks
stdio:example.com:22
1.1.1.1:53/udp
When the chisel server has --socks5 enabled, remotes can
specify "socks" in place of remote-host and remote-port.
+3 -1
View File
@@ -46,7 +46,9 @@ type Server struct {
}
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
CheckOrigin: func(r *http.Request) bool { return true },
ReadBufferSize: settings.EnvInt("WS_BUFF_SIZE", 0),
WriteBufferSize: settings.EnvInt("WS_BUFF_SIZE", 0),
}
// NewServer creates and returns a new chisel server
+1 -1
View File
@@ -82,7 +82,7 @@ func (s *Server) handleWebsocket(w http.ResponseWriter, req *http.Request) {
var r *ssh.Request
select {
case r = <-reqs:
case <-time.After(10 * time.Second):
case <-time.After(settings.EnvDuration("CONFIG_TIMEOUT", 10*time.Second)):
l.Debugf("Timeout waiting for configuration")
sshConn.Close()
return
+3 -2
View File
@@ -10,6 +10,7 @@ import (
"os/user"
"path/filepath"
"github.com/jpillora/chisel/share/settings"
"golang.org/x/crypto/acme/autocert"
)
@@ -63,11 +64,11 @@ func (s *Server) tlsLetsEncrypt(domains []string) *tls.Config {
s.Infof("Accepting LetsEncrypt TOS and fetching certificate...")
return true
},
Email: os.Getenv("CHISEL_LE_EMAIL"),
Email: settings.Env("LE_EMAIL"),
HostPolicy: autocert.HostWhitelist(domains...),
}
//configure file cache
c := os.Getenv("CHISEL_LE_CACHE")
c := settings.Env("LE_CACHE")
if c == "" {
h := os.Getenv("HOME")
if h == "" {
+16 -7
View File
@@ -5,6 +5,7 @@ import (
"errors"
"net"
"net/http"
"sync"
"golang.org/x/sync/errgroup"
)
@@ -13,7 +14,7 @@ import (
//adds graceful shutdowns
type HTTPServer struct {
*http.Server
serving bool
waiterMux sync.Mutex
waiter *errgroup.Group
listenErr error
}
@@ -21,8 +22,7 @@ type HTTPServer struct {
//NewHTTPServer creates a new HTTPServer
func NewHTTPServer() *HTTPServer {
return &HTTPServer{
Server: &http.Server{},
serving: false,
Server: &http.Server{},
}
}
@@ -46,8 +46,9 @@ func (h *HTTPServer) GoServe(ctx context.Context, l net.Listener, handler http.H
if ctx == nil {
return errors.New("ctx must be set")
}
h.waiterMux.Lock()
defer h.waiterMux.Unlock()
h.Handler = handler
h.serving = true
h.waiter, ctx = errgroup.WithContext(ctx)
h.waiter.Go(func() error {
return h.Serve(l)
@@ -60,17 +61,25 @@ func (h *HTTPServer) GoServe(ctx context.Context, l net.Listener, handler http.H
}
func (h *HTTPServer) Close() error {
if !h.serving {
h.waiterMux.Lock()
defer h.waiterMux.Unlock()
if h.waiter == nil {
return errors.New("not started yet")
}
return h.Server.Close()
}
func (h *HTTPServer) Wait() error {
if !h.serving {
h.waiterMux.Lock()
unset := h.waiter == nil
h.waiterMux.Unlock()
if unset {
return errors.New("not started yet")
}
err := h.waiter.Wait()
h.waiterMux.Lock()
wait := h.waiter.Wait
h.waiterMux.Unlock()
err := wait()
if err == http.ErrServerClosed {
err = nil //success
}
+16
View File
@@ -0,0 +1,16 @@
// +build pprof
package cos
import (
"log"
"net/http"
_ "net/http/pprof" //import http profiler api
)
func init() {
go func() {
log.Fatal(http.ListenAndServe("localhost:6060", nil))
}()
log.Printf("[pprof] listening on 6060")
}
+28
View File
@@ -0,0 +1,28 @@
package settings
import (
"os"
"strconv"
"time"
)
//Env returns a chisel environment variable
func Env(name string) string {
return os.Getenv("CHISEL_" + name)
}
//EnvInt returns an integer using an environment variable, with a default fallback
func EnvInt(name string, def int) int {
if n, err := strconv.Atoi(Env(name)); err == nil {
return n
}
return def
}
//EnvDuration returns a duration using an environment variable, with a default fallback
func EnvDuration(name string, def time.Duration) time.Duration {
if n, err := time.ParseDuration(Env(name)); err == nil {
return n
}
return def
}
+2 -2
View File
@@ -121,8 +121,8 @@ func (t *Tunnel) getSSH(ctx context.Context) ssh.Conn {
select {
case <-ctx.Done(): //cancelled
return nil
case <-time.After(35 * time.Second): //a bit longer than ssh timeout
return nil
case <-time.After(settings.EnvDuration("SSH_WAIT", 35*time.Second)):
return nil //a bit longer than ssh timeout
case <-t.activatingConnWait():
t.activeConnMut.RLock()
c := t.activeConn
+2 -2
View File
@@ -9,6 +9,7 @@ import (
"time"
"github.com/jpillora/chisel/share/cio"
"github.com/jpillora/chisel/share/settings"
)
func (t *Tunnel) handleUDP(l *cio.Logger, rwc io.ReadWriteCloser, hostPort string) error {
@@ -80,8 +81,7 @@ func (h *udpHandler) handleRead(p *udpPacket, conn *udpConn) {
buff := make([]byte, maxMTU)
for {
//response must arrive within 15 seconds
//TODO configurable
const deadline = 15 * time.Second
deadline := settings.EnvDuration("UDP_DEADLINE", 15*time.Second)
conn.SetReadDeadline(time.Now().Add(deadline))
//read response
n, err := conn.Read(buff)