Updated Stream Proxy module

- Fixed stream proxy stopping racing condition bug
- Merged PR #187
- Updated stream proxy UI
This commit is contained in:
Toby Chui 2024-06-08 00:33:29 +08:00
parent c6f7f37aaf
commit deddb17803
4 changed files with 66 additions and 114 deletions

View File

@ -272,17 +272,14 @@ func HandleNewPasswordSetup(w http.ResponseWriter, r *http.Request) {
return return
} }
//Delete the user account // Un register the user account
authAgent.UnregisterUser(username) if err := authAgent.UnregisterUser(username); err != nil {
//Ok. Set the new password
err = authAgent.CreateUserAccount(username, newPassword, "")
if err != nil {
utils.SendErrorResponse(w, err.Error()) utils.SendErrorResponse(w, err.Error())
return return
} }
if err != nil { //Ok. Set the new password
if err := authAgent.CreateUserAccount(username, newPassword, ""); err != nil {
utils.SendErrorResponse(w, err.Error()) utils.SendErrorResponse(w, err.Error())
return return
} }

View File

@ -6,6 +6,7 @@ import (
"net" "net"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"imuslab.com/zoraxy/mod/database" "imuslab.com/zoraxy/mod/database"
@ -88,6 +89,11 @@ func NewStreamProxy(options *Options) *Manager {
//Inject manager into the rules //Inject manager into the rules
for _, rule := range previousRules { for _, rule := range previousRules {
rule.parent = &thisManager rule.parent = &thisManager
if rule.Running {
//This was previously running. Start it again
log.Println("[Stream Proxy] Resuming stream proxy rule " + rule.Name)
rule.Start()
}
} }
thisManager.Configs = previousRules thisManager.Configs = previousRules
@ -164,6 +170,11 @@ func (m *Manager) EditConfig(configUUID string, newName string, newListeningAddr
m.SaveConfigToDatabase() m.SaveConfigToDatabase()
//Check if config is running. If yes, restart it
if foundConfig.IsRunning() {
foundConfig.Restart()
}
return nil return nil
} }
@ -196,22 +207,17 @@ func (c *ProxyRelayConfig) Start() error {
// Create a stopChan to control the loop // Create a stopChan to control the loop
tcpStopChan := make(chan bool) tcpStopChan := make(chan bool)
c.tcpStopChan = tcpStopChan
udpStopChan := make(chan bool) udpStopChan := make(chan bool)
c.udpStopChan = udpStopChan
//Start the proxy service //Start the proxy service
if c.UseUDP { if c.UseUDP {
c.udpStopChan = udpStopChan
go func() { go func() {
if !c.UseTCP {
//By default running state shows TCP proxy. If TCP is not in use, UDP is shown instead
c.Running = true
}
err := c.ForwardUDP(c.ListeningAddress, c.ProxyTargetAddr, udpStopChan) err := c.ForwardUDP(c.ListeningAddress, c.ProxyTargetAddr, udpStopChan)
if err != nil { if err != nil {
if !c.UseTCP { if !c.UseTCP {
c.Running = false c.Running = false
c.parent.SaveConfigToDatabase()
} }
log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error()) log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error())
} }
@ -219,41 +225,57 @@ func (c *ProxyRelayConfig) Start() error {
} }
if c.UseTCP { if c.UseTCP {
c.tcpStopChan = tcpStopChan
go func() { go func() {
//Default to transport mode //Default to transport mode
c.Running = true
err := c.Port2host(c.ListeningAddress, c.ProxyTargetAddr, tcpStopChan) err := c.Port2host(c.ListeningAddress, c.ProxyTargetAddr, tcpStopChan)
if err != nil { if err != nil {
c.Running = false c.Running = false
c.parent.SaveConfigToDatabase()
log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error()) log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error())
} }
}() }()
} }
//Successfully spawned off the proxy routine //Successfully spawned off the proxy routine
c.Running = true
c.parent.SaveConfigToDatabase()
return nil return nil
} }
// Stop a running proxy if running // Return if a proxy config is running
func (c *ProxyRelayConfig) IsRunning() bool { func (c *ProxyRelayConfig) IsRunning() bool {
return c.tcpStopChan != nil || c.udpStopChan != nil return c.tcpStopChan != nil || c.udpStopChan != nil
} }
// Restart a proxy config
func (c *ProxyRelayConfig) Restart() {
if c.IsRunning() {
c.Stop()
}
time.Sleep(300 * time.Millisecond)
c.Start()
}
// Stop a running proxy if running // Stop a running proxy if running
func (c *ProxyRelayConfig) Stop() { func (c *ProxyRelayConfig) Stop() {
log.Println("[PROXY] Stopping Stream Proxy " + c.Name) log.Println("[STREAM PROXY] Stopping Stream Proxy " + c.Name)
if c.udpStopChan != nil { if c.udpStopChan != nil {
log.Println("[STREAM PROXY] Stopping UDP for " + c.Name)
c.udpStopChan <- true c.udpStopChan <- true
c.udpStopChan = nil c.udpStopChan = nil
} }
if c.tcpStopChan != nil { if c.tcpStopChan != nil {
log.Println("[STREAM PROXY] Stopping TCP for " + c.Name)
c.tcpStopChan <- true c.tcpStopChan <- true
c.tcpStopChan = nil c.tcpStopChan = nil
} }
log.Println("[PROXY] Stopped Stream Proxy " + c.Name) log.Println("[STREAM PROXY] Stopped Stream Proxy " + c.Name)
c.Running = false c.Running = false
//Update the running status
c.parent.SaveConfigToDatabase()
} }

