20 Commits

Author SHA1 Message Date
Toby Chui
2d611a559a Optimized structure for stream proxy
- Separated instance and config for stream proxy
2025-07-02 21:03:57 +08:00
Toby Chui
6c5eba01c2 Update README.md
Added more contributors in community maintained section name list
2025-07-02 20:42:14 +08:00
Toby Chui
f641797d10 Merge pull request #718 from jemmy1794/Stream-Proxy
Add Proxy Protocol V1 option in TCP Stream Proxy and update Stream Proxy UI
2025-07-02 20:40:08 +08:00
Jemmy
f92ff068f3 Added Proxy Protocol V1 to Stream Proxy UI
- Added a checkbox for Proxy Protocol V1.
- Modified related Config setting function.
2025-07-02 18:04:26 +08:00
Jemmy
b59ac47c8c Added Proxy Protocol V1 function.
- Added useProxyProtocol in ProxyRelayConfig
- Added writeProxyProtocolHeaderV1 function
2025-07-02 17:58:26 +08:00
Toby Chui
8030f3d62a Fixed #688
- Added auto restart after config change in static web server
2025-06-30 20:34:42 +08:00
Toby Chui
f8f623e3e4 Update .gitignore
Ignored dist folder
2025-06-28 17:00:31 +08:00
Toby Chui
061839756c Merge pull request #711 from Morethanevil/main
Update CHANGELOG.md
2025-06-28 14:34:58 +08:00
Marcel
1dcaa0c257 Update CHANGELOG.md 2025-06-28 08:31:20 +02:00
Toby Chui
ffd3909964 Merge pull request #710 from tobychui/v3.2.4
V3.2.4 update
2025-06-28 10:06:23 +08:00
Toby Chui
3ddccdffce Merge branch 'v3.2.4' of https://github.com/tobychui/zoraxy into v3.2.4 2025-06-27 22:02:29 +08:00
Toby Chui
929d4cc82a Optimized SSO UI
- Added tab menu to SSO settings
2025-06-27 22:02:28 +08:00
Toby Chui
4f1cd8a571 Merge pull request #705 from jemmy1794/v3.2.4
Fix: #659
2025-06-24 14:24:26 +08:00
Jemmy
f6b3656bb1 Fix: #659
Listen UDP port on (0.0.0.0)* address.
2025-06-24 13:10:58 +08:00
Toby Chui
74a816216e Merge pull request #702 from PassiveLemon/main
Release type Docker workflows
2025-06-19 07:11:14 +08:00
PassiveLemon
4a093cf096 Merge branch 'tobychui:main' into main 2025-06-18 16:54:24 -04:00
PassiveLemon
68f9fccf3a refactor: release type workflows 2025-06-18 16:53:51 -04:00
Toby Chui
f276040ad0 Added experimental fix for #695
Added prefix trim and location filter for oauth authrozied redirection
2025-06-16 21:21:50 +08:00
Toby Chui
2f40593daf Updated version code 2025-06-16 21:12:49 +08:00
Toby Chui
0b6dbd49bb Fixed #694
- Uncommented the delete proxy rule button
- Added redirection path escape in dpcore
2025-06-16 20:16:36 +08:00
19 changed files with 379 additions and 170 deletions

View File

@@ -2,7 +2,7 @@ name: Build and push Docker image
on: on:
release: release:
types: [ published ] types: [ released, prereleased ]
jobs: jobs:
setup-build-push: setup-build-push:
@@ -33,7 +33,8 @@ jobs:
run: | run: |
cp -lr $GITHUB_WORKSPACE/src/ $GITHUB_WORKSPACE/docker/src/ cp -lr $GITHUB_WORKSPACE/src/ $GITHUB_WORKSPACE/docker/src/
- name: Build and push Docker image - name: Build and push Docker image (Release)
if: "!github.event.release.prerelease"
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: ./docker context: ./docker
@@ -45,3 +46,15 @@ jobs:
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
- name: Build and push Docker image (Prerelease)
if: "github.event.release.prerelease"
uses: docker/build-push-action@v6
with:
context: ./docker
push: true
platforms: linux/amd64,linux/arm64
tags: |
zoraxydocker/zoraxy:${{ github.event.release.tag_name }}
cache-from: type=gha
cache-to: type=gha,mode=max

1
.gitignore vendored
View File

