-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
proxy.go
123 lines (110 loc) · 3.7 KB
/
proxy.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
// Copyright 2020 The Cockroach Authors.
//
// Licensed as a CockroachDB Enterprise file under the Cockroach Community
// License (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
package sqlproxyccl
import (
"io"
"net"
"github.com/cockroachdb/errors"
"github.com/jackc/pgproto3/v2"
)
const pgAcceptSSLRequest = 'S'
// See https://www.postgresql.org/docs/9.1/protocol-message-formats.html.
var pgSSLRequest = []int32{8, 80877103}
// sendErrToClientAndUpdateMetrics simply combines the update of the metrics and
// the transmission of the err back to the client.
func updateMetricsAndSendErrToClient(err error, conn net.Conn, metrics *metrics) {
metrics.updateForError(err)
SendErrToClient(conn, err)
}
func toPgError(err error) *pgproto3.ErrorResponse {
codeErr := (*codeError)(nil)
if errors.As(err, &codeErr) {
var msg string
switch codeErr.code {
// These are send as is.
case codeExpiredClientConnection,
codeBackendDown,
codeParamsRoutingFailed,
codeClientDisconnected,
codeBackendDisconnected,
codeAuthFailed,
codeProxyRefusedConnection,
codeIdleDisconnect:
msg = codeErr.Error()
// The rest - the message sent back is sanitized.
case codeUnexpectedInsecureStartupMessage:
msg = "server requires encryption"
}
var pgCode string
if codeErr.code == codeIdleDisconnect {
pgCode = "57P01" // admin shutdown
} else {
pgCode = "08004" // rejected connection
}
return &pgproto3.ErrorResponse{
Severity: "FATAL",
Code: pgCode,
Message: msg,
Hint: errors.FlattenHints(codeErr.err),
}
}
// Return a generic "internal server error" message.
return &pgproto3.ErrorResponse{
Severity: "FATAL",
Code: "08004", // rejected connection
Message: "internal server error",
}
}
// SendErrToClient will encode and pass back to the SQL client an error message.
// It can be called by the implementors of proxyHandler to give more
// information to the end user in case of a problem.
var SendErrToClient = func(conn net.Conn, err error) {
if err == nil || conn == nil {
return
}
_, _ = conn.Write(toPgError(err).Encode(nil))
}
// ConnectionCopy does a bi-directional copy between the backend and frontend
// connections. It terminates when one of connections terminate.
func ConnectionCopy(crdbConn, conn net.Conn) error {
errOutgoing := make(chan error, 1)
errIncoming := make(chan error, 1)
go func() {
_, err := io.Copy(crdbConn, conn)
errOutgoing <- err
}()
go func() {
_, err := io.Copy(conn, crdbConn)
errIncoming <- err
}()
select {
// NB: when using pgx, we see a nil errIncoming first on clean connection
// termination. Using psql I see a nil errOutgoing first. I think the PG
// protocol stipulates sending a message to the server at which point the
// server closes the connection (errIncoming), but presumably the client
// gets to close the connection once it's sent that message, meaning either
// case is possible.
case err := <-errIncoming:
if err == nil {
return nil
} else if codeErr := (*codeError)(nil); errors.As(err, &codeErr) &&
codeErr.code == codeExpiredClientConnection {
return codeErr
} else if ne := (net.Error)(nil); errors.As(err, &ne) && ne.Timeout() {
return newErrorf(codeIdleDisconnect, "terminating connection due to idle timeout: %v", err)
} else {
return newErrorf(codeBackendDisconnected, "copying from target server to client: %s", err)
}
case err := <-errOutgoing:
// The incoming connection got closed.
if err != nil {
return newErrorf(codeClientDisconnected, "copying from target server to client: %v", err)
}
return nil
}
}