View File

@ -5,15 +5,15 @@
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="ui basic segment" style="margin-top: 0;"> <div class="ui basic segment" style="margin-top: 0;">
<h4>TCP / UDP Proxy Rules</h4> <h3>TCP / UDP Proxy Rules</h3>
<p>A list of TCP proxy rules created on this host. To enable them, use the toggle button on the right.</p> <p>A list of TCP / UDP proxy rules created on this host.</p>
<div style="overflow-x: auto; min-height: 400px;"> <div style="overflow-x: auto; ">
<table id="proxyTable" class="ui celled unstackable table"> <table id="proxyTable" class="ui celled basic unstackable table">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Port/Addr A</th> <th>Listening Address</th>
<th>Port/Addr B</th> <th>Target Address</th>
<th>Mode</th> <th>Mode</th>
<th>Timeout (s)</th> <th>Timeout (s)</th>
<th>Actions</th> <th>Actions</th>
@ -24,12 +24,13 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<br>
<button class="ui basic right floated button" onclick="initProxyConfigList();" title="Refresh List"><i class="ui green refresh icon"></i>Refresh</button> <button class="ui basic right floated button" onclick="initProxyConfigList();" title="Refresh List"><i class="ui green refresh icon"></i>Refresh</button>
<br><br> <br><br>
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="ui basic segment" id="addproxyConfig"> <div class="ui basic segment" id="addproxyConfig">
<h4>Add or Edit Stream Proxy</h4> <h3>Add or Edit Stream Proxy</h3>
<p>Create or edit a new stream proxy instance</p> <p>Create or edit a new stream proxy instance</p>
<form id="streamProxyForm" class="ui form"> <form id="streamProxyForm" class="ui form">
<div class="field" style="display:none;"> <div class="field" style="display:none;">
@ -41,10 +42,10 @@
<input type="text" name="name" placeholder="Config Name"> <input type="text" name="name" placeholder="Config Name">
</div> </div>
<div class="field"> <div class="field">
<label>Listening Port / Address with Port</label> <label>Listening Address with Port</label>
<input type="text" name="listenAddr" placeholder=""> <input type="text" name="listenAddr" placeholder="">
<small>Port to listen on this host. e.g. :25565 or 127.0.0.1:25565. <br> <small>Address to listen on this host. e.g. :25565 or 127.0.0.1:25565. <br>
If you are using Docker, you will need to expose this port to host network.</small> If you are using Docker, you will also need to expose this port to host network.</small>
</div> </div>
<div class="field"> <div class="field">
<label>Proxy Target Address with Port</label> <label>Proxy Target Address with Port</label>
@ -75,72 +76,6 @@
<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);" style="display:none;"><i class="ui green check icon"></i> Update</button> <button id="editStreamProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event);" 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>
<!--
<div class="ui basic inverted segment" style="background: var(--theme_background_inverted); border-radius: 0.6em;">
<p>TCP Proxy support the following TCP sockets proxy modes</p>
<table class="ui celled padded inverted basic table">
<thead>
<tr><th class="single line">Mode</th>
<th>Public-IP</th>
<th>Concurrent Access</th>
<th>Flow Diagram</th>
</tr></thead>
<tbody>
<tr>
<td>
<h4 class="ui center aligned inverted header">Transport</h4>
</td>
<td class="single line">
Server: <i class="ui green check icon"></i><br>
A: <i class="ui remove icon"></i><br>
B: <i class="ui green check icon"></i> (or same LAN)<br>
</td>
<td>
<i class="ui green check icon"></i>
</td>
<td>Port A (e.g. 25565) <i class="arrow right icon"></i> Server<br>
Server <i class="arrow right icon"></i> Port B (e.g. 192.168.0.2:25565)<br>
<small>Traffic from Port A will be forward to Port B's (IP if provided and) Port</small>
</td>
</tr>
<tr>
<td>
<h4 class="ui center aligned inverted header">Listen</h4>
</td>
<td class="single line">
Server: <i class="ui green check icon"></i><br>
A: <i class="ui remove icon"></i><br>
B: <i class="ui remove icon"></i><br>
</td>
<td>
<i class="ui red times icon"></i>
</td>
<td>Port A (e.g. 8080) <i class="arrow right icon"></i> Server<br>
Port B (e.g. 8081) <i class="arrow right icon"></i> Server<br>
<small>Server will act as a bridge to proxy traffic between Port A and B</small>
</td>
</tr>
<tr>
<td>
<h4 class="ui center aligned inverted header">Starter</h4>
</td>
<td class="single line">
Server: <i class="ui times icon"></i><br>
A: <i class="ui green check icon"></i><br>
B: <i class="ui green check icon"></i><br>
</td>
<td>
<i class="ui red times icon"></i>
</td>
<td>Server <i class="arrow right icon"></i> Port A (e.g. remote.local.:8080) <br>
Server <i class="arrow right icon"></i> Port B (e.g. recv.local.:8081) <br>
<small>Port A and B will be actively bridged</small>
</td>
</tr>
</tbody>
</table>
</div>
-->
</form> </form>
</div> </div>
</div> </div>
@ -243,10 +178,10 @@
proxyConfigs.forEach(function(config) { proxyConfigs.forEach(function(config) {
var runningLogo = 'Stopped'; var runningLogo = 'Stopped';
var runningClass = "stopped"; var runningClass = "stopped";
var startButton = `<button onclick="startStreamProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="green play icon"></i> Start Proxy</button>`; var startButton = `<button onclick="startStreamProx('${config.UUID}');" class="ui basic mini circular icon button" title="Start Proxy"><i class="green play icon"></i></button>`;
if (config.Running){ if (config.Running){
runningLogo = 'Running'; runningLogo = 'Running';
startButton = `<button onclick="stopStreamProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="red stop icon"></i> Stop Proxy</button>`; startButton = `<button onclick="stopStreamProx('${config.UUID}');" class="ui basic mini circular icon button" title="Stop Proxy"><i class="red stop icon"></i></button>`;
runningClass = "running" runningClass = "running"
} }
@ -259,11 +194,11 @@
modeText.push("UDP") modeText.push("UDP")
} }
modeText = modeText.join(" | ") modeText = modeText.join(" & ")
var thisConfig = encodeURIComponent(JSON.stringify(config)); var thisConfig = encodeURIComponent(JSON.stringify(config));
var row = $(`<tr class="tcproxConfig ${runningClass}" uuid="${config.UUID}" config="${thisConfig}">`); var row = $(`<tr class="streamproxConfig ${runningClass}" uuid="${config.UUID}" config="${thisConfig}">`);
row.append($('<td>').html(` row.append($('<td>').html(`
${config.Name} ${config.Name}
<div class="statusText">${runningLogo}</div>`)); <div class="statusText">${runningLogo}</div>`));
@ -272,11 +207,9 @@
row.append($('<td>').text(modeText)); row.append($('<td>').text(modeText));
row.append($('<td>').text(config.Timeout)); row.append($('<td>').text(config.Timeout));
row.append($('<td>').html(` row.append($('<td>').html(`
<div class="ui basic vertical fluid tiny buttons"> ${startButton}
${startButton} <button onclick="editTCPProxyConfig('${config.UUID}');" class="ui circular basic mini icon button" title="Edit Config"><i class="edit icon"></i></button>
<button onclick="editTCPProxyConfig('${config.UUID}');" class="ui button" title="Edit Config"><i class="edit icon"></i> Edit </button> <button onclick="deleteTCPProxyConfig('${config.UUID}');" class="ui circular red basic mini icon button" title="Delete Config"><i class="trash icon"></i></button>
<button onclick="deleteTCPProxyConfig('${config.UUID}');" class="ui red basic button" title="Delete Config"><i class="trash icon"></i> Remove</button>
</div>
`)); `));
tableBody.append(row); tableBody.append(row);
}); });
@ -285,7 +218,7 @@
function getConfigDetailsFromDOM(configUUID){ function getConfigDetailsFromDOM(configUUID){
let thisConfig = null; let thisConfig = null;
$(".tcproxConfig").each(function(){ $(".streamproxConfig").each(function(){
let uuid = $(this).attr("uuid"); let uuid = $(this).attr("uuid");
if (configUUID == uuid){ if (configUUID == uuid){
//This is the one we are looking for //This is the one we are looking for

View File

@ -551,23 +551,23 @@ body{
TCP Proxy TCP Proxy
*/ */
.tcproxConfig td:first-child{ .streamproxConfig td:first-child{
position: relative; position: relative;
} }
.tcproxConfig.running td:first-child{ .streamproxConfig.running td:first-child{
border-left: 0.6em solid #02cb59 !important; border-left: 0.6em solid #01cb55 !important;
} }
.tcproxConfig.stopped td:first-child{ .streamproxConfig.stopped td:first-child{
border-left: 0.6em solid #02032a !important; border-left: 0.6em solid #1b1b1b !important;
} }
.tcproxConfig td:first-child .statusText{ .streamproxConfig td:first-child .statusText{
position: absolute; position: absolute;
bottom: 0.3em; bottom: 0.1em;
left: 0.2em; right: 0.2em;
font-size: 1.4em; font-size: 1em;
color:rgb(224, 224, 224); color:rgb(224, 224, 224);
opacity: 0.7; opacity: 0.7;
pointer-events: none; pointer-events: none;