@@ -57,3 +57,4 @@ tmp
sys.* sys.*
www/html/index.html www/html/index.html
*.exe *.exe
/src/dist

View File

@@ -1,3 +1,36 @@
# v3.2.4 28 Jun 2025
A big release since v3.1.9. Versions from 3.2.0 to 3.2.3 were prereleases.
+ Added Authentik support by [JokerQyou](https://github.com/tobychui/zoraxy/commits?author=JokerQyou)
+ Added pluginsystem and moved GAN and Zerotier to plugins
+ Add loopback detection [#573](https://github.com/tobychui/zoraxy/issues/573)
+ Fixed Dark theme not working with Advanced Option accordion [#591](https://github.com/tobychui/zoraxy/issues/591)
+ Update logger to include UserAgent by [Raithmir](https://github.com/Raithmir)
+ Fixed memory usage in UI [#600](https://github.com/tobychui/zoraxy/issues/600)
+ Added docker-compose.yml by [SamuelPalubaCZ](https://github.com/tobychui/zoraxy/commits?author=SamuelPalubaCZ)
+ Added more statistics for proxy hosts [#201](https://github.com/tobychui/zoraxy/issues/201) and [#608](https://github.com/tobychui/zoraxy/issues/608)
+ Fixed origin field in logs [#618](https://github.com/tobychui/zoraxy/issues/618)
+ Added FreeBSD support by Andreas Burri
+ Fixed HTTP proxy redirect [#626](https://github.com/tobychui/zoraxy/issues/626)
+ Fixed proxy handling #629](https://github.com/tobychui/zoraxy/issues/629)
+ Move Scope ID handling into CIDR check by [Nirostar](https://github.com/tobychui/zoraxy/commits?author=Nirostar)
+ Prevent the browser from filling the saved Zoraxy login account by [WHFo](https://github.com/tobychui/zoraxy/commits?author=WHFo)
+ Added port number and http proto to http proxy list link
+ Fixed headers for authelia by [james-d-elliott](https://github.com/tobychui/zoraxy/commits?author=james-d-elliott)
+ Refactored docker container list and UI improvements by [eyerrock](https://github.com/tobychui/zoraxy/commits?author=eyerrock)
+ Refactored Dockerfile by [PassiveLemon](https://github.com/tobychui/zoraxy/commits?author=PassiveLemon)
+ Added new HTTP proxy UI
+ Added inbound host name edit function
+ Added static web server option to disable listen to all interface
+ Merged SSO implementations (Oauth2) [#649](https://github.com/tobychui/zoraxy/pull/649)
+ Merged forward-auth optimization [#692(https://github.com/tobychui/zoraxy/pull/692)
+ Optimized SSO UI
+ Refactored docker image workflows by [PassiveLemon](https://github.com/tobychui/zoraxy/commits?author=PassiveLemon)
+ Added disable chunked transfer encoding checkbox (for upstreams that uses legacy HTTP implementations)
+ Bug fixes [#694](https://github.com/tobychui/zoraxy/issues/694), [#659](https://github.com/tobychui/zoraxy/issues/659) by [jemmy1794](https://github.com/tobychui/zoraxy/commits?author=jemmy1794), [#695](https://github.com/tobychui/zoraxy/issues/695)
# v3.1.9 1 Mar 2025 # v3.1.9 1 Mar 2025
+ Fixed netstat underflow bug + Fixed netstat underflow bug

View File

@@ -200,6 +200,10 @@ Some section of Zoraxy are contributed by our amazing community and if you have
- Docker Container List by [@eyerrock](https://github.com/eyerrock) - Docker Container List by [@eyerrock](https://github.com/eyerrock)
- Stream Proxy [@jemmy1794](https://github.com/jemmy1794)
- Change Log [@Morethanevil](https://github.com/Morethanevil)
### Looking for Maintainer ### Looking for Maintainer
- ACME DNS Challenge Module - ACME DNS Challenge Module

View File

@@ -44,7 +44,7 @@ import (
const ( const (
/* Build Constants */ /* Build Constants */
SYSTEM_NAME = "Zoraxy" SYSTEM_NAME = "Zoraxy"
SYSTEM_VERSION = "3.2.3" SYSTEM_VERSION = "3.2.4"
DEVELOPMENT_BUILD = false DEVELOPMENT_BUILD = false
/* System Constants */ /* System Constants */

View File

@@ -4,13 +4,14 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"net/http"
"net/url"
"strings"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"imuslab.com/zoraxy/mod/database" "imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/info/logger" "imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/utils"
"net/http"
"net/url"
"strings"
) )
type OAuth2RouterOptions struct { type OAuth2RouterOptions struct {
@@ -250,7 +251,19 @@ func (ar *OAuth2Router) HandleOAuth2Auth(w http.ResponseWriter, r *http.Request)
cookie.SameSite = http.SameSiteLaxMode cookie.SameSite = http.SameSiteLaxMode
} }
w.Header().Add("Set-Cookie", cookie.String()) w.Header().Add("Set-Cookie", cookie.String())
//Fix for #695
location := strings.TrimPrefix(state, "/internal/")
//Check if the location starts with http:// or https://. if yes, this is full URL
decodedLocation, err := url.PathUnescape(location)
if err == nil && (strings.HasPrefix(decodedLocation, "http://") || strings.HasPrefix(decodedLocation, "https://")) {
//Redirect to the full URL
http.Redirect(w, r, decodedLocation, http.StatusTemporaryRedirect)
} else {
//Redirect to a relative path
http.Redirect(w, r, state, http.StatusTemporaryRedirect) http.Redirect(w, r, state, http.StatusTemporaryRedirect)
}
return errors.New("authorized") return errors.New("authorized")
} }
unauthorized := false unauthorized := false

View File

@@ -330,7 +330,10 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
locationRewrite := res.Header.Get("Location") locationRewrite := res.Header.Get("Location")
originLocation := res.Header.Get("Location") originLocation := res.Header.Get("Location")
res.Header.Set("zr-origin-location", originLocation) res.Header.Set("zr-origin-location", originLocation)
decodedOriginLocation, err := url.PathUnescape(originLocation)
if err == nil {
originLocation = decodedOriginLocation
}
if strings.HasPrefix(originLocation, "http://") || strings.HasPrefix(originLocation, "https://") { if strings.HasPrefix(originLocation, "http://") || strings.HasPrefix(originLocation, "https://") {
//Full path //Full path
//Replace the forwarded target with expected Host //Replace the forwarded target with expected Host

View File

@@ -47,6 +47,7 @@ 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{
@@ -56,6 +57,7 @@ func (m *Manager) HandleAddProxyConfig(w http.ResponseWriter, r *http.Request) {
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
@@ -86,8 +89,20 @@ 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,
NewTimeout: newTimeout,
}
// 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(newConfig)
if err != nil { if err != nil {
utils.SendErrorResponse(w, err.Error()) utils.SendErrorResponse(w, err.Error())
return return

View File

@@ -0,0 +1,97 @@
package streamproxy
/*
Instances.go
This file contains the methods to start, stop, and manage the proxy relay instances.
*/
import (
"errors"
"time"
)
// Start a proxy if stopped
func (c *ProxyRelayInstance) Start() error {
if c.IsRunning() {
c.Running = true
return errors.New("proxy already running")
}
// Create a stopChan to control the loop
tcpStopChan := make(chan bool)
udpStopChan := make(chan bool)
//Start the proxy service
if c.UseUDP {
c.udpStopChan = udpStopChan
go func() {
err := c.ForwardUDP(c.ListeningAddress, c.ProxyTargetAddr, udpStopChan)
if err != nil {
if !c.UseTCP {
c.Running = false
c.udpStopChan = nil
c.parent.SaveConfigToDatabase()
}
c.parent.logf("[proto:udp] Error starting stream proxy "+c.Name+"("+c.UUID+")", err)
}
}()
}
if c.UseTCP {
c.tcpStopChan = tcpStopChan
go func() {
//Default to transport mode
err := c.Port2host(c.ListeningAddress, c.ProxyTargetAddr, tcpStopChan)
if err != nil {
c.Running = false
c.tcpStopChan = nil
c.parent.SaveConfigToDatabase()
c.parent.logf("[proto:tcp] Error starting stream proxy "+c.Name+"("+c.UUID+")", err)
}
}()
}
//Successfully spawned off the proxy routine
c.Running = true
c.parent.SaveConfigToDatabase()
return nil
}
// Return if a proxy config is running
func (c *ProxyRelayInstance) IsRunning() bool {
return c.tcpStopChan != nil || c.udpStopChan != nil
}
// Restart a proxy config
func (c *ProxyRelayInstance) Restart() {
if c.IsRunning() {
c.Stop()
}
time.Sleep(3000 * time.Millisecond)
c.Start()
}
// Stop a running proxy if running
func (c *ProxyRelayInstance) Stop() {
c.parent.logf("Stopping Stream Proxy "+c.Name, nil)
if c.udpStopChan != nil {
c.parent.logf("Stopping UDP for "+c.Name, nil)
c.udpStopChan <- true
c.udpStopChan = nil
}
if c.tcpStopChan != nil {
c.parent.logf("Stopping TCP for "+c.Name, nil)
c.tcpStopChan <- true
c.tcpStopChan = nil
}
c.parent.logf("Stopped Stream Proxy "+c.Name, nil)
c.Running = false
//Update the running status
c.parent.SaveConfigToDatabase()
}

View File

@@ -8,7 +8,6 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"imuslab.com/zoraxy/mod/info/logger" "imuslab.com/zoraxy/mod/info/logger"
@@ -30,9 +29,23 @@ type ProxyRelayOptions struct {
Timeout int Timeout int
UseTCP bool UseTCP bool
UseUDP bool UseUDP bool
UseProxyProtocol bool
} }
type ProxyRelayConfig struct { // 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
NewTimeout int //New timeout for the connection, leave -1 for no change
}
type ProxyRelayInstance struct {
/* 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
@@ -41,7 +54,10 @@ 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
/* Internal */
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
aTobAccumulatedByteTransfer atomic.Int64 //Accumulated byte transfer from A to B aTobAccumulatedByteTransfer atomic.Int64 //Accumulated byte transfer from A to B
@@ -60,13 +76,14 @@ type Options struct {
type Manager struct { type Manager struct {
//Config and stores //Config and stores
Options *Options Options *Options
Configs []*ProxyRelayConfig Configs []*ProxyRelayInstance
//Realtime Statistics //Realtime Statistics
Connections int //currently connected connect counts Connections int //currently connected connect counts
} }
// NewStreamProxy creates a new stream proxy manager with the given options
func NewStreamProxy(options *Options) (*Manager, error) { func NewStreamProxy(options *Options) (*Manager, error) {
if !utils.FileExists(options.ConfigStore) { if !utils.FileExists(options.ConfigStore) {
err := os.MkdirAll(options.ConfigStore, 0775) err := os.MkdirAll(options.ConfigStore, 0775)
@@ -76,7 +93,7 @@ func NewStreamProxy(options *Options) (*Manager, error) {
} }
//Load relay configs from db //Load relay configs from db
previousRules := []*ProxyRelayConfig{} previousRules := []*ProxyRelayInstance{}
streamProxyConfigFiles, err := filepath.Glob(options.ConfigStore + "/*.config") streamProxyConfigFiles, err := filepath.Glob(options.ConfigStore + "/*.config")
if err != nil { if err != nil {
return nil, err return nil, err
@@ -89,7 +106,7 @@ func NewStreamProxy(options *Options) (*Manager, error) {
options.Logger.PrintAndLog("stream-prox", "Read stream proxy config failed", err) options.Logger.PrintAndLog("stream-prox", "Read stream proxy config failed", err)
continue continue
} }
thisRelayConfig := &ProxyRelayConfig{} thisRelayConfig := &ProxyRelayInstance{}
err = json.Unmarshal(configBytes, thisRelayConfig) err = json.Unmarshal(configBytes, thisRelayConfig)
if err != nil { if err != nil {
options.Logger.PrintAndLog("stream-prox", "Unmarshal stream proxy config failed", err) options.Logger.PrintAndLog("stream-prox", "Unmarshal stream proxy config failed", err)
@@ -142,6 +159,7 @@ func (m *Manager) logf(message string, originalError error) {
m.Options.Logger.PrintAndLog("stream-prox", message, originalError) m.Options.Logger.PrintAndLog("stream-prox", message, originalError)
} }
// NewConfig creates a new proxy relay config with the given options
func (m *Manager) NewConfig(config *ProxyRelayOptions) string { func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
//Generate two zero value for atomic int64 //Generate two zero value for atomic int64
aAcc := atomic.Int64{} aAcc := atomic.Int64{}
@@ -150,13 +168,14 @@ func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
bAcc.Store(0) bAcc.Store(0)
//Generate a new config from options //Generate a new config from options
configUUID := uuid.New().String() configUUID := uuid.New().String()
thisConfig := ProxyRelayConfig{ thisConfig := ProxyRelayInstance{
UUID: configUUID, UUID: configUUID,
Name: config.Name, Name: config.Name,
ListeningAddress: config.ListeningAddr, ListeningAddress: config.ListeningAddr,
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,
@@ -170,7 +189,7 @@ func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
return configUUID return configUUID
} }
func (m *Manager) GetConfigByUUID(configUUID string) (*ProxyRelayConfig, error) { func (m *Manager) GetConfigByUUID(configUUID string) (*ProxyRelayInstance, error) {
// Find and return the config with the specified UUID // Find and return the config with the specified UUID
for _, config := range m.Configs { for _, config := range m.Configs {
if config.UUID == configUUID { if config.UUID == configUUID {
@@ -181,32 +200,33 @@ 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(newConfig *ProxyRuleUpdateConfig) error {
// Find the config with the specified UUID // Find the config with the specified UUID
foundConfig, err := m.GetConfigByUUID(configUUID) foundConfig, err := m.GetConfigByUUID(newConfig.InstanceUUID)
if err != nil { if err != nil {
return err return err
} }
// Validate and update the fields // Validate and update the fields
if newName != "" { if newConfig.NewName != "" {
foundConfig.Name = newName foundConfig.Name = newConfig.NewName
} }
if newListeningAddr != "" { if newConfig.NewListeningAddr != "" {
foundConfig.ListeningAddress = newListeningAddr foundConfig.ListeningAddress = newConfig.NewListeningAddr
} }
if newProxyAddr != "" { if newConfig.NewProxyAddr != "" {
foundConfig.ProxyTargetAddr = newProxyAddr foundConfig.ProxyTargetAddr = newConfig.NewProxyAddr
} }
foundConfig.UseTCP = useTCP foundConfig.UseTCP = newConfig.UseTCP
foundConfig.UseUDP = useUDP foundConfig.UseUDP = newConfig.UseUDP
foundConfig.UseProxyProtocol = newConfig.UseProxyProtocol
if newTimeout != -1 { if newConfig.NewTimeout != -1 {
if newTimeout < 0 { if newConfig.NewTimeout < 0 {
return errors.New("invalid timeout value given") return errors.New("invalid timeout value given")
} }
foundConfig.Timeout = newTimeout foundConfig.Timeout = newConfig.NewTimeout
} }
m.SaveConfigToDatabase() m.SaveConfigToDatabase()
@@ -215,12 +235,11 @@ func (m *Manager) EditConfig(configUUID string, newName string, newListeningAddr
if foundConfig.IsRunning() { if foundConfig.IsRunning() {
foundConfig.Restart() foundConfig.Restart()
} }
return nil return nil
} }
// Remove the config from file by UUID
func (m *Manager) RemoveConfig(configUUID string) error { func (m *Manager) RemoveConfig(configUUID string) error {
//Remove the config from file
err := os.Remove(filepath.Join(m.Options.ConfigStore, configUUID+".config")) err := os.Remove(filepath.Join(m.Options.ConfigStore, configUUID+".config"))
if err != nil { if err != nil {
return err return err
@@ -250,91 +269,3 @@ func (m *Manager) SaveConfigToDatabase() {
} }
} }
} }
/*
Config Functions
*/
// Start a proxy if stopped
func (c *ProxyRelayConfig) Start() error {
if c.IsRunning() {
c.Running = true
return errors.New("proxy already running")
}
// Create a stopChan to control the loop
tcpStopChan := make(chan bool)
udpStopChan := make(chan bool)
//Start the proxy service
if c.UseUDP {
c.udpStopChan = udpStopChan
go func() {
err := c.ForwardUDP(c.ListeningAddress, c.ProxyTargetAddr, udpStopChan)
if err != nil {
if !c.UseTCP {
c.Running = false
c.udpStopChan = nil
c.parent.SaveConfigToDatabase()
}
c.parent.logf("[proto:udp] Error starting stream proxy "+c.Name+"("+c.UUID+")", err)
}
}()
}
if c.UseTCP {
c.tcpStopChan = tcpStopChan
go func() {
//Default to transport mode
err := c.Port2host(c.ListeningAddress, c.ProxyTargetAddr, tcpStopChan)
if err != nil {
c.Running = false
c.tcpStopChan = nil
c.parent.SaveConfigToDatabase()
c.parent.logf("[proto:tcp] Error starting stream proxy "+c.Name+"("+c.UUID+")", err)
}
}()
}
//Successfully spawned off the proxy routine
c.Running = true
c.parent.SaveConfigToDatabase()
return nil
}
// Return if a proxy config is running
func (c *ProxyRelayConfig) IsRunning() bool {
return c.tcpStopChan != nil || c.udpStopChan != nil
}
// Restart a proxy config
func (c *ProxyRelayConfig) Restart() {
if c.IsRunning() {
c.Stop()
}
time.Sleep(3000 * time.Millisecond)
c.Start()
}
// Stop a running proxy if running
func (c *ProxyRelayConfig) Stop() {
c.parent.logf("Stopping Stream Proxy "+c.Name, nil)
if c.udpStopChan != nil {
c.parent.logf("Stopping UDP for "+c.Name, nil)
c.udpStopChan <- true
c.udpStopChan = nil
}
if c.tcpStopChan != nil {
c.parent.logf("Stopping TCP for "+c.Name, nil)
c.tcpStopChan <- true
c.tcpStopChan = nil
}
c.parent.logf("Stopped Stream Proxy "+c.Name, nil)
c.Running = false
//Update the running status
c.parent.SaveConfigToDatabase()
}

View File

@@ -12,7 +12,7 @@ func TestPort2Port(t *testing.T) {
stopChan := make(chan bool) stopChan := make(chan bool)
// Create a ProxyRelayConfig with dummy values // Create a ProxyRelayConfig with dummy values
config := &streamproxy.ProxyRelayConfig{ config := &streamproxy.ProxyRelayInstance{
Timeout: 1, Timeout: 1,
} }

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
@@ -54,7 +72,7 @@ func forward(conn1 net.Conn, conn2 net.Conn, aTob *atomic.Int64, bToa *atomic.In
wg.Wait() wg.Wait()
} }
func (c *ProxyRelayConfig) accept(listener net.Listener) (net.Conn, error) { func (c *ProxyRelayInstance) accept(listener net.Listener) (net.Conn, error) {
conn, err := listener.Accept() conn, err := listener.Accept()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -92,7 +110,7 @@ func startListener(address string) (net.Listener, error) {
portA -> server portA -> server
server -> portB server -> portB
*/ */
func (c *ProxyRelayConfig) Port2host(allowPort string, targetAddress string, stopChan chan bool) error { func (c *ProxyRelayInstance) Port2host(allowPort string, targetAddress string, stopChan chan bool) error {
listenerStartingAddr := allowPort listenerStartingAddr := allowPort
if isValidPort(allowPort) { if isValidPort(allowPort) {
//number only, e.g. 8080 //number only, e.g. 8080
@@ -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

@@ -53,7 +53,7 @@ func initUDPConnections(listenAddr string, targetAddress string) (*net.UDPConn,
} }
// Go routine which manages connection from server to single client // Go routine which manages connection from server to single client
func (c *ProxyRelayConfig) RunUDPConnectionRelay(conn *udpClientServerConn, lisenter *net.UDPConn) { func (c *ProxyRelayInstance) RunUDPConnectionRelay(conn *udpClientServerConn, lisenter *net.UDPConn) {
var buffer [1500]byte var buffer [1500]byte
for { for {
// Read from server // Read from server
@@ -74,7 +74,7 @@ func (c *ProxyRelayConfig) RunUDPConnectionRelay(conn *udpClientServerConn, lise
} }
// Close all connections that waiting for read from server // Close all connections that waiting for read from server
func (c *ProxyRelayConfig) CloseAllUDPConnections() { func (c *ProxyRelayInstance) CloseAllUDPConnections() {
c.udpClientMap.Range(func(clientAddr, clientServerConn interface{}) bool { c.udpClientMap.Range(func(clientAddr, clientServerConn interface{}) bool {
conn := clientServerConn.(*udpClientServerConn) conn := clientServerConn.(*udpClientServerConn)
conn.ServerConn.Close() conn.ServerConn.Close()
@@ -82,7 +82,7 @@ func (c *ProxyRelayConfig) CloseAllUDPConnections() {
}) })
} }
func (c *ProxyRelayConfig) 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
if isValidPort(address1) { if isValidPort(address1) {
@@ -90,8 +90,8 @@ func (c *ProxyRelayConfig) ForwardUDP(address1, address2 string, stopChan chan b
address1 = ":" + address1 address1 = ":" + address1
} }
if strings.HasPrefix(address1, ":") { if strings.HasPrefix(address1, ":") {
//Prepend 127.0.0.1 to the address //Prepend 0.0.0.0 to the address
address1 = "127.0.0.1" + address1 address1 = "0.0.0.0" + address1
} }
lisener, targetAddr, err := initUDPConnections(address1, address2) lisener, targetAddr, err := initUDPConnections(address1, address2)

View File

@@ -69,6 +69,12 @@ func (ws *WebServer) HandlePortChange(w http.ResponseWriter, r *http.Request) {
return return
} }
// Check if newPort is a valid TCP port number (1-65535)
if newPort < 1 || newPort > 65535 {
utils.SendErrorResponse(w, "invalid port number given")
return
}
err = ws.ChangePort(strconv.Itoa(newPort)) err = ws.ChangePort(strconv.Itoa(newPort))
if err != nil { if err != nil {
utils.SendErrorResponse(w, err.Error()) utils.SendErrorResponse(w, err.Error())
@@ -106,6 +112,17 @@ func (ws *WebServer) SetDisableListenToAllInterface(w http.ResponseWriter, r *ht
utils.SendErrorResponse(w, "unable to save setting") utils.SendErrorResponse(w, "unable to save setting")
return return
} }
// Update the option in the web server instance
ws.option.DisableListenToAllInterface = disableListen ws.option.DisableListenToAllInterface = disableListen
// If the server is running and the setting is changed, we need to restart the server
if ws.IsRunning() {
err = ws.Restart()
if err != nil {
utils.SendErrorResponse(w, "unable to restart web server: "+err.Error())
return
}
}
utils.SendOK(w) utils.SendOK(w)
} }

View File

@@ -210,6 +210,27 @@ func (ws *WebServer) Stop() error {
return nil return nil
} }
func (ws *WebServer) Restart() error {
if ws.isRunning {
if err := ws.Stop(); err != nil {
return err
}
}
if err := ws.Start(); err != nil {
return err
}
ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server restarted. Listening on :"+ws.option.Port, nil)
return nil
}
func (ws *WebServer) IsRunning() bool {
ws.mu.Lock()
defer ws.mu.Unlock()
return ws.isRunning
}
// UpdateDirectoryListing enables or disables directory listing. // UpdateDirectoryListing enables or disables directory listing.
func (ws *WebServer) UpdateDirectoryListing(enable bool) { func (ws *WebServer) UpdateDirectoryListing(enable bool) {
ws.option.EnableDirectoryListing = enable ws.option.EnableDirectoryListing = enable

View File

@@ -203,7 +203,7 @@
<th>Destination</th> <th>Destination</th>
<th>Virtual Directory</th> <th>Virtual Directory</th>
<th class="no-sort">Tags</th> <th class="no-sort">Tags</th>
<th class="no-sort" style="width:50px; cursor: default !important;"></th> <th class="no-sort" style="width:100px; cursor: default !important;"></th>
</tr> </tr>
</thead> </thead>
<tbody id="httpProxyList"> <tbody id="httpProxyList">
@@ -588,7 +588,7 @@
</td> --> </td> -->
<td class="center aligned ignoremw" editable="true" datatype="action" data-label=""> <td class="center aligned ignoremw" editable="true" datatype="action" data-label="">
<button title="Edit Proxy Rule" class="ui circular small basic icon button editBtn inlineEditActionBtn" onclick='editEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="ellipsis vertical icon"></i></button> <button title="Edit Proxy Rule" class="ui circular small basic icon button editBtn inlineEditActionBtn" onclick='editEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="ellipsis vertical icon"></i></button>
<!-- <button title="Remove Proxy Rule" class="ui circular mini red basic icon button inlineEditActionBtn" onclick='deleteEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="trash icon"></i></button> --> <button title="Remove Proxy Rule" class="ui circular mini red basic icon button inlineEditActionBtn" onclick='deleteEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="trash icon"></i></button>
</td> </td>
</tr>`); </tr>`);
}); });

View File

@@ -3,18 +3,15 @@
<h2>SSO</h2> <h2>SSO</h2>
<p>Single Sign-On (SSO) and authentication providers settings </p> <p>Single Sign-On (SSO) and authentication providers settings </p>
</div> </div>
<div class="ui basic segment">
<div class="ui yellow message">
<div class="header">
Experimental Feature
</div>
<p>Please note that this feature is still in development and may not work as expected.</p>
</div>
</div>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="ui basic segment"> <div class="ui top attached tabular menu ssoTabs">
<h3>Forward Auth</h3> <a class="item active" data-tab="forward_auth_tab">Forward Auth</a>
<a class="item" data-tab="oauth2_tab">Oauth2</a>
<!-- <a class="item" data-tab="zoraxy_sso_tab">Zoraxy SSO</a> -->
</div>
<div class="ui bottom attached tab segment active" data-tab="forward_auth_tab">
<!-- Forward Auth -->
<h2>Forward Auth</h2>
<p>Configuration settings for the Forward Auth provider.</p> <p>Configuration settings for the Forward Auth provider.</p>
<p>The Forward Auth provider makes a subrequest to an authorization server that supports Forward Auth, then either:</p> <p>The Forward Auth provider makes a subrequest to an authorization server that supports Forward Auth, then either:</p>
<ul> <ul>
@@ -87,9 +84,9 @@
<button class="ui basic button" type="submit"><i class="green check icon"></i> Apply Change</button> <button class="ui basic button" type="submit"><i class="green check icon"></i> Apply Change</button>
</form> </form>
</div> </div>
<div class="ui divider"></div> <div class="ui bottom attached tab segment" data-tab="oauth2_tab">
<div class="ui basic segment"> <!-- Oauth 2 -->
<h3>OAuth 2.0</h3> <h2>OAuth 2.0</h2>
<p>Configuration settings for OAuth 2.0 authentication provider.</p> <p>Configuration settings for OAuth 2.0 authentication provider.</p>
<form class="ui form" action="#" id="oauth2Settings"> <form class="ui form" action="#" id="oauth2Settings">
@@ -135,10 +132,17 @@
<button class="ui basic button" type="submit"><i class="green check icon"></i> Apply Change</button> <button class="ui basic button" type="submit"><i class="green check icon"></i> Apply Change</button>
</form> </form>
</div> </div>
<div class="ui divider"></div> <div class="ui bottom attached tab segment" data-tab="zoraxy_sso_tab">
<!-- Zoraxy SSO -->
<h3>Zoraxy SSO</h3>
<p>Configuration settings for Zoraxy SSO provider.</p>
<p>Currently not implemented.</p>
</div>
</div> </div>
<script> <script>
$(".ssoTabs .item").tab();
$(document).ready(function() { $(document).ready(function() {
/* Load forward-auth settings from backend */ /* Load forward-auth settings from backend */
$.cjax({ $.cjax({

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

View File

@@ -343,7 +343,9 @@
} }
$(editorSideWrapper).each(function(){ $(editorSideWrapper).each(function(){
if ($(this)[0].contentWindow.setDarkTheme){
$(this)[0].contentWindow.setDarkTheme(false); $(this)[0].contentWindow.setDarkTheme(false);
}
}) })
if ($("#pluginContextLoader").is(":visible")){ if ($("#pluginContextLoader").is(":visible")){
@@ -356,7 +358,9 @@
$(".sideWrapper iframe")[0].contentWindow.setDarkTheme(true); $(".sideWrapper iframe")[0].contentWindow.setDarkTheme(true);
} }
$(editorSideWrapper).each(function(){ $(editorSideWrapper).each(function(){
if ($(this)[0].contentWindow.setDarkTheme){
$(this)[0].contentWindow.setDarkTheme(true); $(this)[0].contentWindow.setDarkTheme(true);
}
}) })
if ($("#pluginContextLoader").is(":visible")){ if ($("#pluginContextLoader").is(":visible")){
$("#pluginContextLoader")[0].contentWindow.setDarkTheme(true); $("#pluginContextLoader")[0].contentWindow.setDarkTheme(true);