Merge pull request #848 from jemmy1794/feature/Proxy_Protocol_V2

Add support for Proxy Protocol V1 and V2 in streamproxy configuration
This commit is contained in:
Toby Chui
2025-10-15 07:28:44 +08:00
committed by GitHub
7 changed files with 107 additions and 77 deletions

View File

@@ -17,6 +17,7 @@ require (
github.com/likexian/whois v1.15.1 github.com/likexian/whois v1.15.1
github.com/microcosm-cc/bluemonday v1.0.26 github.com/microcosm-cc/bluemonday v1.0.26
github.com/monperrus/crawler-user-agents v1.1.0 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/shirou/gopsutil/v4 v4.25.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0

View File

@@ -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/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 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c=
github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc= 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 h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= 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= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

View File

@@ -47,19 +47,19 @@ func (m *Manager) HandleAddProxyConfig(w http.ResponseWriter, r *http.Request) {
useTCP, _ := utils.PostBool(r, "useTCP") useTCP, _ := utils.PostBool(r, "useTCP")
useUDP, _ := utils.PostBool(r, "useUDP") useUDP, _ := utils.PostBool(r, "useUDP")
useProxyProtocol, _ := utils.PostBool(r, "useProxyProtocol") ProxyProtocolVersion, _ := utils.PostInt(r, "proxyProtocolVersion")
enableLogging, _ := utils.PostBool(r, "enableLogging") enableLogging, _ := utils.PostBool(r, "enableLogging")
//Create the target config //Create the target config
newConfigUUID := m.NewConfig(&ProxyRelayOptions{ newConfigUUID := m.NewConfig(&ProxyRelayOptions{
Name: name, Name: name,
ListeningAddr: strings.TrimSpace(listenAddr), ListeningAddr: strings.TrimSpace(listenAddr),
ProxyAddr: strings.TrimSpace(proxyAddr), ProxyAddr: strings.TrimSpace(proxyAddr),
Timeout: timeout, Timeout: timeout,
UseTCP: useTCP, UseTCP: useTCP,
UseUDP: useUDP, UseUDP: useUDP,
UseProxyProtocol: useProxyProtocol, ProxyProtocolVersion: ProxyProtocolVersion,
EnableLogging: enableLogging, EnableLogging: enableLogging,
}) })
js, _ := json.Marshal(newConfigUUID) js, _ := json.Marshal(newConfigUUID)
@@ -79,7 +79,7 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request)
proxyAddr, _ := utils.PostPara(r, "proxyAddr") proxyAddr, _ := utils.PostPara(r, "proxyAddr")
useTCP, _ := utils.PostBool(r, "useTCP") useTCP, _ := utils.PostBool(r, "useTCP")
useUDP, _ := utils.PostBool(r, "useUDP") useUDP, _ := utils.PostBool(r, "useUDP")
useProxyProtocol, _ := utils.PostBool(r, "useProxyProtocol") proxyProtocolVersion, _ := utils.PostInt(r, "proxyProtocolVersion")
enableLogging, _ := utils.PostBool(r, "enableLogging") enableLogging, _ := utils.PostBool(r, "enableLogging")
newTimeoutStr, _ := utils.PostPara(r, "timeout") 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 // Create a new ProxyRuleUpdateConfig with the extracted parameters
newConfig := &ProxyRuleUpdateConfig{ newConfig := &ProxyRuleUpdateConfig{
InstanceUUID: configUUID, InstanceUUID: configUUID,
NewName: newName, NewName: newName,
NewListeningAddr: listenAddr, NewListeningAddr: listenAddr,
NewProxyAddr: proxyAddr, NewProxyAddr: proxyAddr,
UseTCP: useTCP, UseTCP: useTCP,
UseUDP: useUDP, UseUDP: useUDP,
UseProxyProtocol: useProxyProtocol, ProxyProtocolVersion: proxyProtocolVersion,
EnableLogging: enableLogging, EnableLogging: enableLogging,
NewTimeout: newTimeout, NewTimeout: newTimeout,
} }
// Call the EditConfig method to modify the configuration // Call the EditConfig method to modify the configuration

View File

@@ -23,42 +23,42 @@ import (
*/ */
type ProxyRelayOptions struct { type ProxyRelayOptions struct {
Name string Name string
ListeningAddr string ListeningAddr string
ProxyAddr string ProxyAddr string
Timeout int Timeout int
UseTCP bool UseTCP bool
UseUDP bool UseUDP bool
UseProxyProtocol bool ProxyProtocolVersion int
EnableLogging bool EnableLogging bool
} }
// ProxyRuleUpdateConfig is used to update the proxy rule config // ProxyRuleUpdateConfig is used to update the proxy rule config
type ProxyRuleUpdateConfig struct { type ProxyRuleUpdateConfig struct {
InstanceUUID string //The target instance UUID to update InstanceUUID string //The target instance UUID to update
NewName string //New name for the instance, leave empty for no change NewName string //New name for the instance, leave empty for no change
NewListeningAddr string //New listening address, 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 NewProxyAddr string //New proxy target address, leave empty for no change
UseTCP bool //Enable TCP proxy, default to false UseTCP bool //Enable TCP proxy, default to false
UseUDP bool //Enable UDP proxy, default to false UseUDP bool //Enable UDP proxy, default to false
UseProxyProtocol bool //Enable Proxy Protocol, default to false ProxyProtocolVersion int //Enable Proxy Protocol v1/v2, default to disabled
EnableLogging bool //Enable Logging TCP/UDP Message, default to true EnableLogging bool //Enable Logging TCP/UDP Message, default to true
NewTimeout int //New timeout for the connection, leave -1 for no change NewTimeout int //New timeout for the connection, leave -1 for no change
} }
type ProxyRelayInstance struct { type ProxyRelayInstance struct {
/* Runtime Config */ /* Runtime Config */
UUID string //A UUIDv4 representing this config UUID string //A UUIDv4 representing this config
Name string //Name of the config Name string //Name of the config
Running bool //Status, read only Running bool //Status, read only
AutoStart bool //If the service suppose to started automatically AutoStart bool //If the service suppose to started automatically
ListeningAddress string //Listening Address, usually 127.0.0.1:port ListeningAddress string //Listening Address, usually 127.0.0.1:port
ProxyTargetAddr string //Proxy target address ProxyTargetAddr string //Proxy target address
UseTCP bool //Enable TCP proxy UseTCP bool //Enable TCP proxy
UseUDP bool //Enable UDP proxy UseUDP bool //Enable UDP proxy
UseProxyProtocol bool //Enable Proxy Protocol ProxyProtocolVersion int //Proxy Protocol v1/v2
EnableLogging bool //Enable logging for ProxyInstance EnableLogging bool //Enable logging for ProxyInstance
Timeout int //Timeout for connection in sec Timeout int //Timeout for connection in sec
/* Internal */ /* Internal */
tcpStopChan chan bool //Stop channel for TCP listener tcpStopChan chan bool //Stop channel for TCP listener
@@ -178,7 +178,7 @@ func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
ProxyTargetAddr: config.ProxyAddr, ProxyTargetAddr: config.ProxyAddr,
UseTCP: config.UseTCP, UseTCP: config.UseTCP,
UseUDP: config.UseUDP, UseUDP: config.UseUDP,
UseProxyProtocol: config.UseProxyProtocol, ProxyProtocolVersion: config.ProxyProtocolVersion,
EnableLogging: config.EnableLogging, EnableLogging: config.EnableLogging,
Timeout: config.Timeout, Timeout: config.Timeout,
tcpStopChan: nil, tcpStopChan: nil,
@@ -224,7 +224,7 @@ func (m *Manager) EditConfig(newConfig *ProxyRuleUpdateConfig) error {
foundConfig.UseTCP = newConfig.UseTCP foundConfig.UseTCP = newConfig.UseTCP
foundConfig.UseUDP = newConfig.UseUDP foundConfig.UseUDP = newConfig.UseUDP
foundConfig.UseProxyProtocol = newConfig.UseProxyProtocol foundConfig.ProxyProtocolVersion = newConfig.ProxyProtocolVersion
foundConfig.EnableLogging = newConfig.EnableLogging foundConfig.EnableLogging = newConfig.EnableLogging
if newConfig.NewTimeout != -1 { if newConfig.NewTimeout != -1 {

View File

@@ -11,6 +11,8 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
proxyproto "github.com/pires/go-proxyproto"
) )
func isValidIP(ip string) bool { func isValidIP(ip string) bool {
@@ -44,20 +46,22 @@ func (c *ProxyRelayInstance) connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.W
wg.Done() 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) clientAddr, ok1 := src.RemoteAddr().(*net.TCPAddr)
proxyAddr, ok2 := src.LocalAddr().(*net.TCPAddr) proxyAddr, ok2 := src.LocalAddr().(*net.TCPAddr)
if !ok1 || !ok2 { if !ok1 || !ok2 {
return errors.New("invalid TCP address for proxy protocol") return errors.New("invalid TCP address for proxy protocol")
} }
header := fmt.Sprintf("PROXY TCP4 %s %s %d %d\r\n", header := proxyproto.Header{
clientAddr.IP.String(), Version: byte(version),
proxyAddr.IP.String(), Command: proxyproto.PROXY,
clientAddr.Port, TransportProtocol: proxyproto.TCPv4,
proxyAddr.Port) SourceAddr: clientAddr,
DestinationAddr: proxyAddr,
}
_, err := dst.Write([]byte(header)) _, err := header.WriteTo(dst)
return err return err
} }
@@ -161,9 +165,9 @@ func (c *ProxyRelayInstance) Port2host(allowPort string, targetAddress string, s
} }
c.LogMsg("[→] connect target address ["+targetAddress+"] success.", nil) 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) c.LogMsg("[+] write proxy protocol header to target address ["+targetAddress+"]", nil)
err = writeProxyProtocolHeaderV1(target, conn) err = WriteProxyProtocolHeader(target, conn, c.ProxyProtocolVersion)
if err != nil { if err != nil {
c.LogMsg("[x] Write proxy protocol header failed: "+err.Error(), nil) c.LogMsg("[x] Write proxy protocol header failed: "+err.Error(), nil)
target.Close() target.Close()

View File

@@ -1,11 +1,14 @@
package streamproxy package streamproxy
import ( import (
"bytes"
"errors" "errors"
"log" "log"
"net" "net"
"strings" "strings"
"time" "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 { func (c *ProxyRelayInstance) ForwardUDP(address1, address2 string, stopChan chan bool) error {
//By default the incoming listen Address is int //By default the incoming listen Address is int
//We need to add the loopback address into it //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 // Fire up routine to manage new connection
go c.RunUDPConnectionRelay(conn, lisener) go c.RunUDPConnectionRelay(conn, lisener)
// Send Proxy Protocol header if enabled
if c.ProxyProtocolVersion == 2 {
_ = WriteProxyProtocolHeaderUDP(conn.ServerConn, cliaddr, targetAddr)
}
} else { } else {
c.LogMsg("[UDP] Found connection for client "+saddr, nil) c.LogMsg("[UDP] Found connection for client "+saddr, nil)
conn = rawConn.(*udpClientServerConn) conn = rawConn.(*udpClientServerConn)

View File

@@ -74,14 +74,6 @@
<small>Forward UDP request on this listening socket</small></label> <small>Forward UDP request on this listening socket</small></label>
</div> </div>
</div> </div>
<div class="field">
<div class="ui toggle checkbox">
<input type="checkbox" tabindex="0" name="useProxyProtocol" class="hidden">
<label>Enable Proxy Protocol V1<br>
<small>Enable TCP Proxy Protocol header V1</small>
</label>
</div>
</div>
<div class="field"> <div class="field">
<div class="ui toggle checkbox"> <div class="ui toggle checkbox">
<input type="checkbox" tabindex="0" name="enableLogging" class="hidden"> <input type="checkbox" tabindex="0" name="enableLogging" class="hidden">
@@ -90,6 +82,15 @@
</label> </label>
</div> </div>
</div> </div>
<div class="field">
<label>Proxy Protocol</label>
<select name="proxyProtocolVersion" class="ui dropdown">
<option value="0">Disabled</option>
<option value="1">Proxy Protocol V1</option>
<option value="2">Proxy Protocol V2</option>
</select>
<small>Select Proxy Protocol v1 / v2 to use (if any)</small>
</div>
<button id="addStreamProxyButton" class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button> <button id="addStreamProxyButton" class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
<button id="editStreamProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event, this);" style="display:none;"><i class="ui green check icon"></i> Update</button> <button id="editStreamProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event, this);" style="display:none;"><i class="ui green check icon"></i> Update</button>
<button class="ui basic red button" onclick="event.preventDefault(); cancelStreamProxyEdit(event);"><i class="ui red remove icon"></i> Cancel</button> <button class="ui basic red button" onclick="event.preventDefault(); cancelStreamProxyEdit(event);"><i class="ui red remove icon"></i> Cancel</button>
@@ -138,7 +139,7 @@
function clearStreamProxyAddEditForm(){ function clearStreamProxyAddEditForm(){
$('#streamProxyForm').find('input:not([type=checkbox]), select').val(''); $('#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 input[name=timeout]").val(10);
$("#streamProxyForm .toggle.checkbox").checkbox("set unchecked"); $("#streamProxyForm .toggle.checkbox").checkbox("set unchecked");
} }
@@ -212,8 +213,10 @@
modeText.push("UDP") modeText.push("UDP")
} }
if (config.UseProxyProtocol){ if (config.ProxyProtocolVersion === 1) {
modeText.push("ProxyProtocol V1") modeText.push("ProxyProtocol V1");
} else if (config.ProxyProtocolVersion === 2) {
modeText.push("ProxyProtocol V2");
} }
modeText = modeText.join(" & ") modeText = modeText.join(" & ")
@@ -277,13 +280,8 @@
$(checkboxEle).checkbox("set unchecked"); $(checkboxEle).checkbox("set unchecked");
} }
return; return;
}else if (key == "UseProxyProtocol"){ }else if (key == "ProxyProtocolVersion") {
let checkboxEle = $("#streamProxyForm input[name=useProxyProtocol]").parent(); $("#streamProxyForm select[name=proxyProtocolVersion]").dropdown("set selected", value);
if (value === true){
$(checkboxEle).checkbox("set checked");
}else{
$(checkboxEle).checkbox("set unchecked");
}
return; return;
}else if (key == "EnableLogging"){ }else if (key == "EnableLogging"){
let checkboxEle = $("#streamProxyForm input[name=enableLogging]").parent(); let checkboxEle = $("#streamProxyForm input[name=enableLogging]").parent();
@@ -342,7 +340,7 @@
proxyAddr: $("#streamProxyForm input[name=proxyAddr]").val().trim(), proxyAddr: $("#streamProxyForm input[name=proxyAddr]").val().trim(),
useTCP: $("#streamProxyForm input[name=useTCP]")[0].checked , useTCP: $("#streamProxyForm input[name=useTCP]")[0].checked ,
useUDP: $("#streamProxyForm input[name=useUDP]")[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 , enableLogging: $("#streamProxyForm input[name=enableLogging]")[0].checked ,
timeout: parseInt($("#streamProxyForm input[name=timeout]").val().trim()), timeout: parseInt($("#streamProxyForm input[name=timeout]").val().trim()),
}, },