From 2243945c78a60c6cb9aebcb74fba57b9c576b66a Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Wed, 2 Sep 2026 16:53:51 +0200 Subject: [PATCH] Add PROXY protocol support --- .github/workflows/ci.yml | 1 + _examples/ssh-forwardagent/go.mod | 1 + _examples/ssh-forwardagent/go.sum | 2 + _examples/ssh-localforward/go.mod | 1 + _examples/ssh-localforward/go.sum | 2 + _examples/ssh-proxy-protocol/go.mod | 15 + _examples/ssh-proxy-protocol/go.sum | 16 + .../ssh-proxy-protocol/proxy_protocol.go | 24 ++ _examples/ssh-pty/go.mod | 1 + _examples/ssh-pty/go.sum | 2 + _examples/ssh-publickey/go.mod | 1 + _examples/ssh-publickey/go.sum | 2 + _examples/ssh-remoteforward/go.mod | 1 + _examples/ssh-remoteforward/go.sum | 2 + _examples/ssh-sftpserver/go.mod | 1 + _examples/ssh-sftpserver/go.sum | 2 + _examples/ssh-simple/go.mod | 1 + _examples/ssh-simple/go.sum | 2 + _examples/ssh-streamlocal/go.mod | 1 + _examples/ssh-streamlocal/go.sum | 2 + _examples/ssh-timeouts/go.mod | 1 + _examples/ssh-timeouts/go.sum | 2 + go.mod | 1 + go.sum | 2 + options.go | 19 + proxyproto.go | 72 ++++ proxyproto_test.go | 347 ++++++++++++++++++ server.go | 34 +- 28 files changed, 556 insertions(+), 2 deletions(-) create mode 100644 _examples/ssh-proxy-protocol/go.mod create mode 100644 _examples/ssh-proxy-protocol/go.sum create mode 100644 _examples/ssh-proxy-protocol/proxy_protocol.go create mode 100644 proxyproto.go create mode 100644 proxyproto_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5620540..9485c64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,6 +95,7 @@ jobs: example: - ssh-forwardagent - ssh-localforward + - ssh-proxy-protocol - ssh-pty - ssh-publickey - ssh-remoteforward diff --git a/_examples/ssh-forwardagent/go.mod b/_examples/ssh-forwardagent/go.mod index 555c993..82ca6fd 100644 --- a/_examples/ssh-forwardagent/go.mod +++ b/_examples/ssh-forwardagent/go.mod @@ -9,6 +9,7 @@ require github.com/engity-com/ssh-server-go v0.0.0 require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/echocat/slf4g v1.8.4 // indirect + github.com/pires/go-proxyproto v0.15.0 // indirect golang.org/x/crypto v0.55.0 // indirect golang.org/x/sys v0.47.0 // indirect ) diff --git a/_examples/ssh-forwardagent/go.sum b/_examples/ssh-forwardagent/go.sum index e4474bb..f1433c8 100644 --- a/_examples/ssh-forwardagent/go.sum +++ b/_examples/ssh-forwardagent/go.sum @@ -2,6 +2,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/echocat/slf4g v1.8.4 h1:wWHO1xJRtzzWrgKUnmTA8CEv/SL+pJTzkyPbnKh1apA= github.com/echocat/slf4g v1.8.4/go.mod h1:YvF/d1TcPvT+/xiHStLHPI4xPT1GGeEmPczn2MSljNA= +github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI= +github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= diff --git a/_examples/ssh-localforward/go.mod b/_examples/ssh-localforward/go.mod index 6fe727d..96be318 100644 --- a/_examples/ssh-localforward/go.mod +++ b/_examples/ssh-localforward/go.mod @@ -9,6 +9,7 @@ require github.com/engity-com/ssh-server-go v0.0.0 require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/echocat/slf4g v1.8.4 // indirect + github.com/pires/go-proxyproto v0.15.0 // indirect golang.org/x/crypto v0.55.0 // indirect golang.org/x/sys v0.47.0 // indirect ) diff --git a/_examples/ssh-localforward/go.sum b/_examples/ssh-localforward/go.sum index e4474bb..f1433c8 100644 --- a/_examples/ssh-localforward/go.sum +++ b/_examples/ssh-localforward/go.sum @@ -2,6 +2,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/echocat/slf4g v1.8.4 h1:wWHO1xJRtzzWrgKUnmTA8CEv/SL+pJTzkyPbnKh1apA= github.com/echocat/slf4g v1.8.4/go.mod h1:YvF/d1TcPvT+/xiHStLHPI4xPT1GGeEmPczn2MSljNA= +github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI= +github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= diff --git a/_examples/ssh-proxy-protocol/go.mod b/_examples/ssh-proxy-protocol/go.mod new file mode 100644 index 0000000..6cf0ba2 --- /dev/null +++ b/_examples/ssh-proxy-protocol/go.mod @@ -0,0 +1,15 @@ +module github.com/engity-com/ssh-server-go/_examples/ssh-proxy-protocol + +go 1.27.0 + +replace github.com/engity-com/ssh-server-go => ../.. + +require github.com/engity-com/ssh-server-go v0.0.0 + +require ( + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect + github.com/echocat/slf4g v1.8.4 // indirect + github.com/pires/go-proxyproto v0.15.0 // indirect + golang.org/x/crypto v0.55.0 // indirect + golang.org/x/sys v0.47.0 // indirect +) diff --git a/_examples/ssh-proxy-protocol/go.sum b/_examples/ssh-proxy-protocol/go.sum new file mode 100644 index 0000000..f1433c8 --- /dev/null +++ b/_examples/ssh-proxy-protocol/go.sum @@ -0,0 +1,16 @@ +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/echocat/slf4g v1.8.4 h1:wWHO1xJRtzzWrgKUnmTA8CEv/SL+pJTzkyPbnKh1apA= +github.com/echocat/slf4g v1.8.4/go.mod h1:YvF/d1TcPvT+/xiHStLHPI4xPT1GGeEmPczn2MSljNA= +github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI= +github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= diff --git a/_examples/ssh-proxy-protocol/proxy_protocol.go b/_examples/ssh-proxy-protocol/proxy_protocol.go new file mode 100644 index 0000000..ebd68c1 --- /dev/null +++ b/_examples/ssh-proxy-protocol/proxy_protocol.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "log" + + "github.com/engity-com/ssh-server-go" +) + +const listenAddress = "127.0.0.1:2222" + +func main() { + server := &ssh.Server{ + Addr: listenAddress, + Handler: func(session ssh.Session) { + _, _ = fmt.Fprintf(session, "Your address is %s\n", session.RemoteAddr()) + }, + ProxyProtocol: &ssh.ProxyProtocolConfig{}, + } + + log.Println("DEVELOPMENT ONLY: anonymous authentication, an ephemeral host key and trusts the PROXY protocol from everywhere.") + log.Printf("starting PROXY-aware SSH server on %s", listenAddress) + log.Fatal(server.ListenAndServe()) +} diff --git a/_examples/ssh-pty/go.mod b/_examples/ssh-pty/go.mod index 7728b44..0b04747 100644 --- a/_examples/ssh-pty/go.mod +++ b/_examples/ssh-pty/go.mod @@ -12,6 +12,7 @@ require ( require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/echocat/slf4g v1.8.4 // indirect + github.com/pires/go-proxyproto v0.15.0 // indirect golang.org/x/crypto v0.55.0 // indirect golang.org/x/sys v0.47.0 // indirect ) diff --git a/_examples/ssh-pty/go.sum b/_examples/ssh-pty/go.sum index 65fac64..3f4451a 100644 --- a/_examples/ssh-pty/go.sum +++ b/_examples/ssh-pty/go.sum @@ -4,6 +4,8 @@ github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/echocat/slf4g v1.8.4 h1:wWHO1xJRtzzWrgKUnmTA8CEv/SL+pJTzkyPbnKh1apA= github.com/echocat/slf4g v1.8.4/go.mod h1:YvF/d1TcPvT+/xiHStLHPI4xPT1GGeEmPczn2MSljNA= +github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI= +github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= diff --git a/_examples/ssh-publickey/go.mod b/_examples/ssh-publickey/go.mod index e230f3c..e2b2e6b 100644 --- a/_examples/ssh-publickey/go.mod +++ b/_examples/ssh-publickey/go.mod @@ -12,5 +12,6 @@ require ( require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/echocat/slf4g v1.8.4 // indirect + github.com/pires/go-proxyproto v0.15.0 // indirect golang.org/x/sys v0.47.0 // indirect ) diff --git a/_examples/ssh-publickey/go.sum b/_examples/ssh-publickey/go.sum index e4474bb..f1433c8 100644 --- a/_examples/ssh-publickey/go.sum +++ b/_examples/ssh-publickey/go.sum @@ -2,6 +2,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/echocat/slf4g v1.8.4 h1:wWHO1xJRtzzWrgKUnmTA8CEv/SL+pJTzkyPbnKh1apA= github.com/echocat/slf4g v1.8.4/go.mod h1:YvF/d1TcPvT+/xiHStLHPI4xPT1GGeEmPczn2MSljNA= +github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI= +github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= diff --git a/_examples/ssh-remoteforward/go.mod b/_examples/ssh-remoteforward/go.mod index 9e9cff3..4932f66 100644 --- a/_examples/ssh-remoteforward/go.mod +++ b/_examples/ssh-remoteforward/go.mod @@ -9,6 +9,7 @@ require github.com/engity-com/ssh-server-go v0.0.0 require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/echocat/slf4g v1.8.4 // indirect + github.com/pires/go-proxyproto v0.15.0 // indirect golang.org/x/crypto v0.55.0 // indirect golang.org/x/sys v0.47.0 // indirect ) diff --git a/_examples/ssh-remoteforward/go.sum b/_examples/ssh-remoteforward/go.sum index e4474bb..f1433c8 100644 --- a/_examples/ssh-remoteforward/go.sum +++ b/_examples/ssh-remoteforward/go.sum @@ -2,6 +2,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/echocat/slf4g v1.8.4 h1:wWHO1xJRtzzWrgKUnmTA8CEv/SL+pJTzkyPbnKh1apA= github.com/echocat/slf4g v1.8.4/go.mod h1:YvF/d1TcPvT+/xiHStLHPI4xPT1GGeEmPczn2MSljNA= +github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI= +github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= diff --git a/_examples/ssh-sftpserver/go.mod b/_examples/ssh-sftpserver/go.mod index 77c0ebc..ae3ecec 100644 --- a/_examples/ssh-sftpserver/go.mod +++ b/_examples/ssh-sftpserver/go.mod @@ -13,6 +13,7 @@ require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/echocat/slf4g v1.8.4 // indirect github.com/kr/fs v0.1.0 // indirect + github.com/pires/go-proxyproto v0.15.0 // indirect golang.org/x/crypto v0.55.0 // indirect golang.org/x/sys v0.47.0 // indirect ) diff --git a/_examples/ssh-sftpserver/go.sum b/_examples/ssh-sftpserver/go.sum index 903d3e7..a4a99b6 100644 --- a/_examples/ssh-sftpserver/go.sum +++ b/_examples/ssh-sftpserver/go.sum @@ -4,6 +4,8 @@ github.com/echocat/slf4g v1.8.4 h1:wWHO1xJRtzzWrgKUnmTA8CEv/SL+pJTzkyPbnKh1apA= github.com/echocat/slf4g v1.8.4/go.mod h1:YvF/d1TcPvT+/xiHStLHPI4xPT1GGeEmPczn2MSljNA= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI= +github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24= github.com/pkg/sftp v1.13.11 h1:0N92SLTB8JqASJB14ZLHHzFnBV8mG9zw4K7jghEFWuE= github.com/pkg/sftp v1.13.11/go.mod h1:uNkH9roSXglNJqM+glJJi+TQXQUm0fXFWqCFmT8hsN0= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= diff --git a/_examples/ssh-simple/go.mod b/_examples/ssh-simple/go.mod index 7260516..12b8234 100644 --- a/_examples/ssh-simple/go.mod +++ b/_examples/ssh-simple/go.mod @@ -9,6 +9,7 @@ require github.com/engity-com/ssh-server-go v0.0.0 require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/echocat/slf4g v1.8.4 // indirect + github.com/pires/go-proxyproto v0.15.0 // indirect golang.org/x/crypto v0.55.0 // indirect golang.org/x/sys v0.47.0 // indirect ) diff --git a/_examples/ssh-simple/go.sum b/_examples/ssh-simple/go.sum index e4474bb..f1433c8 100644 --- a/_examples/ssh-simple/go.sum +++ b/_examples/ssh-simple/go.sum @@ -2,6 +2,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/echocat/slf4g v1.8.4 h1:wWHO1xJRtzzWrgKUnmTA8CEv/SL+pJTzkyPbnKh1apA= github.com/echocat/slf4g v1.8.4/go.mod h1:YvF/d1TcPvT+/xiHStLHPI4xPT1GGeEmPczn2MSljNA= +github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI= +github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= diff --git a/_examples/ssh-streamlocal/go.mod b/_examples/ssh-streamlocal/go.mod index 8918475..acd378a 100644 --- a/_examples/ssh-streamlocal/go.mod +++ b/_examples/ssh-streamlocal/go.mod @@ -9,6 +9,7 @@ require github.com/engity-com/ssh-server-go v0.0.0 require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/echocat/slf4g v1.8.4 // indirect + github.com/pires/go-proxyproto v0.15.0 // indirect golang.org/x/crypto v0.55.0 // indirect golang.org/x/sys v0.47.0 // indirect ) diff --git a/_examples/ssh-streamlocal/go.sum b/_examples/ssh-streamlocal/go.sum index e4474bb..f1433c8 100644 --- a/_examples/ssh-streamlocal/go.sum +++ b/_examples/ssh-streamlocal/go.sum @@ -2,6 +2,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/echocat/slf4g v1.8.4 h1:wWHO1xJRtzzWrgKUnmTA8CEv/SL+pJTzkyPbnKh1apA= github.com/echocat/slf4g v1.8.4/go.mod h1:YvF/d1TcPvT+/xiHStLHPI4xPT1GGeEmPczn2MSljNA= +github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI= +github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= diff --git a/_examples/ssh-timeouts/go.mod b/_examples/ssh-timeouts/go.mod index a036242..e1bc4a6 100644 --- a/_examples/ssh-timeouts/go.mod +++ b/_examples/ssh-timeouts/go.mod @@ -9,6 +9,7 @@ require github.com/engity-com/ssh-server-go v0.0.0 require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/echocat/slf4g v1.8.4 // indirect + github.com/pires/go-proxyproto v0.15.0 // indirect golang.org/x/crypto v0.55.0 // indirect golang.org/x/sys v0.47.0 // indirect ) diff --git a/_examples/ssh-timeouts/go.sum b/_examples/ssh-timeouts/go.sum index e4474bb..f1433c8 100644 --- a/_examples/ssh-timeouts/go.sum +++ b/_examples/ssh-timeouts/go.sum @@ -2,6 +2,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/echocat/slf4g v1.8.4 h1:wWHO1xJRtzzWrgKUnmTA8CEv/SL+pJTzkyPbnKh1apA= github.com/echocat/slf4g v1.8.4/go.mod h1:YvF/d1TcPvT+/xiHStLHPI4xPT1GGeEmPczn2MSljNA= +github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI= +github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= diff --git a/go.mod b/go.mod index a1b4e62..9a1d4bd 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.27.0 require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be github.com/echocat/slf4g v1.8.4 + github.com/pires/go-proxyproto v0.15.0 github.com/stretchr/testify v1.12.1 golang.org/x/crypto v0.55.0 golang.org/x/sys v0.47.0 diff --git a/go.sum b/go.sum index e4474bb..f1433c8 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/echocat/slf4g v1.8.4 h1:wWHO1xJRtzzWrgKUnmTA8CEv/SL+pJTzkyPbnKh1apA= github.com/echocat/slf4g v1.8.4/go.mod h1:YvF/d1TcPvT+/xiHStLHPI4xPT1GGeEmPczn2MSljNA= +github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI= +github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= diff --git a/options.go b/options.go index 29c8ef1..894f5fb 100644 --- a/options.go +++ b/options.go @@ -1,6 +1,7 @@ package ssh import ( + "errors" "os" gossh "golang.org/x/crypto/ssh" @@ -82,3 +83,21 @@ func WrapConn(fn ConnCallback) Option { return nil } } + +// EnableProxyProtocol returns a functional option that enables PROXY protocol +// processing. With no configuration, every connection must supply a PROXY +// header and the header is trusted from any peer. At most one configuration +// may be supplied. +func EnableProxyProtocol(config ...ProxyProtocolConfig) Option { + return func(srv *Server) error { + if len(config) > 1 { + return errors.New("ssh: enable proxy protocol accepts at most one configuration") + } + configured := ProxyProtocolConfig{} + if len(config) == 1 { + configured = config[0] + } + srv.ProxyProtocol = &configured + return nil + } +} diff --git a/proxyproto.go b/proxyproto.go new file mode 100644 index 0000000..06d64d7 --- /dev/null +++ b/proxyproto.go @@ -0,0 +1,72 @@ +package ssh + +import ( + "errors" + "net" + "time" + + "github.com/pires/go-proxyproto" +) + +var errProxyProtocolAlreadyWrapped = errors.New("ssh: connection is already wrapped for PROXY protocol") + +// ProxyProtocolConfig configures PROXY protocol processing for accepted +// connections. A nil ConnPolicy requires a PROXY header but trusts the source +// information supplied by every peer. Listeners reachable by untrusted peers +// should configure a policy such as [proxyproto.TrustProxyHeaderFrom] or +// [proxyproto.TrustProxyHeaderFromRanges]. Connections are wrapped before +// ConnCallback, while the header is processed lazily on first I/O or address +// access. A callback can type-assert the connection to *proxyproto.Conn and use +// Raw to inspect the transport peer instead of the address supplied by the +// header. It must retain or wrap the supplied connection for PROXY processing +// to remain effective. +type ProxyProtocolConfig struct { + // ConnPolicy decides whether a connection may supply a PROXY header and how + // that header is handled. It must return promptly. + ConnPolicy proxyproto.ConnPolicyFunc + // ValidateHeader performs application-specific validation after parsing. It + // must return promptly. + ValidateHeader proxyproto.Validator + // ReadHeaderTimeout bounds PROXY header processing. Zero uses the + // go-proxyproto default; a negative value disables its header timeout. + ReadHeaderTimeout time.Duration + // ReadBufferSize controls the per-connection header buffer. A nonpositive + // value uses the go-proxyproto default. Values below 107 bytes break maximum + // length version 1 headers. + ReadBufferSize int +} + +func wrapProxyProtocolConn(conn net.Conn, config ProxyProtocolConfig) (net.Conn, error) { + if _, ok := conn.(*proxyproto.Conn); ok { + return nil, errProxyProtocolAlreadyWrapped + } + + policy := proxyproto.REQUIRE + if config.ConnPolicy != nil { + var err error + policy, err = config.ConnPolicy(proxyproto.ConnPolicyOptions{ + Upstream: conn.RemoteAddr(), + Downstream: conn.LocalAddr(), + }) + if err != nil { + return nil, err + } + if policy == proxyproto.SKIP { + return conn, nil + } + } + + options := []func(*proxyproto.Conn){proxyproto.WithPolicy(policy)} + if config.ValidateHeader != nil { + options = append(options, proxyproto.ValidateHeader(config.ValidateHeader)) + } + if config.ReadHeaderTimeout < 0 { + options = append(options, proxyproto.SetReadHeaderTimeout(0)) + } else if config.ReadHeaderTimeout > 0 { + options = append(options, proxyproto.SetReadHeaderTimeout(config.ReadHeaderTimeout)) + } + if config.ReadBufferSize > 0 { + options = append(options, proxyproto.WithBufferSize(config.ReadBufferSize)) + } + return proxyproto.NewConn(conn, options...), nil +} diff --git a/proxyproto_test.go b/proxyproto_test.go new file mode 100644 index 0000000..106f69e --- /dev/null +++ b/proxyproto_test.go @@ -0,0 +1,347 @@ +package ssh + +import ( + "errors" + "net" + "sync/atomic" + "testing" + "time" + + "github.com/pires/go-proxyproto" + "github.com/stretchr/testify/require" + gossh "golang.org/x/crypto/ssh" +) + +func TestProxyProtocolReportsHeaderAddresses(t *testing.T) { + for _, version := range []byte{1, 2} { + t.Run(string(rune('0'+version)), func(t *testing.T) { + source := &net.TCPAddr{IP: net.ParseIP("192.0.2.10"), Port: 4242} + destination := &net.TCPAddr{IP: net.ParseIP("198.51.100.20"), Port: 22} + policyOptions := make(chan proxyproto.ConnPolicyOptions, 1) + validated := make(chan *proxyproto.Header, 1) + type addresses struct { + remote string + local string + contextRemote string + contextLocal string + } + observed := make(chan addresses, 1) + callbackRemote := make(chan string, 1) + failures := make(chan error, 1) + srv := &Server{ + ProxyProtocol: &ProxyProtocolConfig{ + ConnPolicy: func(options proxyproto.ConnPolicyOptions) (proxyproto.Policy, error) { + policyOptions <- options + return proxyproto.REQUIRE, nil + }, + ValidateHeader: func(header *proxyproto.Header) error { + validated <- header + return nil + }, + ReadHeaderTimeout: time.Second, + ReadBufferSize: 512, + }, + ConnCallback: func(_ Context, conn net.Conn) net.Conn { + callbackRemote <- conn.RemoteAddr().String() + return conn + }, + ConnectionFailedCallback: func(_ net.Conn, err error) { + failures <- err + }, + Handler: func(session Session) { + observed <- addresses{ + remote: session.RemoteAddr().String(), + local: session.LocalAddr().String(), + contextRemote: session.Context().RemoteAddr().String(), + contextLocal: session.Context().LocalAddr().String(), + } + }, + } + listener := newLocalListener() + serveDone := make(chan error, 1) + go func() { serveDone <- srv.Serve(listener) }() + t.Cleanup(func() { + _ = srv.Close() + if err := <-serveDone; !errors.Is(err, ErrServerClosed) { + t.Errorf("Serve() error = %v; want %v", err, ErrServerClosed) + } + }) + + rawConn, err := net.Dial("tcp", listener.Addr().String()) + require.NoError(t, err) + t.Cleanup(func() { closeQuietly(rawConn) }) + header := &proxyproto.Header{ + Version: version, + Command: proxyproto.PROXY, + TransportProtocol: proxyproto.TCPv4, + SourceAddr: source, + DestinationAddr: destination, + } + _, err = header.WriteTo(rawConn) + require.NoError(t, err) + + clientConn, channels, requests, err := gossh.NewClientConn(rawConn, listener.Addr().String(), &gossh.ClientConfig{ + User: "testuser", + HostKeyCallback: gossh.InsecureIgnoreHostKey(), + }) + require.NoError(t, err) + client := gossh.NewClient(clientConn, channels, requests) + t.Cleanup(func() { closeQuietly(client) }) + session, err := client.NewSession() + require.NoError(t, err) + require.NoError(t, session.Run("")) + + require.Equal(t, source.String(), <-callbackRemote) + got := <-observed + require.Equal(t, source.String(), got.remote) + require.Equal(t, destination.String(), got.local) + require.Equal(t, source.String(), got.contextRemote) + require.Equal(t, destination.String(), got.contextLocal) + policy := <-policyOptions + require.Equal(t, listener.Addr().String(), policy.Downstream.String()) + require.NotEqual(t, source.String(), policy.Upstream.String()) + require.Equal(t, header.SourceAddr.String(), (<-validated).SourceAddr.String()) + select { + case failure := <-failures: + t.Fatalf("unexpected connection failure: %v", failure) + default: + } + }) + } +} + +func TestProxyProtocolConnPolicyCanSkipHeaderProcessing(t *testing.T) { + var policyCalls atomic.Int32 + var validatorCalls atomic.Int32 + session, _, cleanup := newTestSession(t, &Server{ + ProxyProtocol: &ProxyProtocolConfig{ + ConnPolicy: func(proxyproto.ConnPolicyOptions) (proxyproto.Policy, error) { + policyCalls.Add(1) + return proxyproto.SKIP, nil + }, + ValidateHeader: func(*proxyproto.Header) error { + validatorCalls.Add(1) + return nil + }, + }, + Handler: func(Session) {}, + }, nil) + defer cleanup() + require.NoError(t, session.Run("")) + require.Equal(t, int32(1), policyCalls.Load()) + require.Zero(t, validatorCalls.Load()) +} + +func TestProxyProtocolConnPolicyRejectsConnection(t *testing.T) { + expected := errors.New("untrusted proxy") + failure := make(chan error, 1) + srv := &Server{ + ProxyProtocol: &ProxyProtocolConfig{ConnPolicy: func(proxyproto.ConnPolicyOptions) (proxyproto.Policy, error) { + return proxyproto.REJECT, expected + }}, + ConnectionFailedCallback: func(_ net.Conn, err error) { failure <- err }, + } + serverConn, clientConn := net.Pipe() + defer closeQuietly(clientConn) + srv.HandleConn(serverConn) + require.ErrorIs(t, <-failure, expected) + _ = clientConn.SetReadDeadline(time.Now().Add(time.Second)) + _, err := clientConn.Read(make([]byte, 1)) + require.Error(t, err) +} + +func TestProxyProtocolRejectPolicyRejectsHeader(t *testing.T) { + serverConn, clientConn := net.Pipe() + defer closeQuietly(clientConn) + wrapped, err := wrapProxyProtocolConn(serverConn, ProxyProtocolConfig{ConnPolicy: func(proxyproto.ConnPolicyOptions) (proxyproto.Policy, error) { + return proxyproto.REJECT, nil + }}) + require.NoError(t, err) + writeDone := make(chan error, 1) + go func() { + _, err := (&proxyproto.Header{ + Version: 1, + Command: proxyproto.PROXY, + TransportProtocol: proxyproto.TCPv4, + SourceAddr: &net.TCPAddr{IP: net.ParseIP("192.0.2.10"), Port: 4242}, + DestinationAddr: &net.TCPAddr{IP: net.ParseIP("198.51.100.20"), Port: 22}, + }).WriteTo(clientConn) + writeDone <- err + }() + _, err = wrapped.Read(make([]byte, 1)) + require.ErrorIs(t, err, proxyproto.ErrSuperfluousProxyHeader) + require.NoError(t, <-writeDone) +} + +func TestProxyProtocolValidatorRejectsHeader(t *testing.T) { + expected := errors.New("invalid proxy metadata") + var callbackCalls atomic.Int32 + failure := make(chan error, 1) + srv := &Server{ + ProxyProtocol: &ProxyProtocolConfig{ValidateHeader: func(*proxyproto.Header) error { + return expected + }}, + ConnCallback: func(_ Context, conn net.Conn) net.Conn { + callbackCalls.Add(1) + return conn + }, + ConnectionFailedCallback: func(_ net.Conn, err error) { failure <- err }, + } + serverConn, clientConn := net.Pipe() + defer closeQuietly(clientConn) + t.Cleanup(func() { _ = srv.Close() }) + done := make(chan struct{}) + go func() { + defer close(done) + srv.HandleConn(serverConn) + }() + writeDone := make(chan error, 1) + go func() { + _, err := (&proxyproto.Header{ + Version: 2, + Command: proxyproto.PROXY, + TransportProtocol: proxyproto.TCPv4, + SourceAddr: &net.TCPAddr{IP: net.ParseIP("192.0.2.10"), Port: 4242}, + DestinationAddr: &net.TCPAddr{IP: net.ParseIP("198.51.100.20"), Port: 22}, + }).WriteTo(clientConn) + writeDone <- err + }() + require.ErrorIs(t, <-failure, expected) + require.NoError(t, <-writeDone) + <-done + require.Equal(t, int32(1), callbackCalls.Load()) +} + +func TestProxyProtocolReadHeaderTimeout(t *testing.T) { + failure := make(chan error, 1) + srv := &Server{ + ProxyProtocol: &ProxyProtocolConfig{ReadHeaderTimeout: 20 * time.Millisecond}, + ConnectionFailedCallback: func(_ net.Conn, err error) { failure <- err }, + } + serverConn, clientConn := net.Pipe() + defer closeQuietly(clientConn) + t.Cleanup(func() { _ = srv.Close() }) + done := make(chan struct{}) + go func() { + defer close(done) + srv.HandleConn(serverConn) + }() + select { + case err := <-failure: + require.ErrorIs(t, err, proxyproto.ErrNoProxyProtocol) + case <-time.After(time.Second): + t.Fatal("PROXY header timeout did not reject the connection") + } + <-done +} + +func TestProxyProtocolHonorsEarlierHandshakeTimeout(t *testing.T) { + headerTimeout := time.Second + handshakeTimeout := 20 * time.Millisecond + failure := make(chan error, 1) + srv := &Server{ + ProxyProtocol: &ProxyProtocolConfig{ReadHeaderTimeout: headerTimeout}, + HandshakeTimeout: &handshakeTimeout, + ConnectionFailedCallback: func(_ net.Conn, err error) { failure <- err }, + } + serverConn, clientConn := net.Pipe() + defer closeQuietly(clientConn) + started := time.Now() + done := make(chan struct{}) + go func() { + defer close(done) + srv.HandleConn(serverConn) + }() + select { + case err := <-failure: + require.Error(t, err) + require.Less(t, time.Since(started), headerTimeout) + case <-time.After(time.Second): + t.Fatal("SSH handshake timeout did not stop PROXY header processing") + } + <-done +} + +func TestServerCloseStopsProxyProtocolHeaderRead(t *testing.T) { + disabledTimeout := time.Duration(0) + srv := &Server{ + ProxyProtocol: &ProxyProtocolConfig{ReadHeaderTimeout: -1}, + HandshakeTimeout: &disabledTimeout, + } + serverConn, clientConn := net.Pipe() + defer closeQuietly(clientConn) + t.Cleanup(func() { _ = srv.Close() }) + done := make(chan struct{}) + go func() { + defer close(done) + srv.HandleConn(serverConn) + }() + require.Eventually(t, func() bool { + srv.mu.RLock() + defer srv.mu.RUnlock() + return len(srv.activeConns) == 1 + }, time.Second, time.Millisecond) + writeDone := make(chan error, 1) + go func() { + _, err := clientConn.Write([]byte("PROXY ")) + writeDone <- err + }() + select { + case err := <-writeDone: + require.NoError(t, err) + case <-time.After(time.Second): + t.Fatal("PROXY header processing did not start") + } + require.NoError(t, srv.Close()) + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("Server.Close did not stop PROXY header processing") + } + srv.mu.RLock() + require.Empty(t, srv.activeConns) + srv.mu.RUnlock() +} + +func TestProxyProtocolRejectsAlreadyWrappedConnection(t *testing.T) { + serverConn, clientConn := net.Pipe() + defer closeQuietly(clientConn) + alreadyWrapped := proxyproto.NewConn(serverConn) + wrapped, err := wrapProxyProtocolConn(alreadyWrapped, ProxyProtocolConfig{}) + require.ErrorIs(t, err, errProxyProtocolAlreadyWrapped) + require.Nil(t, wrapped) + closeQuietly(alreadyWrapped) +} + +func TestEnableProxyProtocolOption(t *testing.T) { + srv := &Server{} + require.NoError(t, srv.SetOption(EnableProxyProtocol())) + require.Equal(t, &ProxyProtocolConfig{}, srv.ProxyProtocol) + + config := ProxyProtocolConfig{ReadHeaderTimeout: time.Second, ReadBufferSize: 512} + srv = &Server{} + require.NoError(t, srv.SetOption(EnableProxyProtocol(config))) + config.ReadBufferSize = 1024 + require.Equal(t, time.Second, srv.ProxyProtocol.ReadHeaderTimeout) + require.Equal(t, 512, srv.ProxyProtocol.ReadBufferSize) + require.NoError(t, srv.SetOption(EnableProxyProtocol())) + require.Equal(t, &ProxyProtocolConfig{}, srv.ProxyProtocol) + + srv = &Server{} + err := srv.SetOption(EnableProxyProtocol(ProxyProtocolConfig{}, ProxyProtocolConfig{})) + require.Error(t, err) + require.Nil(t, srv.ProxyProtocol) +} + +func TestProxyProtocolConfigIsSnapshotted(t *testing.T) { + srv := &Server{ + ProxyProtocol: &ProxyProtocolConfig{ReadHeaderTimeout: time.Second, ReadBufferSize: 512}, + } + settings := srv.connectionSettings() + srv.ProxyProtocol.ReadHeaderTimeout = 2 * time.Second + srv.ProxyProtocol.ReadBufferSize = 1024 + srv.ProxyProtocol = nil + require.NotNil(t, settings.proxyProtocol) + require.Equal(t, time.Second, settings.proxyProtocol.ReadHeaderTimeout) + require.Equal(t, 512, settings.proxyProtocol.ReadBufferSize) +} diff --git a/server.go b/server.go index 6cf92f8..4c285d2 100644 --- a/server.go +++ b/server.go @@ -102,6 +102,11 @@ type Server struct { ConnectionFailedCallback ConnectionFailedCallback // callback to report connection failures DisconnectCallback DisconnectCallback // callback after an established SSH connection ends + // ProxyProtocol enables PROXY protocol processing when non-nil. Connection + // wrapping occurs before ConnCallback and the SSH handshake. Do not pass + // connections already wrapped by go-proxyproto when this is configured. + ProxyProtocol *ProxyProtocolConfig + // Timeout fields use their Default* value when nil. A configured duration // less than or equal to zero disables that timeout. HandshakeTimeout *time.Duration // timeout until successful authentication, default 2 minutes @@ -182,6 +187,7 @@ type connectionSettings struct { authenticatedConnections *atomic.Int64 globalChannels *atomic.Int64 requireClientAuth bool + proxyProtocol *ProxyProtocolConfig } func (srv *Server) connectionSettings() *connectionSettings { @@ -203,6 +209,11 @@ func (srv *Server) connectionSettings() *connectionSettings { if handler == nil { handler = getDefaultHandler() } + proxyProtocol := srv.ProxyProtocol + if proxyProtocol != nil { + config := *proxyProtocol + proxyProtocol = &config + } return &connectionSettings{ logger: srv.Logger, connCallback: srv.ConnCallback, @@ -230,6 +241,7 @@ func (srv *Server) connectionSettings() *connectionSettings { authenticatedConnections: srv.authenticatedConnections, globalChannels: srv.globalChannels, requireClientAuth: srv.RequireClientAuth, + proxyProtocol: proxyProtocol, } } @@ -872,9 +884,27 @@ func (srv *Server) handleConn(newConn net.Conn, active *activeConn, settings ... if maxTimer != nil { defer maxTimer.Stop() } + if v := connectionSettings.proxyProtocol; v != nil { + proxyConn, err := wrapProxyProtocolConn(newConn, *v) + if err != nil { + srv.releaseStartup(active) + closeQuietly(newConn) + srv.untrackActiveConn(active) + tracked = false + if connectionSettings.connectionFailedCallback != nil { + connectionSettings.connectionFailedCallback(newConn, err) + } + return + } + newConn = proxyConn + if !srv.updateActiveConn(active, proxyConn) { + closeQuietly(proxyConn) + return + } + } _ = newConn.SetDeadline(earliestDeadline(handshakeDeadline, maxDeadline)) - if connectionSettings.connCallback != nil { - cbConn := connectionSettings.connCallback(ctx, newConn) + if v := connectionSettings.connCallback; v != nil { + cbConn := v(ctx, newConn) if cbConn == nil { closeQuietly(newConn) return