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
This commit is contained in:
jemmy1794
2025-10-14 21:30:32 +08:00
parent 66572981b3
commit 8d29a929f7
6 changed files with 82 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

@@ -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()),
}, },