From b4e949b5ab45ce4d57710edace29b34f86a6b7f1 Mon Sep 17 00:00:00 2001 From: Kupchenko Alexander Date: Sun, 22 Jun 2025 23:49:19 +0700 Subject: [PATCH 1/2] make min max message and padding length configurable --- README.md | 7 +++-- internal/cmd/cmd.go | 15 +++++++++++ internal/cmd/options.go | 11 ++++++++ internal/pipe/server.go | 29 ++++++++++++++++++-- internal/tunnel/msgreadwriter.go | 45 ++++++++++++++++---------------- 5 files changed, 80 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 8449dfc..9d0db36 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ need to make some adjustments to the WireGuard client configuration: * Use the address of the `udptlspipe` client as an endpoint in your WireGuard client configuration. * Add `MTU = 1280` to the `[Peer]` section of both WireGuard client and server - configuration files. + configuration files. The MTU can be increased by passing custom values for `--max-message-length`, `--min-message-length`, and `--max-padding-length`. * Exclude the `udptlspipe` server IP from `AllowedIPs` in the WireGuard client configuration. This [calculator][wireguardcalculator] may help you. @@ -237,6 +237,9 @@ Application Options: --tls-keyfile= Path to the private key for the cert specified in tls-certfile. --probe-reverseproxyurl= Unauthorized requests and probes will be proxied to the URL. -v, --verbose Verbose output (optional). + --max-message-length= Max message length (default 1320) + --min-message-length= Min message length (default 100) + --max-padding-length= Max padding length (default 256) Help Options: -h, --help Show this help message @@ -248,4 +251,4 @@ Help Options: * [X] Certificate configuration. * [X] Use WebSocket for transport instead of the custom binary proto. * [ ] Use several upstream connections instead of a single one. -* [ ] Automatic TLS certs generation (let's encrypt, lego). \ No newline at end of file +* [ ] Automatic TLS certs generation (let's encrypt, lego). diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 9da87d2..260e791 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -54,6 +54,9 @@ func Main() { VerifyCertificate: o.VerifyCertificate, TLSServerName: o.TLSServerName, ProbeReverseProxyURL: o.ProbeReverseProxyURL, + MaxMessageLength: o.MaxMessageLength, + MinMessageLength: o.MinMessageLength, + MaxPaddingLength: o.MaxPaddingLength, } if o.TLSCertPath != "" { @@ -73,6 +76,18 @@ func Main() { cfg.TLSCertificate = cert } + if o.MaxMessageLength == 0 { + cfg.MaxMessageLength = 1320 + } + + if o.MaxPaddingLength == 0 { + cfg.MaxPaddingLength = 256 + } + + if o.MinMessageLength == 0 { + cfg.MinMessageLength = 100 + } + srv, err := pipe.NewServer(cfg) if err != nil { log.Error("Failed to initialize server: %v", err) diff --git a/internal/cmd/options.go b/internal/cmd/options.go index 5305075..33955ea 100644 --- a/internal/cmd/options.go +++ b/internal/cmd/options.go @@ -61,6 +61,17 @@ type Options struct { // Verbose defines whether we should write the DEBUG-level log or not. Verbose bool `yaml:"verbose" short:"v" long:"verbose" description:"Verbose output (optional)." optional:"yes" optional-value:"true"` + + // MaxMessageLength is the maximum length that is safe to use. + MaxMessageLength int `yaml:"max-message-length" long:"max-message-length" description:"Max message length (default 1320)" value-name:"" optional:"yes"` + + // MinMessageLength is the minimum message size. If the message is smaller, it + // will be padded with random bytes. + MinMessageLength int `yaml:"min-message-length" long:"min-message-length" description:"Min message length (default 100)" value-name:"" optional:"yes"` + + // MaxPaddingLength is the maximum size of a random padding that's added to + // every message. + MaxPaddingLength int `yaml:"max-padding-length" long:"max-padding-length" description:"Max padding length (default 256)" value-name:"" optional:"yes"` } // type check diff --git a/internal/pipe/server.go b/internal/pipe/server.go index 56b2be5..3eccbb6 100644 --- a/internal/pipe/server.go +++ b/internal/pipe/server.go @@ -75,6 +75,17 @@ type Server struct { // wg tracks active workers. Stop won't finish until there is at least // won't finish until there's at least one active worker. wg sync.WaitGroup + + // maxMessageLength is the maximum length that is safe to use. + maxMessageLength int + + // minMessageLength is the minimum message size. If the message is smaller, it + // will be padded with random bytes. + minMessageLength int + + // maxPaddingLength is the maximum size of a random padding that's added to + // every message. + maxPaddingLength int } // Config represents the server configuration. @@ -125,6 +136,17 @@ type Config struct { // proxy to respond to unauthorized or proxy requests. If not specified, // it will respond with a stub page 403 Forbidden. ProbeReverseProxyURL string + + // MaxMessageLength is the maximum length that is safe to use. + MaxMessageLength int + + // MinMessageLength is the minimum message size. If the message is smaller, it + // will be padded with random bytes. + MinMessageLength int + + // MaxPaddingLength is the maximum size of a random padding that's added to + // every message. + MaxPaddingLength int } // createTLSConfig creates a TLS configuration as per the server configuration. @@ -175,6 +197,9 @@ func NewServer(config *Config) (s *Server, err error) { srcConnsMu: &sync.Mutex{}, dstConns: map[net.Conn]struct{}{}, dstConnsMu: &sync.Mutex{}, + maxMessageLength: config.MaxMessageLength, + minMessageLength: config.MinMessageLength, + maxPaddingLength: config.MaxPaddingLength, } s.tlsConfig, err = createTLSConfig(config) @@ -649,9 +674,9 @@ func (s *Server) processConn(rwc io.ReadWriteCloser) { // connection between them needs to be wrapped. In server mode it is the // source connection, in client mode it is the destination connection. if s.serverMode { - srcRw = tunnel.NewMsgReadWriter(srcRw) + srcRw = tunnel.NewMsgReadWriter(srcRw, s.maxMessageLength, s.minMessageLength, s.maxPaddingLength) } else { - dstRw = tunnel.NewMsgReadWriter(dstRw) + dstRw = tunnel.NewMsgReadWriter(dstRw, s.maxMessageLength, s.minMessageLength, s.maxPaddingLength) } tunnel.Tunnel(s.String(), srcRw, dstRw) diff --git a/internal/tunnel/msgreadwriter.go b/internal/tunnel/msgreadwriter.go index 0c7c12b..985b9fa 100644 --- a/internal/tunnel/msgreadwriter.go +++ b/internal/tunnel/msgreadwriter.go @@ -9,27 +9,26 @@ import ( "github.com/AdguardTeam/golibs/log" ) -// MaxMessageLength is the maximum length that is safe to use. -// TODO(ameshkov): Make it configurable. -const MaxMessageLength = 1320 - -// MinMessageLength is the minimum message size. If the message is smaller, it -// will be padded with random bytes. -const MinMessageLength = 100 - -// MaxPaddingLength is the maximum size of a random padding that's added to -// every message. -const MaxPaddingLength = 256 - -// MsgReadWriter is a wrapper over io.ReadWriter that encodes messages written -// to and read from the base writer. type MsgReadWriter struct { base io.ReadWriter + // MaxMessageLength is the maximum length that is safe to use. + maxMessageLength int + // MaxPaddingLength is the maximum size of a random padding that's added to + // every message. + maxPaddingLength int + // MinMessageLength is the minimum message size. If the message is smaller, it + // will be padded with random bytes. + minMessageLength int } // NewMsgReadWriter creates a new instance of *MsgReadWriter. -func NewMsgReadWriter(base io.ReadWriter) (rw *MsgReadWriter) { - return &MsgReadWriter{base: base} +func NewMsgReadWriter(base io.ReadWriter, maxMessageLength, minMessageLength, maxPaddingLength int) (rw *MsgReadWriter) { + return &MsgReadWriter{ + base: base, + maxMessageLength: maxMessageLength, + minMessageLength: minMessageLength, + maxPaddingLength: maxPaddingLength, + } } // type check @@ -38,13 +37,13 @@ var _ io.ReadWriter = (*MsgReadWriter)(nil) // Read implements the io.ReadWriter interface for *MsgReadWriter. func (rw *MsgReadWriter) Read(b []byte) (n int, err error) { // Read the main message (always goes first). - msg, err := readPrefixed(rw.base) + msg, err := rw.readPrefixed(rw.base) if err != nil { return 0, err } // Skip padding. - _, err = readPrefixed(rw.base) + _, err = rw.readPrefixed(rw.base) if err != nil { return 0, err } @@ -62,11 +61,11 @@ func (rw *MsgReadWriter) Read(b []byte) (n int, err error) { func (rw *MsgReadWriter) Write(b []byte) (n int, err error) { // Create random padding to make it harder to understand what's inside // the tunnel. - minLength := MinMessageLength - len(b) + minLength := rw.minMessageLength - len(b) if minLength <= 0 { minLength = 1 } - maxLength := MaxPaddingLength + maxLength := rw.maxPaddingLength if maxLength <= minLength { maxLength = minLength + 1 } @@ -103,19 +102,19 @@ func pack(b, padding []byte) (msg []byte) { } // readPrefixed reads a 2-byte prefixed byte array from the reader. -func readPrefixed(r io.Reader) (b []byte, err error) { +func (rw *MsgReadWriter) readPrefixed(r io.Reader) (b []byte, err error) { var length uint16 err = binary.Read(r, binary.BigEndian, &length) if err != nil { return nil, err } - if length > MaxMessageLength { + if length > uint16(rw.maxMessageLength) { // Warn the user that this may not work correctly. log.Error( "Warning: received message of length %d larger than %d, considering reducing the MTU", length, - MaxMessageLength, + rw.maxMessageLength, ) } From a9e57528c20ddc29e042e5bad4a61ba16d228883 Mon Sep 17 00:00:00 2001 From: Kupchenko Alexander Date: Mon, 23 Jun 2025 00:57:10 +0700 Subject: [PATCH 2/2] set default values before printing configuration --- internal/cmd/cmd.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 260e791..3d1a238 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -43,6 +43,18 @@ func Main() { log.SetLevel(log.DEBUG) } + if o.MaxMessageLength == 0 { + o.MaxMessageLength = 1320 + } + + if o.MaxPaddingLength == 0 { + o.MaxPaddingLength = 256 + } + + if o.MinMessageLength == 0 { + o.MinMessageLength = 100 + } + log.Info("Configuration:\n%s", o) cfg := &pipe.Config{ @@ -76,18 +88,6 @@ func Main() { cfg.TLSCertificate = cert } - if o.MaxMessageLength == 0 { - cfg.MaxMessageLength = 1320 - } - - if o.MaxPaddingLength == 0 { - cfg.MaxPaddingLength = 256 - } - - if o.MinMessageLength == 0 { - cfg.MinMessageLength = 100 - } - srv, err := pipe.NewServer(cfg) if err != nil { log.Error("Failed to initialize server: %v", err)