Skip to content

Commit 648952f

Browse files
committed
handle ssh dial error
1 parent a26a0bc commit 648952f

4 files changed

Lines changed: 52 additions & 1 deletion

File tree

flow/alerting/classifier.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,17 @@ func GetErrorClass(ctx context.Context, err error) (ErrorClass, ErrorInfo) {
428428
}
429429
}
430430

431+
if sshDialErr, ok := errors.AsType[*exceptions.SSHTunnelDialError](err); ok {
432+
errInfo := ErrorInfo{
433+
Source: ErrorSourceSSH,
434+
Code: "TUNNEL_DIAL_ERROR",
435+
}
436+
if sshDialErr.Retryable {
437+
return ErrorRetryRecoverable, errInfo
438+
}
439+
return ErrorOther, errInfo
440+
}
441+
431442
// An SSH-tunneled connection that goes bad (e.g. keepalive failure) is wrapped explicitly.
432443
if _, ok := errors.AsType[*exceptions.SSHTunnelConnectionError](err); ok {
433444
return ErrorNotifyConnectivity, ErrorInfo{

flow/alerting/classifier_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,21 @@ func TestSSHTunnelConnectionErrorShouldBeConnectivity(t *testing.T) {
7272
}, errInfo, "Unexpected error info")
7373
}
7474

75+
func TestSSHTunnelRecoverableDialError(t *testing.T) {
76+
t.Parallel()
77+
78+
err := fmt.Errorf("failed to sync records: %w",
79+
fmt.Errorf("failed to get schema for watermark table xxx.xxx: %w",
80+
exceptions.NewMySQLExecuteError(exceptions.NewSSHTunnelDialError(
81+
errors.New("ssh: unexpected packet in response to channel open: <nil>")))))
82+
errorClass, errInfo := GetErrorClass(t.Context(), err)
83+
assert.Equal(t, ErrorRetryRecoverable, errorClass)
84+
assert.Equal(t, ErrorInfo{
85+
Source: ErrorSourceSSH,
86+
Code: "TUNNEL_DIAL_ERROR",
87+
}, errInfo)
88+
}
89+
7590
func TestNeonConnectivityErrorShouldBeConnectivity(t *testing.T) {
7691
t.Skip("Not a good idea to run this test in CI as it goes to Neon, maybe we need a better mock")
7792
config, err := pgx.ParseConfig("postgres://random-endpoint-id-here.us-east-2.aws.neon.tech:5432/db?options=endpoint%3Dtest_endpoint")

flow/connectors/utils/ssh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func (tunnel *SSHTunnel) IsBad() bool {
110110
func (tunnel *SSHTunnel) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
111111
conn, err := tunnel.Client.DialContext(ctx, network, address)
112112
if err != nil {
113-
return nil, err
113+
return nil, exceptions.NewSSHTunnelDialError(err)
114114
}
115115
return NewDeadlineCapableConn(conn), nil
116116
}

flow/shared/exceptions/ssh.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package exceptions
22

3+
import "strings"
4+
35
// SSHTunnelSetupError represents errors during SSH Tunnel Setup, the SSH library we use does not provide a common error type,
46
// just does fmt.Errorf
57
type SSHTunnelSetupError struct {
@@ -18,6 +20,29 @@ func (e *SSHTunnelSetupError) Unwrap() error {
1820
return e.error
1921
}
2022

23+
// SSHTunnelDialError represents a failure to open a new connection through an established SSH tunnel.
24+
type SSHTunnelDialError struct {
25+
error
26+
Retryable bool
27+
}
28+
29+
func NewSSHTunnelDialError(err error) *SSHTunnelDialError {
30+
// x/crypto/ssh's mux returns this untyped error when the SSH connection
31+
// is torn down while a channel open is in flight.
32+
if strings.Contains(err.Error(), "unexpected packet in response to channel open") {
33+
return &SSHTunnelDialError{err, true}
34+
}
35+
return &SSHTunnelDialError{err, false}
36+
}
37+
38+
func (e *SSHTunnelDialError) Error() string {
39+
return "SSH Tunnel Dial Error: " + e.error.Error()
40+
}
41+
42+
func (e *SSHTunnelDialError) Unwrap() error {
43+
return e.error
44+
}
45+
2146
// SSHTunnelConnectionError represents errors that surface on a connection tunneled over SSH after the
2247
// SSH tunnel has gone bad (e.g. keepalive failure causes us to close the underlying connections mid-CDC).
2348
type SSHTunnelConnectionError struct {

0 commit comments

Comments
 (0)