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/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

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/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=

View File

@@ -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

View File

@@ -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 {

View File

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

View File

@@ -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)

View File

@@ -74,14 +74,6 @@
<small>Forward UDP request on this listening socket</small></label>
</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="ui toggle checkbox">
<input type="checkbox" tabindex="0" name="enableLogging" class="hidden">
@@ -90,6 +82,15 @@
</label>
</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="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>
@@ -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()),
},