mirror of
https://github.com/onyx-and-iris/q3rcon-proxy.git
synced 2025-07-17 01:01:51 +00:00
133 lines
3.9 KiB
Go
133 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
udpproxy "github.com/onyx-and-iris/q3rcon-proxy"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/urfave/cli/v3"
|
|
)
|
|
|
|
// proxyConfig holds the configuration for a single UDP proxy server.
|
|
type proxyConfig struct {
|
|
proxyHost string
|
|
targetHost string
|
|
portsMapping []string
|
|
sessionTimeout int
|
|
}
|
|
|
|
func main() {
|
|
cmd := &cli.Command{
|
|
Name: "q3rcon-proxy",
|
|
Usage: "A Quake 3 RCON proxy server",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "proxy-host",
|
|
Value: "0.0.0.0",
|
|
Usage: "Proxy host address",
|
|
Sources: cli.EnvVars("Q3RCON_PROXY_HOST"),
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "target-host",
|
|
Value: "localhost",
|
|
Usage: "Target host address",
|
|
Sources: cli.EnvVars("Q3RCON_TARGET_HOST"),
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "ports-mapping",
|
|
Usage: "Proxy and target ports (proxy:target)",
|
|
Sources: cli.EnvVars("Q3RCON_PORTS_MAPPING"),
|
|
Required: true,
|
|
Action: func(ctx context.Context, cmd *cli.Command, v string) error {
|
|
// Validate the ports mapping
|
|
for mapping := range strings.SplitSeq(v, ";") {
|
|
ports := strings.Split(mapping, ":")
|
|
if len(ports) != 2 {
|
|
return fmt.Errorf("invalid ports mapping: %s", mapping)
|
|
}
|
|
proxyPort, err := strconv.Atoi(ports[0])
|
|
if err != nil || proxyPort < 1 || proxyPort > 65535 {
|
|
return fmt.Errorf("invalid proxy port: %s", ports[0])
|
|
}
|
|
targetPort, err := strconv.Atoi(ports[1])
|
|
if err != nil || targetPort < 1 || targetPort > 65535 {
|
|
return fmt.Errorf("invalid target port: %s", ports[1])
|
|
}
|
|
if proxyPort == targetPort {
|
|
return fmt.Errorf("proxy and target ports cannot be the same: %s", mapping)
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "session-timeout",
|
|
Value: 20,
|
|
Usage: "Session timeout in minutes",
|
|
Sources: cli.EnvVars("Q3RCON_SESSION_TIMEOUT"),
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "loglevel",
|
|
Value: "info",
|
|
Usage: "Log level (trace, debug, info, warn, error, fatal, panic)",
|
|
Sources: cli.EnvVars("Q3RCON_LOGLEVEL"),
|
|
},
|
|
},
|
|
Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
|
|
logLevel, err := log.ParseLevel(cmd.String("loglevel"))
|
|
if err != nil {
|
|
return ctx, fmt.Errorf("invalid log level: %w", err)
|
|
}
|
|
log.SetLevel(logLevel)
|
|
return ctx, nil
|
|
},
|
|
Action: func(_ context.Context, cmd *cli.Command) error {
|
|
errChan := make(chan error)
|
|
|
|
for mapping := range strings.SplitSeq(cmd.String("ports-mapping"), ";") {
|
|
cfg := proxyConfig{
|
|
proxyHost: cmd.String("proxy-host"),
|
|
targetHost: cmd.String("target-host"),
|
|
portsMapping: strings.Split(mapping, ":"),
|
|
sessionTimeout: cmd.Int("session-timeout"),
|
|
}
|
|
|
|
go launchProxy(cfg, errChan)
|
|
}
|
|
|
|
// Under normal circumstances, the main goroutine will block here.
|
|
// If we receive an error we will log it and exit
|
|
return <-errChan
|
|
},
|
|
}
|
|
|
|
log.Fatal(cmd.Run(context.Background(), os.Args))
|
|
}
|
|
|
|
// launchProxy initializes the UDP proxy server with the given configuration.
|
|
// It listens on the specified proxy host and port, and forwards traffic to the target host and port.
|
|
// server.ListenAndServe blocks until the server is stopped or an error occurs.
|
|
func launchProxy(cfg proxyConfig, errChan chan<- error) {
|
|
proxyPort, targetPort := cfg.portsMapping[0], cfg.portsMapping[1]
|
|
|
|
hostAddr := fmt.Sprintf("%s:%s", cfg.proxyHost, proxyPort)
|
|
proxyAddr := fmt.Sprintf("%s:%s", cfg.targetHost, targetPort)
|
|
|
|
server, err := udpproxy.New(
|
|
hostAddr, proxyAddr,
|
|
udpproxy.WithSessionTimeout(time.Duration(cfg.sessionTimeout)*time.Minute))
|
|
if err != nil {
|
|
errChan <- fmt.Errorf("failed to create proxy: %w", err)
|
|
return
|
|
}
|
|
|
|
log.Printf("q3rcon-proxy initialized: [proxy] (%s) [target] (%s)", hostAddr, proxyAddr)
|
|
|
|
errChan <- server.ListenAndServe()
|
|
}
|