Skip to content

Commit 843067b

Browse files
committed
feat: send proxy protocol headers
1 parent ab8f06a commit 843067b

File tree

5 files changed

+76
-39
lines changed

5 files changed

+76
-39
lines changed

go.mod

+4-3
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@ require (
99
github.com/jpillora/backoff v1.0.0
1010
github.com/jpillora/requestlog v1.0.0
1111
github.com/jpillora/sizestr v1.0.0
12-
golang.org/x/crypto v0.16.0
13-
golang.org/x/net v0.14.0
12+
github.com/pires/go-proxyproto v0.8.0
13+
golang.org/x/crypto v0.21.0
14+
golang.org/x/net v0.23.0
1415
golang.org/x/sync v0.5.0
1516
)
1617

1718
require (
1819
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect
1920
github.com/jpillora/ansi v1.0.3 // indirect
2021
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
21-
golang.org/x/sys v0.15.0 // indirect
22+
golang.org/x/sys v0.18.0 // indirect
2223
golang.org/x/text v0.14.0 // indirect
2324
)

go.sum

+10-8
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,20 @@ github.com/jpillora/requestlog v1.0.0 h1:bg++eJ74T7DYL3DlIpiwknrtfdUA9oP/M4fL+Pp
1414
github.com/jpillora/requestlog v1.0.0/go.mod h1:HTWQb7QfDc2jtHnWe2XEIEeJB7gJPnVdpNn52HXPvy8=
1515
github.com/jpillora/sizestr v1.0.0 h1:4tr0FLxs1Mtq3TnsLDV+GYUWG7Q26a6s+tV5Zfw2ygw=
1616
github.com/jpillora/sizestr v1.0.0/go.mod h1:bUhLv4ctkknatr6gR42qPxirmd5+ds1u7mzD+MZ33f0=
17+
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
18+
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
1719
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
1820
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
19-
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
20-
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
21-
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
22-
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
21+
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
22+
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
23+
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
24+
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
2325
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
2426
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
2527
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
26-
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
27-
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
28-
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
29-
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
28+
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
29+
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
30+
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
31+
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
3032
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
3133
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=

share/settings/remote.go

