From cf3437bb63506f14d6852e12db97bfd85f99ab72 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 19 Feb 2026 11:30:13 +0100 Subject: [PATCH] Add support of proxy protocol --- README.md | 6 ++++++ example.config.toml | 9 +++++++++ go.mod | 1 + go.sum | 2 ++ internal/cli/run_proxy.go | 10 ++++++++++ internal/config/config.go | 1 + internal/config/parse.go | 1 + internal/proxyprotocol/adapter.go | 20 ++++++++++++++++++++ internal/proxyprotocol/conn.go | 25 +++++++++++++++++++++++++ 9 files changed, 75 insertions(+) create mode 100644 internal/proxyprotocol/adapter.go create mode 100644 internal/proxyprotocol/conn.go diff --git a/README.md b/README.md index 09dae08..1a5bbb9 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,12 @@ that probably matter. way of doing business I suppose. I think the only viable way is to have a proxy that can be restored anywhere easily. +* Supports proxy protocol v1/v2 + +This makes integration with loadbalancers like HAProxy and ELB a first class +citizen by supporting their +[commuication protocols](https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt). + * **A single secret** I think that multiple secrets solve no problems and just complex diff --git a/example.config.toml b/example.config.toml index eb2c62c..68f7926 100644 --- a/example.config.toml +++ b/example.config.toml @@ -23,6 +23,15 @@ secret = "ee367a189aee18fa31c190054efd4a8e9573746f726167652e676f6f676c6561706973 # Host:port pair to run proxy on. bind-to = "0.0.0.0:3128" +# This defines what types of traffic mtg listens to. If you are not sure, +# then definitely keep it disable. Enable it only and only if incoming traffic +# is coming from some sort of load-balancer like HAProxy or ELB. +# https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt +# +# mtg uses a library that supports v1 and v2 versions of ProxyProtocol. +# default value is false. +# proxy-protocol-listener = false + # Defines how many concurrent connections are allowed to this proxy. # All other incoming connections are going to be dropped. concurrency = 8192 diff --git a/go.mod b/go.mod index b4c34f7..5520d48 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( require ( github.com/pelletier/go-toml/v2 v2.2.4 + github.com/pires/go-proxyproto v0.11.0 github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e github.com/yl2chen/cidranger v1.0.2 ) diff --git a/go.sum b/go.sum index c7d814b..13d71bb 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4= +github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/internal/cli/run_proxy.go b/internal/cli/run_proxy.go index 5357309..23580eb 100644 --- a/internal/cli/run_proxy.go +++ b/internal/cli/run_proxy.go @@ -10,6 +10,7 @@ import ( "github.com/9seconds/mtg/v2/antireplay" "github.com/9seconds/mtg/v2/events" "github.com/9seconds/mtg/v2/internal/config" + "github.com/9seconds/mtg/v2/internal/proxyprotocol" "github.com/9seconds/mtg/v2/internal/utils" "github.com/9seconds/mtg/v2/ipblocklist" "github.com/9seconds/mtg/v2/ipblocklist/files" @@ -17,6 +18,7 @@ import ( "github.com/9seconds/mtg/v2/mtglib" "github.com/9seconds/mtg/v2/network" "github.com/9seconds/mtg/v2/stats" + "github.com/pires/go-proxyproto" "github.com/rs/zerolog" "github.com/yl2chen/cidranger" ) @@ -275,6 +277,14 @@ func runProxy(conf *config.Config, version string) error { //nolint: funlen return fmt.Errorf("cannot start proxy: %w", err) } + if conf.ProxyProtocolListener.Get(false) { + listener = &proxyprotocol.ListenerAdapter{ + Listener: proxyproto.Listener{ + Listener: listener, + }, + } + } + ctx := utils.RootContext() go proxy.Serve(listener) //nolint: errcheck diff --git a/internal/config/config.go b/internal/config/config.go index 3bab778..74abdf0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,6 +25,7 @@ type Config struct { AllowFallbackOnUnknownDC TypeBool `json:"allowFallbackOnUnknownDc"` Secret mtglib.Secret `json:"secret"` BindTo TypeHostPort `json:"bindTo"` + ProxyProtocolListener TypeBool `json:"proxyProtocolListener"` PreferIP TypePreferIP `json:"preferIp"` DomainFrontingPort TypePort `json:"domainFrontingPort"` TolerateTimeSkewness TypeDuration `json:"tolerateTimeSkewness"` diff --git a/internal/config/parse.go b/internal/config/parse.go index fc1a148..7cdf6da 100644 --- a/internal/config/parse.go +++ b/internal/config/parse.go @@ -13,6 +13,7 @@ type tomlConfig struct { AllowFallbackOnUnknownDC bool `toml:"allow-fallback-on-unknown-dc" json:"allowFallbackOnUnknownDc,omitempty"` Secret string `toml:"secret" json:"secret"` BindTo string `toml:"bind-to" json:"bindTo"` + ProxyProtocolListener bool `toml:"proxy-protocol-listener" json:"proxyProtocolListener"` PreferIP string `toml:"prefer-ip" json:"preferIp,omitempty"` DomainFrontingPort uint `toml:"domain-fronting-port" json:"domainFrontingPort,omitempty"` TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"` diff --git a/internal/proxyprotocol/adapter.go b/internal/proxyprotocol/adapter.go new file mode 100644 index 0000000..3b69d7b --- /dev/null +++ b/internal/proxyprotocol/adapter.go @@ -0,0 +1,20 @@ +package proxyprotocol + +import ( + "net" + + "github.com/pires/go-proxyproto" +) + +type ListenerAdapter struct { + proxyproto.Listener +} + +func (l *ListenerAdapter) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + if err != nil { + return nil, err + } + + return connWrapper{conn.(*proxyproto.Conn)}, nil +} diff --git a/internal/proxyprotocol/conn.go b/internal/proxyprotocol/conn.go new file mode 100644 index 0000000..aff1457 --- /dev/null +++ b/internal/proxyprotocol/conn.go @@ -0,0 +1,25 @@ +package proxyprotocol + +import "github.com/pires/go-proxyproto" + +type connWrapper struct { + *proxyproto.Conn +} + +func (c connWrapper) CloseRead() error { + tcpConn, ok := c.TCPConn() + if !ok { + panic("we support only tcp connections") + } + + return tcpConn.CloseRead() +} + +func (c connWrapper) CloseWrite() error { + tcpConn, ok := c.TCPConn() + if !ok { + panic("we support only tcp connections") + } + + return tcpConn.CloseWrite() +}