Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PROXY v2 support #552

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ $ chisel client --help

which does reverse port forwarding, sharing <remote-host>:<remote-port>
from the client to the server's <local-interface>:<local-port>.
additionally, P can be appended after the R flag to send a PROXY v2 header
which can be used to identify the original source of incoming connections:

RP:<local-interface>:<local-port>:<remote-host>:<remote-port>/<protocol>

example remotes

Expand All @@ -247,6 +251,7 @@ $ chisel client --help
socks
5000:socks
R:2222:localhost:22
RP:2222:localhost:22
R:socks
R:5000:socks
stdio:example.com:22
Expand All @@ -264,7 +269,9 @@ $ chisel client --help
will be proxied through the client which specified the remote.
Reverse remotes specifying "R:socks" will listen on the server's
default socks port (1080) and terminate the connection at the
client's internal SOCKS5 proxy.
client's internal SOCKS5 proxy. Additionally, P can be appended after
the R prefix to have the chisel server pass a PROXY v2 header
which can be used to identify the original source of incoming connections.

When stdio is used as local-host, the tunnel will connect standard
input/output of this program with the remote. This is useful when
Expand Down
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ require (
github.com/jpillora/backoff v1.0.0
github.com/jpillora/requestlog v1.0.0
github.com/jpillora/sizestr v1.0.0
golang.org/x/crypto v0.16.0
golang.org/x/net v0.14.0
github.com/pires/go-proxyproto v0.8.0
golang.org/x/crypto v0.21.0
golang.org/x/net v0.23.0
golang.org/x/sync v0.5.0
)