+14-11
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import (
3535
type Remote struct {
3636
LocalHost, LocalPort, LocalProto string
3737
RemoteHost, RemotePort, RemoteProto string
38-
Socks, Reverse, Stdio bool
38+
Socks, Reverse, Stdio, ProxyProto bool
3939
}
4040

4141
const revPrefix = "R:"
@@ -129,6 +129,9 @@ func DecodeRemote(s string) (*Remote, error) {
129129
if r.Stdio && r.Reverse {
130130
return nil, errors.New("stdio cannot be reversed")
131131
}
132+
if r.ProxyProto && !r.Reverse {
133+
return nil, errors.New("cannot use proxy protocol for a non-reversed remote")
134+
}
132135
return r, nil
133136
}
134137

@@ -153,7 +156,7 @@ func isHost(s string) bool {
153156

154157
var l4Proto = regexp.MustCompile(`(?i)\/(tcp|udp)$`)
155158

156-
//L4Proto extacts the layer-4 protocol from the given string
159+
// L4Proto extacts the layer-4 protocol from the given string
157160
func L4Proto(s string) (head, proto string) {
158161
if l4Proto.MatchString(s) {
159162
l := len(s)
@@ -162,7 +165,7 @@ func L4Proto(s string) (head, proto string) {
162165
return s, ""
163166
}
164167

165-
//implement Stringer
168+
// implement Stringer
166169
func (r Remote) String() string {
167170
sb := strings.Builder{}
168171
if r.Reverse {
@@ -177,7 +180,7 @@ func (r Remote) String() string {
177180
return sb.String()
178181
}
179182

180-
//Encode remote to a string
183+
// Encode remote to a string
181184
func (r Remote) Encode() string {
182185
if r.LocalPort == "" {
183186
r.LocalPort = r.RemotePort
@@ -193,7 +196,7 @@ func (r Remote) Encode() string {
193196
return local + ":" + remote
194197
}
195198

196-
//Local is the decodable local portion
199+
// Local is the decodable local portion
197200
func (r Remote) Local() string {
198201
if r.Stdio {
199202
return "stdio"
@@ -204,7 +207,7 @@ func (r Remote) Local() string {
204207
return r.LocalHost + ":" + r.LocalPort
205208
}
206209

207-
//Remote is the decodable remote portion
210+
// Remote is the decodable remote portion
208211
func (r Remote) Remote() string {
209212
if r.Socks {
210213
return "socks"
@@ -215,16 +218,16 @@ func (r Remote) Remote() string {
215218
return r.RemoteHost + ":" + r.RemotePort
216219
}
217220

218-
//UserAddr is checked when checking if a
219-
//user has access to a given remote
221+
// UserAddr is checked when checking if a
222+
// user has access to a given remote
220223
func (r Remote) UserAddr() string {
221224
if r.Reverse {
222225
return "R:" + r.LocalHost + ":" + r.LocalPort
223226
}
224227
return r.RemoteHost + ":" + r.RemotePort
225228
}
226229

227-
//CanListen checks if the port can be listened on
230+
// CanListen checks if the port can be listened on
228231
func (r Remote) CanListen() bool {
229232
//valid protocols
230233
switch r.LocalProto {
@@ -253,7 +256,7 @@ func (r Remote) CanListen() bool {
253256

254257
type Remotes []*Remote
255258

256-
//Filter out forward reversed/non-reversed remotes
259+
// Filter out forward reversed/non-reversed remotes
257260
func (rs Remotes) Reversed(reverse bool) Remotes {
258261
subset := Remotes{}
259262
for _, r := range rs {
@@ -265,7 +268,7 @@ func (rs Remotes) Reversed(reverse bool) Remotes {
265268
return subset
266269
}
267270

268-
//Encode back into strings
271+
// Encode back into strings
269272
func (rs Remotes) Encode() []string {
270273
s := make([]string, len(rs))
271274
for i, r := range rs {

share/tunnel/tunnel_in_proxy.go

+22-5
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@ import (
99
"github.com/jpillora/chisel/share/cio"
1010
"github.com/jpillora/chisel/share/settings"
1111
"github.com/jpillora/sizestr"
12+
"github.com/pires/go-proxyproto"
1213
"golang.org/x/crypto/ssh"
1314
)
1415

15-
//sshTunnel exposes a subset of Tunnel to subtypes
16+
// sshTunnel exposes a subset of Tunnel to subtypes
1617
type sshTunnel interface {
1718
getSSH(ctx context.Context) ssh.Conn
1819
}
1920

20-
//Proxy is the inbound portion of a Tunnel
21+
// Proxy is the inbound portion of a Tunnel
2122
type Proxy struct {
2223
*cio.Logger
2324
sshTun sshTunnel
@@ -30,7 +31,7 @@ type Proxy struct {
3031
mu sync.Mutex
3132
}
3233

33-
//NewProxy creates a Proxy
34+
// NewProxy creates a Proxy
3435
func NewProxy(logger *cio.Logger, sshTun sshTunnel, index int, remote *settings.Remote) (*Proxy, error) {
3536
id := index + 1
3637
p := &Proxy{
@@ -69,8 +70,8 @@ func (p *Proxy) listen() error {
6970
return nil
7071
}
7172

72-
//Run enables the proxy and blocks while its active,
73-
//close the proxy by cancelling the context.
73+
// Run enables the proxy and blocks while its active,
74+
// close the proxy by cancelling the context.
7475
func (p *Proxy) Run(ctx context.Context) error {
7576
if p.remote.Stdio {
7677
return p.runStdio(ctx)
@@ -144,6 +145,22 @@ func (p *Proxy) pipeRemote(ctx context.Context, src io.ReadWriteCloser) {
144145
return
145146
}
146147
go ssh.DiscardRequests(reqs)
148+
//if proxy protocol is requested, send the header
149+
if p.remote.ProxyProto {
150+
conn, ok := src.(net.Conn)
151+
if !ok {
152+
//this should never happen, and if it does, something has gone horribly wrong (file an issue)
153+
panic("attempted to use proxy protocol for a source which is not a network connection")
154+
}
155+
156+
header := proxyproto.HeaderProxyFromAddrs(2, conn.RemoteAddr(), conn.LocalAddr())
157+
s, err := header.WriteTo(dst)
158+
if err != nil {
159+
l.Infof("Stream error: %s", err)
160+
return
161+
}
162+
l.Debugf("PROXY v2 header (sent %s)", sizestr.ToString(s))
163+
}
147164
//then pipe
148165
s, r := cio.Pipe(src, dst)
149166
l.Debugf("Close (sent %s received %s)", sizestr.ToString(s), sizestr.ToString(r))

share/tunnel/tunnel_in_proxy_udp.go

+26-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/gob"
66
"fmt"
7+
"github.com/pires/go-proxyproto"
78
"io"
89
"net"
910
"strings"
@@ -18,18 +19,19 @@ import (
1819
"golang.org/x/sync/errgroup"
1920
)
2021

21-
//listenUDP is a special listener which forwards packets via
22-
//the bound ssh connection. tricky part is multiplexing lots of
23-
//udp clients through the entry node. each will listen on its
24-
//own source-port for a response:
25-
// (random)
26-
// src-1 1111->... dst-1 6345->7777
27-
// src-2 2222->... <---> udp <---> udp <-> dst-1 7543->7777
28-
// src-3 3333->... listener handler dst-1 1444->7777
22+
// listenUDP is a special listener which forwards packets via
23+
// the bound ssh connection. tricky part is multiplexing lots of
24+
// udp clients through the entry node. each will listen on its
25+
// own source-port for a response:
2926
//
30-
//we must store these mappings (1111-6345, etc) in memory for a length
31-
//of time, so that when the exit node receives a response on 6345, it
32-
//knows to return it to 1111.
27+
// (random)
28+
// src-1 1111->... dst-1 6345->7777
29+
// src-2 2222->... <---> udp <---> udp <-> dst-1 7543->7777
30+
// src-3 3333->... listener handler dst-1 1444->7777
31+
//
32+
// we must store these mappings (1111-6345, etc) in memory for a length
33+
// of time, so that when the exit node receives a response on 6345, it
34+
// knows to return it to 1111.
3335
func listenUDP(l *cio.Logger, sshTun sshTunnel, remote *settings.Remote) (*udpListener, error) {
3436
a, err := net.ResolveUDPAddr("udp", remote.Local())
3537
if err != nil {
@@ -102,8 +104,20 @@ func (u *udpListener) runInbound(ctx context.Context) error {
102104
}
103105
return u.Errorf("inbound-udpchan: %w", err)
104106
}
105-
//send over channel, including source address
106107
b := buff[:n]
108+
//if proxy protocol is requested, prepend the header
109+
if u.remote.ProxyProto {
110+
//NOTE: LocalAddr for UDP doesn't actually get the destination IP in the packet
111+
//getting that information is non-trivial and non-portable from what I can see
112+
//therefore, this will suffice for now
113+
header := proxyproto.HeaderProxyFromAddrs(2, addr, u.inbound.LocalAddr())
114+
formatted, err := header.Format()
115+
if err != nil {
116+
return u.Errorf("header format: %w", err)
117+
}
118+
b = append(formatted, b...)
119+
}
120+
//send over channel, including source address
107121
if err := uc.encode(addr.String(), b); err != nil {
108122
if strings.HasSuffix(err.Error(), "EOF") {
109123
continue //dropped packet...

0 commit comments

Comments
 (0)