mirror of
https://github.com/jpillora/chisel
synced 2026-06-08 15:07:02 +00:00
document udp, log client connection failures, expose more settings via env-vars
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://godoc.org/github.com/jpillora/chisel) [](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.
|
||||
|
||||

|
||||
|
||||
@@ -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
@@ -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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user