From 8d29a929f729a3f8c1a02f883b9a73fb91eb3e6d Mon Sep 17 00:00:00 2001 From: jemmy1794 Date: Tue, 14 Oct 2025 21:30:32 +0800 Subject: [PATCH 1/2] Add support for Proxy Protocol V1 and V2 in streamproxy configuration - Updated HTML form to allow selection of Proxy Protocol version. - Backend and config struct now use ProxyProtocolVersion (int) instead of UseProxyProtocol (bool). - UI and API now pass and display Proxy Protocol version. TODO: UDP should only allow Proxy Protocol V2 --- src/go.mod | 1 + src/go.sum | 2 + src/mod/streamproxy/handler.go | 38 +++++++++---------- src/mod/streamproxy/streamproxy.go | 60 +++++++++++++++--------------- src/mod/streamproxy/tcpprox.go | 22 ++++++----- src/web/components/streamprox.html | 36 +++++++++--------- 6 files changed, 82 insertions(+), 77 deletions(-) diff --git a/src/go.mod b/src/go.mod index eab53e0..6d354b2 100644 --- a/src/go.mod +++ b/src/go.mod @@ -17,6 +17,7 @@ require ( github.com/likexian/whois v1.15.1 github.com/microcosm-cc/bluemonday v1.0.26 github.com/monperrus/crawler-user-agents v1.1.0 + github.com/pires/go-proxyproto v0.8.1 github.com/shirou/gopsutil/v4 v4.25.1 github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.0 diff --git a/src/go.sum b/src/go.sum index 9e8da09..dd2019c 100644 --- a/src/go.sum +++ b/src/go.sum @@ -610,6 +610,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c= github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc= +github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= +github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/src/mod/streamproxy/handler.go b/src/mod/streamproxy/handler.go index 147100e..33faa66 100644 --- a/src/mod/streamproxy/handler.go +++ b/src/mod/streamproxy/handler.go @@ -47,19 +47,19 @@ func (m *Manager) HandleAddProxyConfig(w http.ResponseWriter, r *http.Request) { useTCP, _ := utils.PostBool(r, "useTCP") useUDP, _ := utils.PostBool(r, "useUDP") - useProxyProtocol, _ := utils.PostBool(r, "useProxyProtocol") + ProxyProtocolVersion, _ := utils.PostInt(r, "proxyProtocolVersion") enableLogging, _ := utils.PostBool(r, "enableLogging") //Create the target config newConfigUUID := m.NewConfig(&ProxyRelayOptions{ - Name: name, - ListeningAddr: strings.TrimSpace(listenAddr), - ProxyAddr: strings.TrimSpace(proxyAddr), - Timeout: timeout, - UseTCP: useTCP, - UseUDP: useUDP, - UseProxyProtocol: useProxyProtocol, - EnableLogging: enableLogging, + Name: name, + ListeningAddr: strings.TrimSpace(listenAddr), + ProxyAddr: strings.TrimSpace(proxyAddr), + Timeout: timeout, + UseTCP: useTCP, + UseUDP: useUDP, + ProxyProtocolVersion: ProxyProtocolVersion, + EnableLogging: enableLogging, }) js, _ := json.Marshal(newConfigUUID) @@ -79,7 +79,7 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request) proxyAddr, _ := utils.PostPara(r, "proxyAddr") useTCP, _ := utils.PostBool(r, "useTCP") useUDP, _ := utils.PostBool(r, "useUDP") - useProxyProtocol, _ := utils.PostBool(r, "useProxyProtocol") + proxyProtocolVersion, _ := utils.PostInt(r, "proxyProtocolVersion") enableLogging, _ := utils.PostBool(r, "enableLogging") newTimeoutStr, _ := utils.PostPara(r, "timeout") @@ -94,15 +94,15 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request) // Create a new ProxyRuleUpdateConfig with the extracted parameters newConfig := &ProxyRuleUpdateConfig{ - InstanceUUID: configUUID, - NewName: newName, - NewListeningAddr: listenAddr, - NewProxyAddr: proxyAddr, - UseTCP: useTCP, - UseUDP: useUDP, - UseProxyProtocol: useProxyProtocol, - EnableLogging: enableLogging, - NewTimeout: newTimeout, + InstanceUUID: configUUID, + NewName: newName, + NewListeningAddr: listenAddr, + NewProxyAddr: proxyAddr, + UseTCP: useTCP, + UseUDP: useUDP, + ProxyProtocolVersion: proxyProtocolVersion, + EnableLogging: enableLogging, + NewTimeout: newTimeout, } // Call the EditConfig method to modify the configuration diff --git a/src/mod/streamproxy/streamproxy.go b/src/mod/streamproxy/streamproxy.go index c473e33..428ca54 100644 --- a/src/mod/streamproxy/streamproxy.go +++ b/src/mod/streamproxy/streamproxy.go @@ -23,42 +23,42 @@ import ( */ type ProxyRelayOptions struct { - Name string - ListeningAddr string - ProxyAddr string - Timeout int - UseTCP bool - UseUDP bool - UseProxyProtocol bool - EnableLogging bool + Name string + ListeningAddr string + ProxyAddr string + Timeout int + UseTCP bool + UseUDP bool + ProxyProtocolVersion int + EnableLogging bool } // ProxyRuleUpdateConfig is used to update the proxy rule config type ProxyRuleUpdateConfig struct { - InstanceUUID string //The target instance UUID to update - NewName string //New name for the instance, leave empty for no change - NewListeningAddr string //New listening address, leave empty for no change - NewProxyAddr string //New proxy target address, leave empty for no change - UseTCP bool //Enable TCP proxy, default to false - UseUDP bool //Enable UDP proxy, default to false - UseProxyProtocol bool //Enable Proxy Protocol, default to false - EnableLogging bool //Enable Logging TCP/UDP Message, default to true - NewTimeout int //New timeout for the connection, leave -1 for no change + InstanceUUID string //The target instance UUID to update + NewName string //New name for the instance, leave empty for no change + NewListeningAddr string //New listening address, leave empty for no change + NewProxyAddr string //New proxy target address, leave empty for no change + UseTCP bool //Enable TCP proxy, default to false + UseUDP bool //Enable UDP proxy, default to false + ProxyProtocolVersion int //Enable Proxy Protocol v1/v2, default to disabled + EnableLogging bool //Enable Logging TCP/UDP Message, default to true + NewTimeout int //New timeout for the connection, leave -1 for no change } type ProxyRelayInstance struct { /* Runtime Config */ - UUID string //A UUIDv4 representing this config - Name string //Name of the config - Running bool //Status, read only - AutoStart bool //If the service suppose to started automatically - ListeningAddress string //Listening Address, usually 127.0.0.1:port - ProxyTargetAddr string //Proxy target address - UseTCP bool //Enable TCP proxy - UseUDP bool //Enable UDP proxy - UseProxyProtocol bool //Enable Proxy Protocol - EnableLogging bool //Enable logging for ProxyInstance - Timeout int //Timeout for connection in sec + UUID string //A UUIDv4 representing this config + Name string //Name of the config + Running bool //Status, read only + AutoStart bool //If the service suppose to started automatically + ListeningAddress string //Listening Address, usually 127.0.0.1:port + ProxyTargetAddr string //Proxy target address + UseTCP bool //Enable TCP proxy + UseUDP bool //Enable UDP proxy + ProxyProtocolVersion int //Proxy Protocol v1/v2 + EnableLogging bool //Enable logging for ProxyInstance + Timeout int //Timeout for connection in sec /* Internal */ tcpStopChan chan bool //Stop channel for TCP listener @@ -178,7 +178,7 @@ func (m *Manager) NewConfig(config *ProxyRelayOptions) string { ProxyTargetAddr: config.ProxyAddr, UseTCP: config.UseTCP, UseUDP: config.UseUDP, - UseProxyProtocol: config.UseProxyProtocol, + ProxyProtocolVersion: config.ProxyProtocolVersion, EnableLogging: config.EnableLogging, Timeout: config.Timeout, tcpStopChan: nil, @@ -224,7 +224,7 @@ func (m *Manager) EditConfig(newConfig *ProxyRuleUpdateConfig) error { foundConfig.UseTCP = newConfig.UseTCP foundConfig.UseUDP = newConfig.UseUDP - foundConfig.UseProxyProtocol = newConfig.UseProxyProtocol + foundConfig.ProxyProtocolVersion = newConfig.ProxyProtocolVersion foundConfig.EnableLogging = newConfig.EnableLogging if newConfig.NewTimeout != -1 { diff --git a/src/mod/streamproxy/tcpprox.go b/src/mod/streamproxy/tcpprox.go index d16ad8d..b102807 100644 --- a/src/mod/streamproxy/tcpprox.go +++ b/src/mod/streamproxy/tcpprox.go @@ -11,6 +11,8 @@ import ( "sync" "sync/atomic" "time" + + proxyproto "github.com/pires/go-proxyproto" ) func isValidIP(ip string) bool { @@ -44,20 +46,22 @@ func (c *ProxyRelayInstance) connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.W wg.Done() } -func writeProxyProtocolHeaderV1(dst net.Conn, src net.Conn) error { +func WriteProxyProtocolHeader(dst net.Conn, src net.Conn, version int) error { clientAddr, ok1 := src.RemoteAddr().(*net.TCPAddr) proxyAddr, ok2 := src.LocalAddr().(*net.TCPAddr) if !ok1 || !ok2 { return errors.New("invalid TCP address for proxy protocol") } - header := fmt.Sprintf("PROXY TCP4 %s %s %d %d\r\n", - clientAddr.IP.String(), - proxyAddr.IP.String(), - clientAddr.Port, - proxyAddr.Port) + header := proxyproto.Header{ + Version: byte(version), + Command: proxyproto.PROXY, + TransportProtocol: proxyproto.TCPv4, + SourceAddr: clientAddr, + DestinationAddr: proxyAddr, + } - _, err := dst.Write([]byte(header)) + _, err := header.WriteTo(dst) return err } @@ -161,9 +165,9 @@ func (c *ProxyRelayInstance) Port2host(allowPort string, targetAddress string, s } c.LogMsg("[→] connect target address ["+targetAddress+"] success.", nil) - if c.UseProxyProtocol { + if c.ProxyProtocolVersion != 0 { c.LogMsg("[+] write proxy protocol header to target address ["+targetAddress+"]", nil) - err = writeProxyProtocolHeaderV1(target, conn) + err = WriteProxyProtocolHeader(target, conn, c.ProxyProtocolVersion) if err != nil { c.LogMsg("[x] Write proxy protocol header failed: "+err.Error(), nil) target.Close() diff --git a/src/web/components/streamprox.html b/src/web/components/streamprox.html index bddac54..3746a9b 100644 --- a/src/web/components/streamprox.html +++ b/src/web/components/streamprox.html @@ -74,14 +74,6 @@ Forward UDP request on this listening socket -
-
- - -
-
@@ -90,6 +82,15 @@
+
+ + + Select Proxy Protocol v1 / v2 to use (if any) +
@@ -138,7 +139,7 @@ function clearStreamProxyAddEditForm(){ $('#streamProxyForm').find('input:not([type=checkbox]), select').val(''); - $('#streamProxyForm select').dropdown('clear'); + $('#streamProxyForm select[name=proxyProtocolVersion]').dropdown('set selected', '0'); $("#streamProxyForm input[name=timeout]").val(10); $("#streamProxyForm .toggle.checkbox").checkbox("set unchecked"); } @@ -212,8 +213,10 @@ modeText.push("UDP") } - if (config.UseProxyProtocol){ - modeText.push("ProxyProtocol V1") + if (config.ProxyProtocolVersion === 1) { + modeText.push("ProxyProtocol V1"); + } else if (config.ProxyProtocolVersion === 2) { + modeText.push("ProxyProtocol V2"); } modeText = modeText.join(" & ") @@ -277,13 +280,8 @@ $(checkboxEle).checkbox("set unchecked"); } return; - }else if (key == "UseProxyProtocol"){ - let checkboxEle = $("#streamProxyForm input[name=useProxyProtocol]").parent(); - if (value === true){ - $(checkboxEle).checkbox("set checked"); - }else{ - $(checkboxEle).checkbox("set unchecked"); - } + }else if (key == "ProxyProtocolVersion") { + $("#streamProxyForm select[name=proxyProtocolVersion]").dropdown("set selected", value); return; }else if (key == "EnableLogging"){ let checkboxEle = $("#streamProxyForm input[name=enableLogging]").parent(); @@ -342,7 +340,7 @@ proxyAddr: $("#streamProxyForm input[name=proxyAddr]").val().trim(), useTCP: $("#streamProxyForm input[name=useTCP]")[0].checked , useUDP: $("#streamProxyForm input[name=useUDP]")[0].checked , - useProxyProtocol: $("#streamProxyForm input[name=useProxyProtocol]")[0].checked , + proxyProtocolVersion: parseInt($("#streamProxyForm select[name=proxyProtocolVersion]").val(), 10), enableLogging: $("#streamProxyForm input[name=enableLogging]")[0].checked , timeout: parseInt($("#streamProxyForm input[name=timeout]").val().trim()), }, From aa243f23eebe198f87699e5c478302d36f162494 Mon Sep 17 00:00:00 2001 From: jemmy1794 Date: Tue, 14 Oct 2025 21:56:11 +0800 Subject: [PATCH 2/2] Add WriteProxyProtocolHeaderUDP function to support Proxy Protocol v2 for UDP connections --- src/mod/streamproxy/udpprox.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/mod/streamproxy/udpprox.go b/src/mod/streamproxy/udpprox.go index dc761df..672688c 100644 --- a/src/mod/streamproxy/udpprox.go +++ b/src/mod/streamproxy/udpprox.go @@ -1,11 +1,14 @@ package streamproxy import ( + "bytes" "errors" "log" "net" "strings" "time" + + proxyproto "github.com/pires/go-proxyproto" ) /* @@ -82,6 +85,24 @@ func (c *ProxyRelayInstance) CloseAllUDPConnections() { }) } +// Write Proxy Protocol v2 header to UDP connection +func WriteProxyProtocolHeaderUDP(conn *net.UDPConn, srcAddr, dstAddr *net.UDPAddr) error { + header := proxyproto.Header{ + Version: 2, + Command: proxyproto.PROXY, + TransportProtocol: proxyproto.UDPv4, + SourceAddr: srcAddr, + DestinationAddr: dstAddr, + } + var buf bytes.Buffer + _, err := header.WriteTo(&buf) + if err != nil { + return err + } + _, err = conn.Write(buf.Bytes()) + return err +} + func (c *ProxyRelayInstance) ForwardUDP(address1, address2 string, stopChan chan bool) error { //By default the incoming listen Address is int //We need to add the loopback address into it @@ -142,6 +163,10 @@ func (c *ProxyRelayInstance) ForwardUDP(address1, address2 string, stopChan chan // Fire up routine to manage new connection go c.RunUDPConnectionRelay(conn, lisener) + // Send Proxy Protocol header if enabled + if c.ProxyProtocolVersion == 2 { + _ = WriteProxyProtocolHeaderUDP(conn.ServerConn, cliaddr, targetAddr) + } } else { c.LogMsg("[UDP] Found connection for client "+saddr, nil) conn = rawConn.(*udpClientServerConn)