-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathclient_connect.go
134 lines (130 loc) · 3.37 KB
/
client_connect.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package chclient
import (
"context"
"errors"
"fmt"
"io"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/jpillora/backoff"
chshare "github.com/jpillora/chisel/share"
"github.com/jpillora/chisel/share/cnet"
"github.com/jpillora/chisel/share/cos"
"github.com/jpillora/chisel/share/settings"
"golang.org/x/crypto/ssh"
)
func (c *Client) connectionLoop(ctx context.Context) error {
//connection loop!
b := &backoff.Backoff{Max: c.config.MaxRetryInterval}
for {
connected, err := c.connectionOnce(ctx)
//reset backoff after successful connections
if connected {
b.Reset()
}
//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 {
msg := fmt.Sprintf("Connection error: %s", err)
if attempt > 0 {
maxAttemptVal := fmt.Sprint(maxAttempt)
if maxAttempt < 0 {
maxAttemptVal = "unlimited"
}
msg += fmt.Sprintf(" (Attempt: %d/%s)", attempt, maxAttemptVal)
}
c.Infof(msg)
}
//give up?
if maxAttempt >= 0 && attempt >= maxAttempt {
c.Infof("Give up")
break
}
d := b.Duration()
c.Infof("Retrying in %s...", d)
select {
case <-cos.AfterSignal(d):
continue //retry now
case <-ctx.Done():
c.Infof("Cancelled")
return nil
}
}
c.Close()
return nil
}
// connectionOnce connects to the chisel server and blocks
func (c *Client) connectionOnce(ctx context.Context) (connected bool, err error) {
//already closed?
select {
case <-ctx.Done():
return false, errors.New("Cancelled")
default:
//still open
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
//prepare dialer
d := websocket.Dialer{
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),
NetDialContext: c.config.DialContext,
}
//optional proxy
if p := c.proxyURL; p != nil {
if err := c.setProxy(p, &d); err != nil {
return false, err
}
}
wsConn, _, err := d.DialContext(ctx, c.server, c.config.Headers)
if err != nil {
return false, err
}
conn := cnet.NewWebSocketConn(wsConn)
// perform SSH handshake on net.Conn
c.Debugf("Handshaking...")
sshConn, chans, reqs, err := ssh.NewClientConn(conn, "", c.sshConfig)
if err != nil {
e := err.Error()
if strings.Contains(e, "unable to authenticate") {
c.Infof("Authentication failed")
c.Debugf(e)
} else {
c.Infof(e)
}
return false, err
}
defer sshConn.Close()
// chisel client handshake (reverse of server handshake)
// send configuration
c.Debugf("Sending config")
t0 := time.Now()
_, configerr, err := sshConn.SendRequest(
"config",
true,
settings.EncodeConfig(c.computed),
)
if err != nil {
c.Infof("Config verification failed")
return false, err
}
if len(configerr) > 0 {
return false, errors.New(string(configerr))
}
c.Infof("Connected (Latency %s)", time.Since(t0))
//connected, handover ssh connection for tunnel to use, and block
err = c.tunnel.BindSSH(ctx, sshConn, reqs, chans)
c.Infof("Disconnected")
connected = time.Since(t0) > 5*time.Second
return connected, err
}