require (
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect
github.com/jpillora/ansi v1.0.3 // indirect
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
18 changes: 10 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@ github.com/jpillora/requestlog v1.0.0 h1:bg++eJ74T7DYL3DlIpiwknrtfdUA9oP/M4fL+Pp
github.com/jpillora/requestlog v1.0.0/go.mod h1:HTWQb7QfDc2jtHnWe2XEIEeJB7gJPnVdpNn52HXPvy8=
github.com/jpillora/sizestr v1.0.0 h1:4tr0FLxs1Mtq3TnsLDV+GYUWG7Q26a6s+tV5Zfw2ygw=
github.com/jpillora/sizestr v1.0.0/go.mod h1:bUhLv4ctkknatr6gR42qPxirmd5+ds1u7mzD+MZ33f0=
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
9 changes: 8 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,10 @@ var clientHelp = `

which does reverse port forwarding, sharing <remote-host>:<remote-port>
from the client to the server's <local-interface>:<local-port>.
additionally, P can be appended after the R flag to send a PROXY v2 header
which can be used to identify the original source of incoming connections:

RP:<local-interface>:<local-port>:<remote-host>:<remote-port>/<protocol>

example remotes

Expand All @@ -333,6 +337,7 @@ var clientHelp = `
socks
5000:socks
R:2222:localhost:22
RP:2222:localhost:22
R:socks
R:5000:socks
stdio:example.com:22
Expand All @@ -350,7 +355,9 @@ var clientHelp = `
will be proxied through the client which specified the remote.
Reverse remotes specifying "R:socks" will listen on the server's
default socks port (1080) and terminate the connection at the
client's internal SOCKS5 proxy.
client's internal SOCKS5 proxy. Additionally, P can be appended after
the R prefix to have the chisel server pass a PROXY v2 header
which can be used to identify the original source of incoming connections.

When stdio is used as local-host, the tunnel will connect standard
input/output of this program with the remote. This is useful when
Expand Down
23 changes: 18 additions & 5 deletions share/settings/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,28 @@ import (
type Remote struct {
LocalHost, LocalPort, LocalProto string
RemoteHost, RemotePort, RemoteProto string
Socks, Reverse, Stdio bool
Socks, Reverse, Stdio, ProxyProto bool
}

const revPrefix = "R:"
const revProxyPrefix = "RP:"

func DecodeRemote(s string) (*Remote, error) {
proxy := false
reverse := false
if strings.HasPrefix(s, revPrefix) {
if strings.HasPrefix(s, revProxyPrefix) {
s = strings.TrimPrefix(s, revProxyPrefix)
reverse = true
proxy = true
} else if strings.HasPrefix(s, revPrefix) {
s = strings.TrimPrefix(s, revPrefix)
reverse = true
}
parts := regexp.MustCompile(`(\[[^\[\]]+\]|[^\[\]:]+):?`).FindAllStringSubmatch(s, -1)
if len(parts) <= 0 || len(parts) >= 5 {
return nil, errors.New("Invalid remote")
}
r := &Remote{Reverse: reverse}
r := &Remote{Reverse: reverse, ProxyProto: proxy}
//parse from back to front, to set 'remote' fields first,
//then to set 'local' fields second (allows the 'remote' side
//to provide the defaults)
Expand Down Expand Up @@ -129,6 +135,9 @@ func DecodeRemote(s string) (*Remote, error) {
if r.Stdio && r.Reverse {
return nil, errors.New("stdio cannot be reversed")
}
if r.ProxyProto && !r.Reverse {
return nil, errors.New("cannot use proxy protocol for a non-reversed remote")
}
return r, nil
}

Expand Down Expand Up @@ -165,7 +174,9 @@ func L4Proto(s string) (head, proto string) {
//implement Stringer
func (r Remote) String() string {
sb := strings.Builder{}
if r.Reverse {
if r.Reverse && r.ProxyProto {
sb.WriteString(revProxyPrefix)
} else if r.Reverse {
sb.WriteString(revPrefix)
}
sb.WriteString(strings.TrimPrefix(r.Local(), "0.0.0.0:"))
Expand All @@ -187,7 +198,9 @@ func (r Remote) Encode() string {
if r.RemoteProto == "udp" {
remote += "/udp"
}
if r.Reverse {
if r.Reverse && r.ProxyProto {
return "RP:" + local + ":" + remote
} else if r.Reverse {
return "R:" + local + ":" + remote
}
return local + ":" + remote
Expand Down
23 changes: 23 additions & 0 deletions share/settings/remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ func TestRemoteDecode(t *testing.T) {
},
"R:0.0.0.0:80:google.com:80",
},
{
"RP:google.com:80",
Remote{
LocalPort: "80",
RemoteHost: "google.com",
RemotePort: "80",
Reverse: true,
ProxyProto: true,
},
"RP:0.0.0.0:80:google.com:80",
},
{
"示例網站.com:80",
Remote{
Expand Down Expand Up @@ -111,6 +122,18 @@ func TestRemoteDecode(t *testing.T) {
},
"R:[::]:3000:[::1]:3000",
},
{
"RP:[::]:3000:[::1]:3000",
Remote{
LocalHost: "[::]",
LocalPort: "3000",
RemoteHost: "[::1]",
RemotePort: "3000",
Reverse: true,
ProxyProto: true,
},
"RP:[::]:3000:[::1]:3000",
},
} {
//expected defaults
expected := test.Output
Expand Down
17 changes: 17 additions & 0 deletions share/tunnel/tunnel_in_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/jpillora/chisel/share/cio"
"github.com/jpillora/chisel/share/settings"
"github.com/jpillora/sizestr"
"github.com/pires/go-proxyproto"
"golang.org/x/crypto/ssh"
)

Expand Down Expand Up @@ -144,6 +145,22 @@ func (p *Proxy) pipeRemote(ctx context.Context, src io.ReadWriteCloser) {
return
}
go ssh.DiscardRequests(reqs)
//if proxy protocol is requested, send the header
if p.remote.ProxyProto {
conn, ok := src.(net.Conn)
if !ok {
//this should never happen, and if it does, something has gone horribly wrong (file an issue)
panic("attempted to use proxy protocol for a source which is not a network connection")
}

header := proxyproto.HeaderProxyFromAddrs(2, conn.RemoteAddr(), conn.LocalAddr())
s, err := header.WriteTo(dst)
if err != nil {
l.Infof("Stream error: %s", err)
return
}
l.Debugf("PROXY v2 header (sent %s)", sizestr.ToString(s))
}
//then pipe
s, r := cio.Pipe(src, dst)
l.Debugf("Close (sent %s received %s)", sizestr.ToString(s), sizestr.ToString(r))
Expand Down
15 changes: 14 additions & 1 deletion share/tunnel/tunnel_in_proxy_udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/gob"
"fmt"
"github.com/pires/go-proxyproto"
"io"
"net"
"strings"
Expand Down Expand Up @@ -102,8 +103,20 @@ func (u *udpListener) runInbound(ctx context.Context) error {
}
return u.Errorf("inbound-udpchan: %w", err)
}
//send over channel, including source address
b := buff[:n]
//if proxy protocol is requested, prepend the header
if u.remote.ProxyProto {
//NOTE: LocalAddr for UDP doesn't actually get the destination IP in the packet
//getting that information is non-trivial and non-portable from what I can see
//therefore, this will suffice for now
header := proxyproto.HeaderProxyFromAddrs(2, addr, u.inbound.LocalAddr())
formatted, err := header.Format()
if err != nil {
return u.Errorf("header format: %w", err)
}
b = append(formatted, b...)
}
//send over channel, including source address
if err := uc.encode(addr.String(), b); err != nil {
if strings.HasSuffix(err.Error(), "EOF") {
continue //dropped packet...
Expand Down
21 changes: 21 additions & 0 deletions test/e2e/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,24 @@ func TestReverse(t *testing.T) {
t.Fatalf("expected exclamation mark added")
}
}

func TestReverseProxyProtocol(t *testing.T) {
tmpPort := availablePort()
//setup server, client, fileserver
teardown := simpleSetup(t,
&chserver.Config{
Reverse: true,
},
&chclient.Config{
Remotes: []string{"RP:" + tmpPort + ":$FILEPORT"},
})
defer teardown()
//test remote (this goes through the server and out the client)
result, err := post("http://localhost:"+tmpPort, "foo")
if err != nil {
t.Fatal(err)
}
if result != "foo!" {
t.Fatalf("expected exclamation mark added")
}
}
7 changes: 6 additions & 1 deletion test/e2e/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package e2e_test

import (
"context"
"github.com/pires/go-proxyproto"
"io"
"log"
"net"
Expand Down Expand Up @@ -44,14 +45,18 @@ func (tl *testLayout) setup(t *testing.T) (server *chserver.Server, client *chcl
if err != nil {
t.Fatal(err)
}
pl := &proxyproto.Listener{
Listener: fl,
}
log.Printf("fileserver: listening on %s", fileAddr)
go func() {
f.Serve(fl)
f.Serve(pl)
cancel()
}()
go func() {
<-ctx.Done()
f.Close()
pl.Close()
}()
}
//server
Expand Down