Skip to content

Commit 17fc073

Browse files
authored
Support proxy protocol v2 in MySQL (#12424)
#11684 added support for proxy protocol v2 for SSH and Postgres but MySQL uses different code path and it was missing. This change fixes that. It also adds tests for v2 protocol support for MySQL, Postgres, Mongo and Redis
1 parent 275a443 commit 17fc073

File tree

5 files changed

+119
-54
lines changed

5 files changed

+119
-54
lines changed

Diff for: lib/multiplexer/proxyline.go

+33
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,39 @@ func (p *ProxyLine) String() string {
6060
return fmt.Sprintf("PROXY %s %s %s %d %d\r\n", p.Protocol, p.Source.IP.String(), p.Destination.IP.String(), p.Source.Port, p.Destination.Port)
6161
}
6262

63+
// Bytes returns on-the wire bytes representation of proxy line conforming to the proxy v2 protocol
64+
func (p *ProxyLine) Bytes() []byte {
65+
b := &bytes.Buffer{}
66+
header := proxyV2Header{VersionCommand: (Version2 << 4) | ProxyCommand}
67+
copy(header.Signature[:], proxyV2Prefix)
68+
var addr interface{}
69+
switch p.Protocol {
70+
case TCP4:
71+
header.Protocol = ProtocolTCP4
72+
addr4 := proxyV2Address4{
73+
SourcePort: uint16(p.Source.Port),
74+
DestinationPort: uint16(p.Destination.Port),
75+
}
76+
copy(addr4.Source[:], p.Source.IP.To4())
77+
copy(addr4.Destination[:], p.Destination.IP.To4())
78+
addr = addr4
79+
case TCP6:
80+
header.Protocol = ProtocolTCP6
81+
addr6 := proxyV2Address6{
82+
SourcePort: uint16(p.Source.Port),
83+
DestinationPort: uint16(p.Destination.Port),
84+
}
85+
copy(addr6.Source[:], p.Source.IP.To16())
86+
copy(addr6.Destination[:], p.Destination.IP.To16())
87+
addr = addr6
88+
}
89+
header.Length = uint16(binary.Size(addr))
90+
binary.Write(b, binary.BigEndian, header)
91+
binary.Write(b, binary.BigEndian, addr)
92+
93+
return b.Bytes()
94+
}
95+
6396
// ReadProxyLine reads proxy line protocol from the reader
6497
func ReadProxyLine(reader *bufio.Reader) (*ProxyLine, error) {
6598
line, err := reader.ReadString('\n')

Diff for: lib/multiplexer/testproxy.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ type TestProxy struct {
3333
target string
3434
closeCh chan (struct{})
3535
log logrus.FieldLogger
36+
v2 bool
3637
}
3738

3839
// NewTestProxy creates a new test proxy that sends a proxy-line when
3940
// proxying connections to the provided target address.
40-
func NewTestProxy(target string) (*TestProxy, error) {
41+
func NewTestProxy(target string, v2 bool) (*TestProxy, error) {
4142
listener, err := net.Listen("tcp", "localhost:0")
4243
if err != nil {
4344
return nil, trace.Wrap(err)
@@ -47,6 +48,7 @@ func NewTestProxy(target string) (*TestProxy, error) {
4748
target: target,
4849
closeCh: make(chan struct{}),
4950
log: logrus.WithField(trace.Component, "test:proxy"),
51+
v2: v2,
5052
}, nil
5153
}
5254

@@ -128,7 +130,11 @@ func (p *TestProxy) sendProxyLine(clientConn, serverConn net.Conn) error {
128130
Destination: net.TCPAddr{IP: net.ParseIP(serverAddr.Host()), Port: serverAddr.Port(0)},
129131
}
130132
p.log.Debugf("Sending %v to %v.", proxyLine.String(), serverConn.RemoteAddr().String())
131-
_, err = serverConn.Write([]byte(proxyLine.String()))
133+
if p.v2 {
134+
_, err = serverConn.Write(proxyLine.Bytes())
135+
} else {
136+
_, err = serverConn.Write([]byte(proxyLine.String()))
137+
}
132138
if err != nil {
133139
return trace.Wrap(err)
134140
}

Diff for: lib/multiplexer/wrappers.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,16 @@ func (c *Conn) Detect() (Protocol, error) {
8484

8585
// ReadProxyLine reads proxy-line from the connection.
8686
func (c *Conn) ReadProxyLine() (*ProxyLine, error) {
87-
proxyLine, err := ReadProxyLine(c.reader)
87+
var proxyLine *ProxyLine
88+
protocol, err := c.Detect()
89+
if err != nil {
90+
return nil, trace.Wrap(err)
91+
}
92+
if protocol == ProtoProxyV2 {
93+
proxyLine, err = ReadProxyLineV2(c.reader)
94+
} else {
95+
proxyLine, err = ReadProxyLine(c.reader)
96+
}
8897
if err != nil {
8998
return nil, trace.Wrap(err)
9099
}

Diff for: lib/srv/db/mysql/proxy.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ func (p *Proxy) maybeReadProxyLine(conn *multiplexer.Conn) error {
212212
if err != nil {
213213
return trace.Wrap(err)
214214
}
215-
if proto != multiplexer.ProtoProxy {
215+
if proto != multiplexer.ProtoProxy && proto != multiplexer.ProtoProxyV2 {
216216
return nil
217217
}
218218
proxyLine, err := conn.ReadProxyLine()

Diff for: lib/srv/db/proxy_test.go

+67-50
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package db
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"testing"
2223
"time"
2324

@@ -40,17 +41,21 @@ func TestProxyProtocolPostgres(t *testing.T) {
4041

4142
testCtx.createUserAndRole(ctx, t, "alice", "admin", []string{"postgres"}, []string{"postgres"})
4243

43-
// Point our proxy to the Teleport's db listener on the multiplexer.
44-
proxy, err := multiplexer.NewTestProxy(testCtx.mux.DB().Addr().String())
45-
require.NoError(t, err)
46-
t.Cleanup(func() { proxy.Close() })
47-
go proxy.Serve()
48-
49-
// Connect to the proxy instead of directly to Postgres listener and make
50-
// sure the connection succeeds.
51-
psql, err := testCtx.postgresClientWithAddr(ctx, proxy.Address(), "alice", "postgres", "postgres", "postgres")
52-
require.NoError(t, err)
53-
require.NoError(t, psql.Close(ctx))
44+
for _, v2 := range []bool{false, true} {
45+
t.Run(fmt.Sprintf("v2=%v", v2), func(t *testing.T) {
46+
// Point our proxy to the Teleport's db listener on the multiplexer.
47+
proxy, err := multiplexer.NewTestProxy(testCtx.mux.DB().Addr().String(), v2)
48+
require.NoError(t, err)
49+
t.Cleanup(func() { proxy.Close() })
50+
go proxy.Serve()
51+
52+
// Connect to the proxy instead of directly to Postgres listener and make
53+
// sure the connection succeeds.
54+
psql, err := testCtx.postgresClientWithAddr(ctx, proxy.Address(), "alice", "postgres", "postgres", "postgres")
55+
require.NoError(t, err)
56+
require.NoError(t, psql.Close(ctx))
57+
})
58+
}
5459
}
5560

5661
// TestProxyProtocolMySQL ensures that clients can successfully connect to a
@@ -63,17 +68,21 @@ func TestProxyProtocolMySQL(t *testing.T) {
6368

6469
testCtx.createUserAndRole(ctx, t, "alice", "admin", []string{"root"}, []string{types.Wildcard})
6570

66-
// Point our proxy to the Teleport's MySQL listener.
67-
proxy, err := multiplexer.NewTestProxy(testCtx.mysqlListener.Addr().String())
68-
require.NoError(t, err)
69-
t.Cleanup(func() { proxy.Close() })
70-
go proxy.Serve()
71-
72-
// Connect to the proxy instead of directly to MySQL listener and make
73-
// sure the connection succeeds.
74-
mysql, err := testCtx.mysqlClientWithAddr(proxy.Address(), "alice", "mysql", "root")
75-
require.NoError(t, err)
76-
require.NoError(t, mysql.Close())
71+
for _, v2 := range []bool{false, true} {
72+
t.Run(fmt.Sprintf("v2=%v", v2), func(t *testing.T) {
73+
// Point our proxy to the Teleport's MySQL listener.
74+
proxy, err := multiplexer.NewTestProxy(testCtx.mysqlListener.Addr().String(), v2)
75+
require.NoError(t, err)
76+
t.Cleanup(func() { proxy.Close() })
77+
go proxy.Serve()
78+
79+
// Connect to the proxy instead of directly to MySQL listener and make
80+
// sure the connection succeeds.
81+
mysql, err := testCtx.mysqlClientWithAddr(proxy.Address(), "alice", "mysql", "root")
82+
require.NoError(t, err)
83+
require.NoError(t, mysql.Close())
84+
})
85+
}
7786
}
7887

7988
// TestProxyProtocolMongo ensures that clients can successfully connect to a
@@ -86,17 +95,21 @@ func TestProxyProtocolMongo(t *testing.T) {
8695

8796
testCtx.createUserAndRole(ctx, t, "alice", "admin", []string{"admin"}, []string{types.Wildcard})
8897

89-
// Point our proxy to the Teleport's TLS listener.
90-
proxy, err := multiplexer.NewTestProxy(testCtx.webListener.Addr().String())
91-
require.NoError(t, err)
92-
t.Cleanup(func() { proxy.Close() })
93-
go proxy.Serve()
94-
95-
// Connect to the proxy instead of directly to Teleport listener and make
96-
// sure the connection succeeds.
97-
mongo, err := testCtx.mongoClientWithAddr(ctx, proxy.Address(), "alice", "mongo", "admin")
98-
require.NoError(t, err)
99-
require.NoError(t, mongo.Disconnect(ctx))
98+
for _, v2 := range []bool{false, true} {
99+
t.Run(fmt.Sprintf("v2=%v", v2), func(t *testing.T) {
100+
// Point our proxy to the Teleport's TLS listener.
101+
proxy, err := multiplexer.NewTestProxy(testCtx.webListener.Addr().String(), v2)
102+
require.NoError(t, err)
103+
t.Cleanup(func() { proxy.Close() })
104+
go proxy.Serve()
105+
106+
// Connect to the proxy instead of directly to Teleport listener and make
107+
// sure the connection succeeds.
108+
mongo, err := testCtx.mongoClientWithAddr(ctx, proxy.Address(), "alice", "mongo", "admin")
109+
require.NoError(t, err)
110+
require.NoError(t, mongo.Disconnect(ctx))
111+
})
112+
}
100113
}
101114

102115
func TestProxyProtocolRedis(t *testing.T) {
@@ -106,23 +119,27 @@ func TestProxyProtocolRedis(t *testing.T) {
106119

107120
testCtx.createUserAndRole(ctx, t, "alice", "admin", []string{"admin"}, []string{types.Wildcard})
108121

109-
// Point our proxy to the Teleport's TLS listener.
110-
proxy, err := multiplexer.NewTestProxy(testCtx.webListener.Addr().String())
111-
require.NoError(t, err)
112-
t.Cleanup(func() { proxy.Close() })
113-
go proxy.Serve()
114-
115-
// Connect to the proxy instead of directly to Teleport listener and make
116-
// sure the connection succeeds.
117-
redisClient, err := testCtx.redisClientWithAddr(ctx, proxy.Address(), "alice", "redis", "admin")
118-
require.NoError(t, err)
119-
120-
// Send ECHO to Redis server and check if we get it back.
121-
resp := redisClient.Echo(ctx, "hello")
122-
require.NoError(t, resp.Err())
123-
require.Equal(t, "hello", resp.Val())
124-
125-
require.NoError(t, redisClient.Close())
122+
for _, v2 := range []bool{false, true} {
123+
t.Run(fmt.Sprintf("v2=%v", v2), func(t *testing.T) {
124+
// Point our proxy to the Teleport's TLS listener.
125+
proxy, err := multiplexer.NewTestProxy(testCtx.webListener.Addr().String(), v2)
126+
require.NoError(t, err)
127+
t.Cleanup(func() { proxy.Close() })
128+
go proxy.Serve()
129+
130+
// Connect to the proxy instead of directly to Teleport listener and make
131+
// sure the connection succeeds.
132+
redisClient, err := testCtx.redisClientWithAddr(ctx, proxy.Address(), "alice", "redis", "admin")
133+
require.NoError(t, err)
134+
135+
// Send ECHO to Redis server and check if we get it back.
136+
resp := redisClient.Echo(ctx, "hello")
137+
require.NoError(t, resp.Err())
138+
require.Equal(t, "hello", resp.Val())
139+
140+
require.NoError(t, redisClient.Close())
141+
})
142+
}
126143
}
127144

128145
// TestProxyClientDisconnectDueToIdleConnection ensures that idle clients will be disconnected.

0 commit comments

Comments
 (0)