Merge pull request #718 from jemmy1794/Stream-Proxy

Add Proxy Protocol V1 option in TCP Stream Proxy and update Stream Proxy UI
This commit is contained in:
Toby Chui
2025-07-02 20:40:08 +08:00
committed by GitHub
4 changed files with 74 additions and 14 deletions

View File

@@ -47,15 +47,17 @@ 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")
//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,
}) })
js, _ := json.Marshal(newConfigUUID) js, _ := json.Marshal(newConfigUUID)
@@ -75,6 +77,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")
newTimeoutStr, _ := utils.PostPara(r, "timeout") newTimeoutStr, _ := utils.PostPara(r, "timeout")
newTimeout := -1 newTimeout := -1
@@ -87,7 +90,7 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request)
} }
// Call the EditConfig method to modify the configuration // Call the EditConfig method to modify the configuration
err = m.EditConfig(configUUID, newName, listenAddr, proxyAddr, useTCP, useUDP, newTimeout) err = m.EditConfig(configUUID, newName, listenAddr, proxyAddr, useTCP, useUDP, useProxyProtocol, newTimeout)
if err != nil { if err != nil {
utils.SendErrorResponse(w, err.Error()) utils.SendErrorResponse(w, err.Error())
return return

View File

@@ -24,12 +24,13 @@ 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
} }
type ProxyRelayConfig struct { type ProxyRelayConfig struct {
@@ -41,6 +42,7 @@ type ProxyRelayConfig struct {
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
Timeout int //Timeout for connection in sec Timeout int //Timeout for connection in sec
tcpStopChan chan bool //Stop channel for TCP listener tcpStopChan chan bool //Stop channel for TCP listener
udpStopChan chan bool //Stop channel for UDP listener udpStopChan chan bool //Stop channel for UDP listener
@@ -157,6 +159,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,
Timeout: config.Timeout, Timeout: config.Timeout,
tcpStopChan: nil, tcpStopChan: nil,
udpStopChan: nil, udpStopChan: nil,
@@ -181,7 +184,7 @@ func (m *Manager) GetConfigByUUID(configUUID string) (*ProxyRelayConfig, error)
} }
// Edit the config based on config UUID, leave empty for unchange fields // Edit the config based on config UUID, leave empty for unchange fields
func (m *Manager) EditConfig(configUUID string, newName string, newListeningAddr string, newProxyAddr string, useTCP bool, useUDP bool, newTimeout int) error { func (m *Manager) EditConfig(configUUID string, newName string, newListeningAddr string, newProxyAddr string, useTCP bool, useUDP bool, useProxyProtocol bool, newTimeout int) error {
// Find the config with the specified UUID // Find the config with the specified UUID
foundConfig, err := m.GetConfigByUUID(configUUID) foundConfig, err := m.GetConfigByUUID(configUUID)
if err != nil { if err != nil {
@@ -201,6 +204,7 @@ func (m *Manager) EditConfig(configUUID string, newName string, newListeningAddr
foundConfig.UseTCP = useTCP foundConfig.UseTCP = useTCP
foundConfig.UseUDP = useUDP foundConfig.UseUDP = useUDP
foundConfig.UseProxyProtocol = useProxyProtocol
if newTimeout != -1 { if newTimeout != -1 {
if newTimeout < 0 { if newTimeout < 0 {

View File

@@ -2,6 +2,7 @@ package streamproxy
import ( import (
"errors" "errors"
"fmt"
"io" "io"
"log" "log"
"net" "net"
@@ -43,6 +44,23 @@ func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup, accumulator *a
wg.Done() wg.Done()
} }
func writeProxyProtocolHeaderV1(dst net.Conn, src net.Conn) 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)
_, err := dst.Write([]byte(header))
return err
}
func forward(conn1 net.Conn, conn2 net.Conn, aTob *atomic.Int64, bToa *atomic.Int64) { func forward(conn1 net.Conn, conn2 net.Conn, aTob *atomic.Int64, bToa *atomic.Int64) {
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String()) log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
var wg sync.WaitGroup var wg sync.WaitGroup
@@ -140,6 +158,20 @@ func (c *ProxyRelayConfig) Port2host(allowPort string, targetAddress string, sto
return return
} }
log.Println("[→]", "connect target address ["+targetAddress+"] success.") log.Println("[→]", "connect target address ["+targetAddress+"] success.")
if c.UseProxyProtocol {
log.Println("[+]", "write proxy protocol header to target address ["+targetAddress+"]")
err = writeProxyProtocolHeaderV1(target, conn)
if err != nil {
log.Println("[x]", "Write proxy protocol header faild: ", err)
target.Close()
conn.Close()
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
time.Sleep(time.Duration(c.Timeout) * time.Second)
return
}
}
forward(target, conn, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer) forward(target, conn, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
}(targetAddress) }(targetAddress)
} }

View File

@@ -73,6 +73,14 @@
<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>
<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>
@@ -195,6 +203,10 @@
modeText.push("UDP") modeText.push("UDP")
} }
if (config.UseProxyProtocol){
modeText.push("ProxyProtocol V1")
}
modeText = modeText.join(" & ") modeText = modeText.join(" & ")
var thisConfig = encodeURIComponent(JSON.stringify(config)); var thisConfig = encodeURIComponent(JSON.stringify(config));
@@ -252,6 +264,14 @@
$(checkboxEle).checkbox("set unchecked"); $(checkboxEle).checkbox("set unchecked");
} }
return; return;
}else if (key == "UseProxyProtocol"){
let checkboxEle = $("#streamProxyForm input[name=useProxyProtocol]").parent();
if (value === true){
$(checkboxEle).checkbox("set checked");
}else{
$(checkboxEle).checkbox("set unchecked");
}
return;
}else if (key == "ListeningAddress"){ }else if (key == "ListeningAddress"){
field = $("#streamProxyForm input[name=listenAddr]"); field = $("#streamProxyForm input[name=listenAddr]");
}else if (key == "ProxyTargetAddr"){ }else if (key == "ProxyTargetAddr"){
@@ -301,6 +321,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 ,
timeout: parseInt($("#streamProxyForm input[name=timeout]").val().trim()), timeout: parseInt($("#streamProxyForm input[name=timeout]").val().trim()),
}, },
success: function(response) { success: function(response) {