Skip to content

Commit

Permalink
feat: add receive proxy protocol support
Browse files Browse the repository at this point in the history
  • Loading branch information
haveachin committed Feb 3, 2024
1 parent d278315 commit 7e87d71
Show file tree
Hide file tree
Showing 13 changed files with 267 additions and 132 deletions.
8 changes: 5 additions & 3 deletions cmd/infrared/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ func main() {

log.Info().Msg("Starting Infrared")

ir.AddServerConfig()

if err := run(); err != nil {
log.Fatal().
Err(err).
Expand Down Expand Up @@ -125,7 +123,11 @@ func run() error {
if errors.Is(err, ir.ErrNoServers) {

Check failure on line 123 in cmd/infrared/main.go

View workflow job for this annotation

GitHub Actions / Lint

ifElseChain: rewrite if-else to switch statement (gocritic)
log.Fatal().
Str("docs", "https://infrared.dev/config/proxies").
Msg("No proxy configs found; check the docs")
Msg("No proxy configs found; Check the docs")
} else if errors.Is(err, ir.ErrNoTrustedCIDRs) {
log.Fatal().
Str("docs", "https://infrared.dev/features/proxy-protocol#receive-proxy-protocol").
Msg("Receive PROXY Protocol enabled, but no CIDRs specified; Check the docs")
} else if err != nil {
return err
}
Expand Down
17 changes: 17 additions & 0 deletions configs/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@
#
bind: 0.0.0.0:25565

# This is for receiving PROXY Protocol Headers
#
proxyProtocol:
# Set this to true to enable it.
# You also need to set trusted CIDRs to use this feature.
# You can only receive PROXY Protocol Headers from trusted CIDRs.
#
receive: false

# List all your trusted CIDRs here.
# A CIDR is basically a way to talk about a whole range of IPs
# instead of just one. See here for more info:
# https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#IPv4_CIDR_blocks
#
trustedCIDRs:
- 127.0.0.1/32

# Maximum duration between packets before the client gets timed out.
#
keepAliveTimeout: 30s
Expand Down
12 changes: 1 addition & 11 deletions configs/haproxy.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@
global
maxconn 20000
log stdout local0 debug
user haproxy
chroot /usr/share/haproxy
pidfile /run/haproxy.pid
daemon

defaults
log global
Expand All @@ -20,11 +16,6 @@ resolvers nameserver
nameserver ns1 1.1.1.1:53
nameserver ns2 8.8.8.8:53

#listen minecraft
# bind :25500
# mode tcp
# server s1 127.0.0.1:25565 send-proxy-v2 resolvers nameserver

frontend minecraft_fe
maxconn 2000
mode tcp
Expand All @@ -33,5 +24,4 @@ frontend minecraft_fe

backend minecraft_be
mode tcp
# server s1 185.232.71.248:25565 send-proxy-v2 resolvers nameserver
server s1 127.0.0.1:25565 send-proxy-v2 resolvers nameserver
server s1 127.0.0.1:25565 send-proxy-v2 resolvers nameserver
2 changes: 1 addition & 1 deletion configs/proxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ domains:
addresses:
- 127.0.0.1:25565

# Send a Proxy Protocol v2 Header to the server to
# Send a PROXY Protocol Header to the server to
# forward the players IP address
#
#sendProxyProtocol: true
11 changes: 3 additions & 8 deletions deployments/docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,14 @@ services:
- infrared.java.servers.devserver.address=:25566

haproxy:
image: haproxy
image: haproxy:alpine
container_name: infrared-dev-haproxy
sysctls:
- net.ipv4.ip_unprivileged_port_start=0
volumes:
- ../.dev/haproxy:/usr/local/etc/haproxy:ro
ports:
- 25567:25565/tcp
networks:
- infrared
network_mode: host

redis:
image: redis
image: redis:alpine
container_name: infrared-dev-redis
ports:
- 6379:6379/tcp
Expand Down
8 changes: 4 additions & 4 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export default defineConfig({
{
text: 'Features',
items: [
{ text: 'PROXY Protocol', link: '/features/forward-player-ips' },
{ text: 'Rate Limiter', link: '/features/rate-limit-ips' },
{ text: 'PROXY Protocol', link: '/features/proxy-protocol' },
{ text: 'Rate Limiter', link: '/features/rate-limiter' },
]
},
{
Expand Down Expand Up @@ -58,12 +58,12 @@ export default defineConfig({
{
text: 'Features',
items: [
{ text: 'Forward Player IPs', link: '/features/forward-player-ips' },
{ text: 'PROXY Protocol', link: '/features/proxy-protocol' },
{
text: 'Filters',
link: '/features/filters',
items: [
{ text: 'Rate Limit IPs', link: '/features/rate-limit-ips' },
{ text: 'Rate Limiter', link: '/features/rate-limiter' },
]
}
]
Expand Down
16 changes: 0 additions & 16 deletions docs/features/forward-player-ips.md

This file was deleted.

43 changes: 43 additions & 0 deletions docs/features/proxy-protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# PROXY Protocol

Infrared supportes [PROXY Protocol v2]().

## Receive PROXY Protocol

You can receive PROXY Protocol Headers, but you **need** to specify your trusted [CIDRs](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#IPv4_CIDR_blocks).
To enable it in Infrared you just have to change this in you [global config](../config/index.md):

```yml
# This is for receiving PROXY Protocol Headers
#
proxyProtocol:
# Set this to true to enable it.
# You also need to set trusted CIDRs to use this feature.
# You can only receive PROXY Protocol Headers from trusted CIDRs.
#
receive: false

# List all your trusted CIDRs here.
# A CIDR is basically a way to talk about a whole range of IPs
# instead of just one.
#
trustedCIDRs:
- 127.0.0.1/32
```
## Forward Player IPs
You can forward the player IPs via PROXY Protocol.
To enable it in Infrared you just have to change this in you [**proxy config**](../config/proxies.md):
```yml
# Send a PROXY Protocol Header to the server to
# forward the players IP address.
#
#sendProxyProtocol: true // [!code --]
sendProxyProtocol: true // [!code ++]
```
## Paper
In Paper you have to enable it also to work.
See [the Paper documentation on PROXY Protocol](https://docs.papermc.io/paper/reference/global-configuration#proxies_proxy_protocol) for more.
File renamed without changes.
126 changes: 62 additions & 64 deletions pkg/infrared/infrared.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,18 @@ import (
"time"

"github.com/haveachin/infrared/pkg/infrared/protocol"
"github.com/pires/go-proxyproto"
"github.com/rs/zerolog"
)

type Config struct {
BindAddr string `yaml:"bind"`
ServerConfigs []ServerConfig `yaml:"servers"`
FiltersConfig FiltersConfig `yaml:"filters"`
KeepAliveTimeout time.Duration `yaml:"keepAliveTimeout"`
BindAddr string `yaml:"bind"`
KeepAliveTimeout time.Duration `yaml:"keepAliveTimeout"`
ServerConfigs []ServerConfig `yaml:"servers"`
FiltersConfig FiltersConfig `yaml:"filters"`
ProxyProtocolConfig ProxyProtocolConfig `yaml:"proxyProtocol"`
}

type ConfigFunc func(cfg *Config)

func WithBindAddr(bindAddr string) ConfigFunc {
return func(cfg *Config) {
cfg.BindAddr = bindAddr
}
}

func AddServerConfig(fns ...ServerConfigFunc) ConfigFunc {
return func(cfg *Config) {
var sCfg ServerConfig
for _, fn := range fns {
fn(&sCfg)
}
cfg.ServerConfigs = append(cfg.ServerConfigs, sCfg)
}
}

func WithKeepAliveTimeout(d time.Duration) ConfigFunc {
return func(cfg *Config) {
cfg.KeepAliveTimeout = d
}
}

func DefaultConfig() Config {
func NewConfig() Config {
return Config{
BindAddr: ":25565",
KeepAliveTimeout: 30 * time.Second,
Expand All @@ -54,7 +30,49 @@ func DefaultConfig() Config {
WindowLength: time.Second,
},
},
ProxyProtocolConfig: ProxyProtocolConfig{
TrustedCIDRs: make([]string, 0),
},
}
}

func (cfg Config) WithBindAddr(bindAddr string) Config {
cfg.BindAddr = bindAddr
return cfg
}

func (cfg Config) AddServerConfig(fns ...ServerConfigFunc) Config {
var sCfg ServerConfig
for _, fn := range fns {
fn(&sCfg)
}
cfg.ServerConfigs = append(cfg.ServerConfigs, sCfg)
return cfg
}

func (cfg Config) WithKeepAliveTimeout(d time.Duration) Config {
cfg.KeepAliveTimeout = d
return cfg
}

func (cfg Config) WithProxyProtocolReceive(receive bool) Config {
cfg.ProxyProtocolConfig.Receive = receive
return cfg
}

func (cfg Config) WithProxyProtocolTrustedCIDRs(trustedCIDRs ...string) Config {
cfg.ProxyProtocolConfig.TrustedCIDRs = trustedCIDRs
return cfg
}

func (cfg Config) WithRateLimiterWindowLength(windowLength time.Duration) Config {
cfg.FiltersConfig.RateLimiter.WindowLength = windowLength
return cfg
}

func (cfg Config) WithRateLimiterRequestLimit(requestLimit int) Config {
cfg.FiltersConfig.RateLimiter.RequestLimit = requestLimit
return cfg
}

type ConfigProvider interface {
Expand Down Expand Up @@ -89,13 +107,8 @@ type Infrared struct {
sr ServerRequester
}

func New(fns ...ConfigFunc) *Infrared {
cfg := DefaultConfig()
for _, fn := range fns {
fn(&cfg)
}

return NewWithConfig(cfg)
func New() *Infrared {
return NewWithConfig(NewConfig())
}

func NewWithConfigProvider(prv ConfigProvider) *Infrared {
Expand Down Expand Up @@ -127,6 +140,18 @@ func (ir *Infrared) initListener() error {
}
}

if ir.cfg.ProxyProtocolConfig.Receive {
fn := ir.NewListenerFunc
ir.NewListenerFunc = func(addr string) (net.Listener, error) {
l, err := fn(addr)
if err != nil {
return nil, err
}

return newProxyProtocolListener(l, ir.cfg.ProxyProtocolConfig.TrustedCIDRs)
}
}

l, err := ir.NewListenerFunc(ir.cfg.BindAddr)
if err != nil {
return err
Expand Down Expand Up @@ -329,30 +354,3 @@ func (ir *Infrared) pipe(dst io.WriteCloser, src io.ReadCloser, srcClosedChan ch

srcClosedChan <- struct{}{}
}

func writeProxyProtocolHeader(addr net.Addr, rc net.Conn) error {
rcAddr := rc.RemoteAddr()
tcpAddr, ok := rcAddr.(*net.TCPAddr)
if !ok {
panic("not a tcp connection")
}

tp := proxyproto.TCPv4
if tcpAddr.IP.To4() == nil {
tp = proxyproto.TCPv6
}

header := &proxyproto.Header{
Version: 2,
Command: proxyproto.PROXY,
TransportProtocol: tp,
SourceAddr: addr,
DestinationAddr: rcAddr,
}

if _, err := header.WriteTo(rc); err != nil {
return err
}

return nil
}
Loading

0 comments on commit 7e87d71

Please sign in to comment.