mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-07-08 07:11:45 +02:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
45506c8772 | |||
c091b9d1ca | |||
691cb603ce | |||
e53724d6e5 | |||
273cae2a98 | |||
6b3b89f7bf | |||
2d611a559a | |||
6c5eba01c2 | |||
f641797d10 | |||
f92ff068f3 | |||
b59ac47c8c | |||
8030f3d62a | |||
f8f623e3e4 | |||
061839756c | |||
1dcaa0c257 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -56,4 +56,5 @@ log
|
|||||||
tmp
|
tmp
|
||||||
sys.*
|
sys.*
|
||||||
www/html/index.html
|
www/html/index.html
|
||||||
*.exe
|
*.exe
|
||||||
|
/src/dist
|
||||||
|
33
CHANGELOG.md
33
CHANGELOG.md
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -34,6 +34,7 @@ func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) {
|
|||||||
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
|
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
|
||||||
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
||||||
|
authRouter.HandleFunc("/api/proxy/setTlsConfig", ReverseProxyHandleSetTlsConfig)
|
||||||
authRouter.HandleFunc("/api/proxy/setHostname", ReverseProxyHandleSetHostname)
|
authRouter.HandleFunc("/api/proxy/setHostname", ReverseProxyHandleSetHostname)
|
||||||
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
||||||
@ -79,6 +80,7 @@ func RegisterTLSAPIs(authRouter *auth.RouterDef) {
|
|||||||
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
|
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
|
||||||
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
|
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
|
||||||
authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
|
authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
|
||||||
|
authRouter.HandleFunc("/api/cert/resolve", handleCertTryResolve)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the APIs for Authentication handlers like Forward Auth and OAUTH2
|
// Register the APIs for Authentication handlers like Forward Auth and OAUTH2
|
||||||
|
89
src/cert.go
89
src/cert.go
@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -101,6 +102,13 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
|
|||||||
results = append(results, &thisCertInfo)
|
results = append(results, &thisCertInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convert ExpireDate to date object and sort asc
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
date1, _ := time.Parse("2006-01-02 15:04:05", results[i].ExpireDate)
|
||||||
|
date2, _ := time.Parse("2006-01-02 15:04:05", results[j].ExpireDate)
|
||||||
|
return date1.Before(date2)
|
||||||
|
})
|
||||||
|
|
||||||
js, _ := json.Marshal(results)
|
js, _ := json.Marshal(results)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Write(js)
|
w.Write(js)
|
||||||
@ -352,6 +360,87 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
|||||||
fmt.Fprintln(w, "File upload successful!")
|
fmt.Fprintln(w, "File upload successful!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleCertTryResolve(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get the domain
|
||||||
|
domain, err := utils.GetPara(r, "domain")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid domain given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the proxy rule, the pass in domain value must be root or matching domain
|
||||||
|
proxyRule, err := dynamicProxyRouter.GetProxyEndpointById(domain, false)
|
||||||
|
if err != nil {
|
||||||
|
//Try to resolve the domain via alias
|
||||||
|
proxyRule, err = dynamicProxyRouter.GetProxyEndpointByAlias(domain)
|
||||||
|
if err != nil {
|
||||||
|
//No matching rule found
|
||||||
|
utils.SendErrorResponse(w, "proxy rule not found for domain: "+domain)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// list all the alias domains for this rule
|
||||||
|
allDomains := []string{proxyRule.RootOrMatchingDomain}
|
||||||
|
aliasDomains := []string{}
|
||||||
|
for _, alias := range proxyRule.MatchingDomainAlias {
|
||||||
|
if alias != "" {
|
||||||
|
aliasDomains = append(aliasDomains, alias)
|
||||||
|
allDomains = append(allDomains, alias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to resolve the domain
|
||||||
|
domainKeyPairs := map[string]string{}
|
||||||
|
for _, thisDomain := range allDomains {
|
||||||
|
pubkey, prikey, err := tlsCertManager.GetCertificateByHostname(thisDomain)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Make sure pubkey and private key are not empty
|
||||||
|
if pubkey == "" || prikey == "" {
|
||||||
|
domainKeyPairs[thisDomain] = ""
|
||||||
|
} else {
|
||||||
|
//Store the key pair
|
||||||
|
keyname := strings.TrimSuffix(filepath.Base(pubkey), filepath.Ext(pubkey))
|
||||||
|
if keyname == "localhost" {
|
||||||
|
//Internal certs like localhost should not be used
|
||||||
|
//report as "fallback" key
|
||||||
|
keyname = "fallback certificate"
|
||||||
|
}
|
||||||
|
domainKeyPairs[thisDomain] = keyname
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//A domain must be UseDNSValidation if it is a wildcard domain or its alias is a wildcard domain
|
||||||
|
useDNSValidation := strings.HasPrefix(proxyRule.RootOrMatchingDomain, "*")
|
||||||
|
for _, alias := range aliasDomains {
|
||||||
|
if strings.HasPrefix(alias, "*") || strings.HasPrefix(domain, "*") {
|
||||||
|
useDNSValidation = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CertInfo struct {
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
AliasDomains []string `json:"alias_domains"`
|
||||||
|
DomainKeyPair map[string]string `json:"domain_key_pair"`
|
||||||
|
UseDNSValidation bool `json:"use_dns_validation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &CertInfo{
|
||||||
|
Domain: proxyRule.RootOrMatchingDomain,
|
||||||
|
AliasDomains: aliasDomains,
|
||||||
|
DomainKeyPair: domainKeyPairs,
|
||||||
|
UseDNSValidation: useDNSValidation,
|
||||||
|
}
|
||||||
|
|
||||||
|
js, _ := json.Marshal(result)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
||||||
|
|
||||||
// Handle cert remove
|
// Handle cert remove
|
||||||
func handleCertRemove(w http.ResponseWriter, r *http.Request) {
|
func handleCertRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
domain, err := utils.PostPara(r, "domain")
|
domain, err := utils.PostPara(r, "domain")
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
|
"imuslab.com/zoraxy/mod/tlscert"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -59,12 +60,18 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
|||||||
thisConfigEndpoint.Tags = []string{}
|
thisConfigEndpoint.Tags = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Make sure the TLS options are not nil
|
||||||
|
if thisConfigEndpoint.TlsOptions == nil {
|
||||||
|
thisConfigEndpoint.TlsOptions = tlscert.GetDefaultHostSpecificTlsBehavior()
|
||||||
|
}
|
||||||
|
|
||||||
//Matching domain not set. Assume root
|
//Matching domain not set. Assume root
|
||||||
if thisConfigEndpoint.RootOrMatchingDomain == "" {
|
if thisConfigEndpoint.RootOrMatchingDomain == "" {
|
||||||
thisConfigEndpoint.RootOrMatchingDomain = "/"
|
thisConfigEndpoint.RootOrMatchingDomain = "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyTypeRoot {
|
switch thisConfigEndpoint.ProxyType {
|
||||||
|
case dynamicproxy.ProxyTypeRoot:
|
||||||
//This is a root config file
|
//This is a root config file
|
||||||
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -73,7 +80,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
|||||||
|
|
||||||
dynamicProxyRouter.SetProxyRouteAsRoot(rootProxyEndpoint)
|
dynamicProxyRouter.SetProxyRouteAsRoot(rootProxyEndpoint)
|
||||||
|
|
||||||
} else if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyTypeHost {
|
case dynamicproxy.ProxyTypeHost:
|
||||||
//This is a host config file
|
//This is a host config file
|
||||||
readyProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
readyProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -81,7 +88,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dynamicProxyRouter.AddProxyRouteToRuntime(readyProxyEndpoint)
|
dynamicProxyRouter.AddProxyRouteToRuntime(readyProxyEndpoint)
|
||||||
} else {
|
default:
|
||||||
return errors.New("not supported proxy type")
|
return errors.New("not supported proxy type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
123
src/mod/dynamicproxy/permissionpolicy/contentsecuritypolicy.go
Normal file
123
src/mod/dynamicproxy/permissionpolicy/contentsecuritypolicy.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package permissionpolicy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Content Security Policy
|
||||||
|
|
||||||
|
This is a content security policy header modifier that changes
|
||||||
|
the request content security policy fields
|
||||||
|
|
||||||
|
author: tobychui
|
||||||
|
|
||||||
|
//TODO: intergrate this with the dynamic proxy module
|
||||||
|
*/
|
||||||
|
|
||||||
|
type ContentSecurityPolicy struct {
|
||||||
|
DefaultSrc []string `json:"default_src"`
|
||||||
|
ScriptSrc []string `json:"script_src"`
|
||||||
|
StyleSrc []string `json:"style_src"`
|
||||||
|
ImgSrc []string `json:"img_src"`
|
||||||
|
ConnectSrc []string `json:"connect_src"`
|
||||||
|
FontSrc []string `json:"font_src"`
|
||||||
|
ObjectSrc []string `json:"object_src"`
|
||||||
|
MediaSrc []string `json:"media_src"`
|
||||||
|
FrameSrc []string `json:"frame_src"`
|
||||||
|
WorkerSrc []string `json:"worker_src"`
|
||||||
|
ChildSrc []string `json:"child_src"`
|
||||||
|
ManifestSrc []string `json:"manifest_src"`
|
||||||
|
PrefetchSrc []string `json:"prefetch_src"`
|
||||||
|
FormAction []string `json:"form_action"`
|
||||||
|
FrameAncestors []string `json:"frame_ancestors"`
|
||||||
|
BaseURI []string `json:"base_uri"`
|
||||||
|
Sandbox []string `json:"sandbox"`
|
||||||
|
ReportURI []string `json:"report_uri"`
|
||||||
|
ReportTo []string `json:"report_to"`
|
||||||
|
UpgradeInsecureRequests bool `json:"upgrade_insecure_requests"`
|
||||||
|
BlockAllMixedContent bool `json:"block_all_mixed_content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDefaultContentSecurityPolicy returns a ContentSecurityPolicy struct with default permissive settings
|
||||||
|
func GetDefaultContentSecurityPolicy() *ContentSecurityPolicy {
|
||||||
|
return &ContentSecurityPolicy{
|
||||||
|
DefaultSrc: []string{"*"},
|
||||||
|
ScriptSrc: []string{"*"},
|
||||||
|
StyleSrc: []string{"*"},
|
||||||
|
ImgSrc: []string{"*"},
|
||||||
|
ConnectSrc: []string{"*"},
|
||||||
|
FontSrc: []string{"*"},
|
||||||
|
ObjectSrc: []string{"*"},
|
||||||
|
MediaSrc: []string{"*"},
|
||||||
|
FrameSrc: []string{"*"},
|
||||||
|
WorkerSrc: []string{"*"},
|
||||||
|
ChildSrc: []string{"*"},
|
||||||
|
ManifestSrc: []string{"*"},
|
||||||
|
PrefetchSrc: []string{"*"},
|
||||||
|
FormAction: []string{"*"},
|
||||||
|
FrameAncestors: []string{"*"},
|
||||||
|
BaseURI: []string{"*"},
|
||||||
|
Sandbox: []string{},
|
||||||
|
ReportURI: []string{},
|
||||||
|
ReportTo: []string{},
|
||||||
|
UpgradeInsecureRequests: false,
|
||||||
|
BlockAllMixedContent: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToHeader converts a ContentSecurityPolicy struct into a CSP header key-value pair
|
||||||
|
func (csp *ContentSecurityPolicy) ToHeader() []string {
|
||||||
|
directives := []string{}
|
||||||
|
|
||||||
|
addDirective := func(name string, sources []string) {
|
||||||
|
if len(sources) > 0 {
|
||||||
|
directives = append(directives, name+" "+strings.Join(sources, " "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addDirective("default-src", csp.DefaultSrc)
|
||||||
|
addDirective("script-src", csp.ScriptSrc)
|
||||||
|
addDirective("style-src", csp.StyleSrc)
|
||||||
|
addDirective("img-src", csp.ImgSrc)
|
||||||
|
addDirective("connect-src", csp.ConnectSrc)
|
||||||
|
addDirective("font-src", csp.FontSrc)
|
||||||
|
addDirective("object-src", csp.ObjectSrc)
|
||||||
|
addDirective("media-src", csp.MediaSrc)
|
||||||
|
addDirective("frame-src", csp.FrameSrc)
|
||||||
|
addDirective("worker-src", csp.WorkerSrc)
|
||||||
|
addDirective("child-src", csp.ChildSrc)
|
||||||
|
addDirective("manifest-src", csp.ManifestSrc)
|
||||||
|
addDirective("prefetch-src", csp.PrefetchSrc)
|
||||||
|
addDirective("form-action", csp.FormAction)
|
||||||
|
addDirective("frame-ancestors", csp.FrameAncestors)
|
||||||
|
addDirective("base-uri", csp.BaseURI)
|
||||||
|
if len(csp.Sandbox) > 0 {
|
||||||
|
directives = append(directives, "sandbox "+strings.Join(csp.Sandbox, " "))
|
||||||
|
}
|
||||||
|
if len(csp.ReportURI) > 0 {
|
||||||
|
addDirective("report-uri", csp.ReportURI)
|
||||||
|
}
|
||||||
|
if len(csp.ReportTo) > 0 {
|
||||||
|
addDirective("report-to", csp.ReportTo)
|
||||||
|
}
|
||||||
|
if csp.UpgradeInsecureRequests {
|
||||||
|
directives = append(directives, "upgrade-insecure-requests")
|
||||||
|
}
|
||||||
|
if csp.BlockAllMixedContent {
|
||||||
|
directives = append(directives, "block-all-mixed-content")
|
||||||
|
}
|
||||||
|
|
||||||
|
headerValue := strings.Join(directives, "; ")
|
||||||
|
return []string{"Content-Security-Policy", headerValue}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InjectContentSecurityPolicyHeader injects the CSP header into the response
|
||||||
|
func InjectContentSecurityPolicyHeader(w http.ResponseWriter, csp *ContentSecurityPolicy) {
|
||||||
|
if csp == nil || w.Header().Get("Content-Security-Policy") != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
headerKV := csp.ToHeader()
|
||||||
|
w.Header().Set(headerKV[0], headerKV[1])
|
||||||
|
}
|
@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -105,3 +106,49 @@ func (router *Router) RemoveProxyEndpointByRootname(rootnameOrMatchingDomain str
|
|||||||
|
|
||||||
return targetEpt.Remove()
|
return targetEpt.Remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProxyEndpointById retrieves a proxy endpoint by its ID from the Router's ProxyEndpoints map.
|
||||||
|
// It returns the ProxyEndpoint if found, or an error if not found.
|
||||||
|
func (h *Router) GetProxyEndpointById(searchingDomain string, includeAlias bool) (*ProxyEndpoint, error) {
|
||||||
|
var found *ProxyEndpoint
|
||||||
|
h.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
||||||
|
proxy, ok := value.(*ProxyEndpoint)
|
||||||
|
if ok && (proxy.RootOrMatchingDomain == searchingDomain || (includeAlias && utils.StringInArray(proxy.MatchingDomainAlias, searchingDomain))) {
|
||||||
|
found = proxy
|
||||||
|
return false // stop iteration
|
||||||
|
}
|
||||||
|
return true // continue iteration
|
||||||
|
})
|
||||||
|
if found != nil {
|
||||||
|
return found, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("proxy rule with given id not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Router) GetProxyEndpointByAlias(alias string) (*ProxyEndpoint, error) {
|
||||||
|
var found *ProxyEndpoint
|
||||||
|
h.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
||||||
|
proxy, ok := value.(*ProxyEndpoint)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
//Also check for wildcard aliases that matches the alias
|
||||||
|
for _, thisAlias := range proxy.MatchingDomainAlias {
|
||||||
|
if ok && thisAlias == alias {
|
||||||
|
found = proxy
|
||||||
|
return false // stop iteration
|
||||||
|
} else if ok && strings.HasPrefix(thisAlias, "*") {
|
||||||
|
//Check if the alias matches a wildcard alias
|
||||||
|
if strings.HasSuffix(alias, thisAlias[1:]) {
|
||||||
|
found = proxy
|
||||||
|
return false // stop iteration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true // continue iteration
|
||||||
|
})
|
||||||
|
if found != nil {
|
||||||
|
return found, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("proxy rule with given alias not found")
|
||||||
|
}
|
||||||
|
@ -175,7 +175,8 @@ type ProxyEndpoint struct {
|
|||||||
Disabled bool //If the rule is disabled
|
Disabled bool //If the rule is disabled
|
||||||
|
|
||||||
//Inbound TLS/SSL Related
|
//Inbound TLS/SSL Related
|
||||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||||
|
TlsOptions *tlscert.HostSpecificTlsBehavior //TLS options for this endpoint, if nil, use global TLS options
|
||||||
|
|
||||||
//Virtual Directories
|
//Virtual Directories
|
||||||
VirtualDirectories []*VirtualDirectoryEndpoint
|
VirtualDirectories []*VirtualDirectoryEndpoint
|
||||||
|
@ -47,15 +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")
|
||||||
|
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,
|
||||||
|
EnableLogging: enableLogging,
|
||||||
})
|
})
|
||||||
|
|
||||||
js, _ := json.Marshal(newConfigUUID)
|
js, _ := json.Marshal(newConfigUUID)
|
||||||
@ -75,6 +79,8 @@ 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")
|
||||||
|
enableLogging, _ := utils.PostBool(r, "enableLogging")
|
||||||
|
|
||||||
newTimeoutStr, _ := utils.PostPara(r, "timeout")
|
newTimeoutStr, _ := utils.PostPara(r, "timeout")
|
||||||
newTimeout := -1
|
newTimeout := -1
|
||||||
@ -86,8 +92,21 @@ 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,
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
110
src/mod/streamproxy/instances.go
Normal file
110
src/mod/streamproxy/instances.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package streamproxy
|
||||||
|
|
||||||
|
/*
|
||||||
|
Instances.go
|
||||||
|
|
||||||
|
This file contains the methods to start, stop, and manage the proxy relay instances.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ProxyRelayInstance) LogMsg(message string, originalError error) {
|
||||||
|
if !c.EnableLogging {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if originalError != nil {
|
||||||
|
log.Println(message, "error:", originalError)
|
||||||
|
} else {
|
||||||
|
log.Println(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
@ -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"
|
||||||
@ -24,24 +23,44 @@ 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
|
||||||
|
EnableLogging bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyRelayConfig struct {
|
// ProxyRuleUpdateConfig is used to update the proxy rule config
|
||||||
UUID string //A UUIDv4 representing this config
|
type ProxyRuleUpdateConfig struct {
|
||||||
Name string //Name of the config
|
InstanceUUID string //The target instance UUID to update
|
||||||
Running bool //Status, read only
|
NewName string //New name for the instance, leave empty for no change
|
||||||
AutoStart bool //If the service suppose to started automatically
|
NewListeningAddr string //New listening address, leave empty for no change
|
||||||
ListeningAddress string //Listening Address, usually 127.0.0.1:port
|
NewProxyAddr string //New proxy target address, leave empty for no change
|
||||||
ProxyTargetAddr string //Proxy target address
|
UseTCP bool //Enable TCP proxy, default to false
|
||||||
UseTCP bool //Enable TCP proxy
|
UseUDP bool //Enable UDP proxy, default to false
|
||||||
UseUDP bool //Enable UDP proxy
|
UseProxyProtocol bool //Enable Proxy Protocol, default to false
|
||||||
Timeout int //Timeout for connection in sec
|
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
|
||||||
|
|
||||||
|
/* 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 +79,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 +96,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 +109,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 +162,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 +171,15 @@ 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,
|
||||||
|
EnableLogging: config.EnableLogging,
|
||||||
Timeout: config.Timeout,
|
Timeout: config.Timeout,
|
||||||
tcpStopChan: nil,
|
tcpStopChan: nil,
|
||||||
udpStopChan: nil,
|
udpStopChan: nil,
|
||||||
@ -170,7 +193,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 +204,34 @@ 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
|
||||||
|
foundConfig.EnableLogging = newConfig.EnableLogging
|
||||||
|
|
||||||
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 +240,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 +274,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()
|
|
||||||
}
|
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package streamproxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@ -30,48 +31,67 @@ func isValidPort(port string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup, accumulator *atomic.Int64) {
|
func (c *ProxyRelayInstance) connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup, accumulator *atomic.Int64) {
|
||||||
n, err := io.Copy(conn1, conn2)
|
n, err := io.Copy(conn1, conn2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
accumulator.Add(n) //Add to accumulator
|
accumulator.Add(n) //Add to accumulator
|
||||||
conn1.Close()
|
conn1.Close()
|
||||||
log.Println("[←]", "close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]")
|
c.LogMsg("[←] close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]", nil)
|
||||||
//conn2.Close()
|
//conn2.Close()
|
||||||
//log.Println("[←]", "close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]")
|
//c.LogMsg("[←] close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]", nil)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
func forward(conn1 net.Conn, conn2 net.Conn, aTob *atomic.Int64, bToa *atomic.Int64) {
|
func writeProxyProtocolHeaderV1(dst net.Conn, src net.Conn) error {
|
||||||
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
|
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 (c *ProxyRelayInstance) forward(conn1 net.Conn, conn2 net.Conn, aTob *atomic.Int64, bToa *atomic.Int64) {
|
||||||
|
msg := fmt.Sprintf("[+] start transmit. [%s],[%s] <-> [%s],[%s]",
|
||||||
|
conn1.LocalAddr().String(), conn1.RemoteAddr().String(),
|
||||||
|
conn2.LocalAddr().String(), conn2.RemoteAddr().String())
|
||||||
|
c.LogMsg(msg, nil)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
// wait tow goroutines
|
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
go connCopy(conn1, conn2, &wg, aTob)
|
go c.connCopy(conn1, conn2, &wg, aTob)
|
||||||
go connCopy(conn2, conn1, &wg, bToa)
|
go c.connCopy(conn2, conn1, &wg, bToa)
|
||||||
//blocking when the wg is locked
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if connection in blacklist or whitelist
|
// Check if connection in blacklist or whitelist
|
||||||
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||||
if !c.parent.Options.AccessControlHandler(conn) {
|
if !c.parent.Options.AccessControlHandler(conn) {
|
||||||
time.Sleep(300 * time.Millisecond)
|
time.Sleep(300 * time.Millisecond)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
log.Println("[x]", "Connection from "+addr.IP.String()+" rejected by access control policy")
|
c.LogMsg("[x] Connection from "+addr.IP.String()+" rejected by access control policy", nil)
|
||||||
return nil, errors.New("Connection from " + addr.IP.String() + " rejected by access control policy")
|
return nil, errors.New("Connection from " + addr.IP.String() + " rejected by access control policy")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("[√]", "accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]")
|
c.LogMsg("[√] accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]", nil)
|
||||||
return conn, err
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startListener(address string) (net.Listener, error) {
|
func startListener(address string) (net.Listener, error) {
|
||||||
@ -92,7 +112,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
|
||||||
@ -112,7 +132,7 @@ func (c *ProxyRelayConfig) Port2host(allowPort string, targetAddress string, sto
|
|||||||
//Start stop handler
|
//Start stop handler
|
||||||
go func() {
|
go func() {
|
||||||
<-stopChan
|
<-stopChan
|
||||||
log.Println("[x]", "Received stop signal. Exiting Port to Host forwarder")
|
c.LogMsg("[x] Received stop signal. Exiting Port to Host forwarder", nil)
|
||||||
server.Close()
|
server.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -129,18 +149,32 @@ func (c *ProxyRelayConfig) Port2host(allowPort string, targetAddress string, sto
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func(targetAddress string) {
|
go func(targetAddress string) {
|
||||||
log.Println("[+]", "start connect host:["+targetAddress+"]")
|
c.LogMsg("[+] start connect host:["+targetAddress+"]", nil)
|
||||||
target, err := net.Dial("tcp", targetAddress)
|
target, err := net.Dial("tcp", targetAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// temporarily unavailable, don't use fatal.
|
// temporarily unavailable, don't use fatal.
|
||||||
log.Println("[x]", "connect target address ["+targetAddress+"] faild. retry in ", c.Timeout, "seconds. ")
|
c.LogMsg("[x] connect target address ["+targetAddress+"] failed. retry in "+strconv.Itoa(c.Timeout)+" seconds.", nil)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
|
c.LogMsg("[←] close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]", nil)
|
||||||
time.Sleep(time.Duration(c.Timeout) * time.Second)
|
time.Sleep(time.Duration(c.Timeout) * time.Second)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("[→]", "connect target address ["+targetAddress+"] success.")
|
c.LogMsg("[→] connect target address ["+targetAddress+"] success.", nil)
|
||||||
forward(target, conn, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
|
|
||||||
|
if c.UseProxyProtocol {
|
||||||
|
c.LogMsg("[+] write proxy protocol header to target address ["+targetAddress+"]", nil)
|
||||||
|
err = writeProxyProtocolHeaderV1(target, conn)
|
||||||
|
if err != nil {
|
||||||
|
c.LogMsg("[x] Write proxy protocol header failed: "+err.Error(), nil)
|
||||||
|
target.Close()
|
||||||
|
conn.Close()
|
||||||
|
c.LogMsg("[←] close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]", nil)
|
||||||
|
time.Sleep(time.Duration(c.Timeout) * time.Second)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.forward(target, conn, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
|
||||||
}(targetAddress)
|
}(targetAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
@ -138,12 +138,12 @@ func (c *ProxyRelayConfig) ForwardUDP(address1, address2 string, stopChan chan b
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.udpClientMap.Store(saddr, conn)
|
c.udpClientMap.Store(saddr, conn)
|
||||||
log.Println("[UDP] Created new connection for client " + saddr)
|
c.LogMsg("[UDP] Created new connection for client "+saddr, nil)
|
||||||
// Fire up routine to manage new connection
|
// Fire up routine to manage new connection
|
||||||
go c.RunUDPConnectionRelay(conn, lisener)
|
go c.RunUDPConnectionRelay(conn, lisener)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.Println("[UDP] Found connection for client " + saddr)
|
c.LogMsg("[UDP] Found connection for client "+saddr, nil)
|
||||||
conn = rawConn.(*udpClientServerConn)
|
conn = rawConn.(*udpClientServerConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,11 +20,21 @@ type CertCache struct {
|
|||||||
PriKey string
|
PriKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HostSpecificTlsBehavior struct {
|
||||||
|
DisableSNI bool //If SNI is enabled for this server name
|
||||||
|
DisableLegacyCertificateMatching bool //If legacy certificate matching is disabled for this server name
|
||||||
|
EnableAutoHTTPS bool //If auto HTTPS is enabled for this server name
|
||||||
|
PreferredCertificate string //Preferred certificate for this server name, if empty, use the first matching certificate
|
||||||
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
CertStore string //Path where all the certs are stored
|
CertStore string //Path where all the certs are stored
|
||||||
LoadedCerts []*CertCache //A list of loaded certs
|
LoadedCerts []*CertCache //A list of loaded certs
|
||||||
Logger *logger.Logger //System wide logger for debug mesage
|
Logger *logger.Logger //System wide logger for debug mesage
|
||||||
verbal bool
|
|
||||||
|
/* External handlers */
|
||||||
|
hostSpecificTlsBehavior func(serverName string) (*HostSpecificTlsBehavior, error) // Function to get host specific TLS behavior, if nil, use global TLS options
|
||||||
|
verbal bool
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed localhost.pem localhost.key
|
//go:embed localhost.pem localhost.key
|
||||||
@ -50,10 +60,11 @@ func NewManager(certStore string, verbal bool, logger *logger.Logger) (*Manager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
thisManager := Manager{
|
thisManager := Manager{
|
||||||
CertStore: certStore,
|
CertStore: certStore,
|
||||||
LoadedCerts: []*CertCache{},
|
LoadedCerts: []*CertCache{},
|
||||||
verbal: verbal,
|
hostSpecificTlsBehavior: defaultHostSpecificTlsBehavior, //Default to no SNI and no auto HTTPS
|
||||||
Logger: logger,
|
verbal: verbal,
|
||||||
|
Logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := thisManager.UpdateLoadedCertList()
|
err := thisManager.UpdateLoadedCertList()
|
||||||
@ -64,6 +75,21 @@ func NewManager(certStore string, verbal bool, logger *logger.Logger) (*Manager,
|
|||||||
return &thisManager, nil
|
return &thisManager, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default host specific TLS behavior
|
||||||
|
// This is used when no specific TLS behavior is defined for a server name
|
||||||
|
func GetDefaultHostSpecificTlsBehavior() *HostSpecificTlsBehavior {
|
||||||
|
return &HostSpecificTlsBehavior{
|
||||||
|
DisableSNI: false,
|
||||||
|
DisableLegacyCertificateMatching: false,
|
||||||
|
EnableAutoHTTPS: false,
|
||||||
|
PreferredCertificate: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultHostSpecificTlsBehavior(serverName string) (*HostSpecificTlsBehavior, error) {
|
||||||
|
return GetDefaultHostSpecificTlsBehavior(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Update domain mapping from file
|
// Update domain mapping from file
|
||||||
func (m *Manager) UpdateLoadedCertList() error {
|
func (m *Manager) UpdateLoadedCertList() error {
|
||||||
//Get a list of certificates from file
|
//Get a list of certificates from file
|
||||||
@ -161,24 +187,11 @@ func (m *Manager) ListCerts() ([]string, error) {
|
|||||||
|
|
||||||
// Get a certificate from disk where its certificate matches with the helloinfo
|
// Get a certificate from disk where its certificate matches with the helloinfo
|
||||||
func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
//Check if the domain corrisponding cert exists
|
//Look for the certificate by hostname
|
||||||
pubKey := "./tmp/localhost.pem"
|
pubKey, priKey, err := m.GetCertificateByHostname(helloInfo.ServerName)
|
||||||
priKey := "./tmp/localhost.key"
|
if err != nil {
|
||||||
|
m.Logger.PrintAndLog("tls-router", "Failed to get certificate for "+helloInfo.ServerName, err)
|
||||||
if utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".pem")) && utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".key")) {
|
return nil, err
|
||||||
//Direct hit
|
|
||||||
pubKey = filepath.Join(m.CertStore, helloInfo.ServerName+".pem")
|
|
||||||
priKey = filepath.Join(m.CertStore, helloInfo.ServerName+".key")
|
|
||||||
} else if m.CertMatchExists(helloInfo.ServerName) {
|
|
||||||
//Use x509
|
|
||||||
pubKey, priKey = m.GetCertByX509CNHostname(helloInfo.ServerName)
|
|
||||||
} else {
|
|
||||||
//Fallback to legacy method of matching certificates
|
|
||||||
if m.DefaultCertExists() {
|
|
||||||
//Use default.pem and default.key
|
|
||||||
pubKey = filepath.Join(m.CertStore, "default.pem")
|
|
||||||
priKey = filepath.Join(m.CertStore, "default.key")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Load the cert and serve it
|
//Load the cert and serve it
|
||||||
@ -190,6 +203,51 @@ func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, err
|
|||||||
return &cer, nil
|
return &cer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCertificateByHostname returns the certificate and private key for a given hostname
|
||||||
|
func (m *Manager) GetCertificateByHostname(hostname string) (string, string, error) {
|
||||||
|
//Check if the domain corrisponding cert exists
|
||||||
|
pubKey := "./tmp/localhost.pem"
|
||||||
|
priKey := "./tmp/localhost.key"
|
||||||
|
|
||||||
|
tlsBehavior, err := m.hostSpecificTlsBehavior(hostname)
|
||||||
|
if err != nil {
|
||||||
|
tlsBehavior, _ = defaultHostSpecificTlsBehavior(hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsBehavior.DisableSNI && tlsBehavior.PreferredCertificate != "" &&
|
||||||
|
utils.FileExists(filepath.Join(m.CertStore, tlsBehavior.PreferredCertificate+".pem")) &&
|
||||||
|
utils.FileExists(filepath.Join(m.CertStore, tlsBehavior.PreferredCertificate+".key")) {
|
||||||
|
//User setup a Preferred certificate, use the preferred certificate directly
|
||||||
|
pubKey = filepath.Join(m.CertStore, tlsBehavior.PreferredCertificate+".pem")
|
||||||
|
priKey = filepath.Join(m.CertStore, tlsBehavior.PreferredCertificate+".key")
|
||||||
|
} else {
|
||||||
|
if !tlsBehavior.DisableLegacyCertificateMatching &&
|
||||||
|
utils.FileExists(filepath.Join(m.CertStore, hostname+".pem")) &&
|
||||||
|
utils.FileExists(filepath.Join(m.CertStore, hostname+".key")) {
|
||||||
|
//Legacy filename matching, use the file names directly
|
||||||
|
//This is the legacy method of matching certificates, it will match the file names directly
|
||||||
|
//This is used for compatibility with Zoraxy v2 setups
|
||||||
|
pubKey = filepath.Join(m.CertStore, hostname+".pem")
|
||||||
|
priKey = filepath.Join(m.CertStore, hostname+".key")
|
||||||
|
} else if !tlsBehavior.DisableSNI &&
|
||||||
|
m.CertMatchExists(hostname) {
|
||||||
|
//SNI scan match, find the first matching certificate
|
||||||
|
pubKey, priKey = m.GetCertByX509CNHostname(hostname)
|
||||||
|
} else if tlsBehavior.EnableAutoHTTPS {
|
||||||
|
//Get certificate from CA, WIP
|
||||||
|
//TODO: Implement AutoHTTPS
|
||||||
|
} else {
|
||||||
|
//Fallback to legacy method of matching certificates
|
||||||
|
if m.DefaultCertExists() {
|
||||||
|
//Use default.pem and default.key
|
||||||
|
pubKey = filepath.Join(m.CertStore, "default.pem")
|
||||||
|
priKey = filepath.Join(m.CertStore, "default.key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pubKey, priKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Check if both the default cert public key and private key exists
|
// Check if both the default cert public key and private key exists
|
||||||
func (m *Manager) DefaultCertExists() bool {
|
func (m *Manager) DefaultCertExists() bool {
|
||||||
return utils.FileExists(filepath.Join(m.CertStore, "default.pem")) && utils.FileExists(filepath.Join(m.CertStore, "default.key"))
|
return utils.FileExists(filepath.Join(m.CertStore, "default.pem")) && utils.FileExists(filepath.Join(m.CertStore, "default.key"))
|
||||||
@ -220,7 +278,6 @@ func (m *Manager) RemoveCert(domain string) error {
|
|||||||
|
|
||||||
//Update the cert list
|
//Update the cert list
|
||||||
m.UpdateLoadedCertList()
|
m.UpdateLoadedCertList()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||||
"imuslab.com/zoraxy/mod/netutils"
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
"imuslab.com/zoraxy/mod/tlscert"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
"imuslab.com/zoraxy/mod/uptime"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
@ -334,7 +335,8 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
tags = filteredTags
|
tags = filteredTags
|
||||||
|
|
||||||
var proxyEndpointCreated *dynamicproxy.ProxyEndpoint
|
var proxyEndpointCreated *dynamicproxy.ProxyEndpoint
|
||||||
if eptype == "host" {
|
switch eptype {
|
||||||
|
case "host":
|
||||||
rootOrMatchingDomain, err := utils.PostPara(r, "rootname")
|
rootOrMatchingDomain, err := utils.PostPara(r, "rootname")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "hostname not defined")
|
utils.SendErrorResponse(w, "hostname not defined")
|
||||||
@ -415,7 +417,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint)
|
dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint)
|
||||||
proxyEndpointCreated = &thisProxyEndpoint
|
proxyEndpointCreated = &thisProxyEndpoint
|
||||||
} else if eptype == "root" {
|
case "root":
|
||||||
//Get the default site options and target
|
//Get the default site options and target
|
||||||
dsOptString, err := utils.PostPara(r, "defaultSiteOpt")
|
dsOptString, err := utils.PostPara(r, "defaultSiteOpt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -469,7 +471,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
proxyEndpointCreated = &rootRoutingEndpoint
|
proxyEndpointCreated = &rootRoutingEndpoint
|
||||||
} else {
|
default:
|
||||||
//Invalid eptype
|
//Invalid eptype
|
||||||
utils.SendErrorResponse(w, "invalid endpoint type")
|
utils.SendErrorResponse(w, "invalid endpoint type")
|
||||||
return
|
return
|
||||||
@ -677,6 +679,65 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReverseProxyHandleSetTlsConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
utils.SendErrorResponse(w, "Method not supported")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rootnameOrMatchingDomain, err := utils.PostPara(r, "ep")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Invalid ep given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig, err := utils.PostPara(r, "tlsConfig")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Invalid TLS config given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig = strings.TrimSpace(tlsConfig)
|
||||||
|
if tlsConfig == "" {
|
||||||
|
utils.SendErrorResponse(w, "TLS config cannot be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newTlsConfig := &tlscert.HostSpecificTlsBehavior{}
|
||||||
|
err = json.Unmarshal([]byte(tlsConfig), newTlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Invalid TLS config given: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target endpoint
|
||||||
|
ept, err := dynamicProxyRouter.LoadProxy(rootnameOrMatchingDomain)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ept.TlsOptions = newTlsConfig
|
||||||
|
|
||||||
|
//Prepare to replace the current routing rule
|
||||||
|
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(ept)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule)
|
||||||
|
|
||||||
|
//Save it to file
|
||||||
|
err = SaveReverseProxyConfig(ept)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Failed to save TLS config: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
func ReverseProxyHandleSetHostname(w http.ResponseWriter, r *http.Request) {
|
func ReverseProxyHandleSetHostname(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
utils.SendErrorResponse(w, "Method not supported")
|
utils.SendErrorResponse(w, "Method not supported")
|
||||||
@ -1015,6 +1076,7 @@ func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request)
|
|||||||
func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
|
func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
js, err := json.Marshal(dynamicProxyRouter)
|
js, err := json.Marshal(dynamicProxyRouter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("proxy-config", "Unable to marshal status data", err)
|
||||||
utils.SendErrorResponse(w, "Unable to marshal status data")
|
utils.SendErrorResponse(w, "Unable to marshal status data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -338,8 +338,37 @@
|
|||||||
<!-- TLS / SSL -->
|
<!-- TLS / SSL -->
|
||||||
<div class="rpconfig_content" rpcfg="ssl">
|
<div class="rpconfig_content" rpcfg="ssl">
|
||||||
<div class="ui segment">
|
<div class="ui segment">
|
||||||
<p>Work In Progress <br>
|
<p>The table below shows which certificate will be served by Zoraxy when a client request the following hostnames.</p>
|
||||||
Please use the outer-most menu TLS / SSL tab for now. </p>
|
<table class="ui celled small compact table Tls_resolve_list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Hostname</th>
|
||||||
|
<th>Resolve to Certificate</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- Rows will be dynamically populated -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
|
<input type="checkbox" class="Tls_EnableSNI">
|
||||||
|
<label>Enable SNI<br>
|
||||||
|
<small>Resolve Server Name Indication (SNI) and automatically select a certificate</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
|
<input type="checkbox" class="Tls_EnableLegacyCertificateMatching">
|
||||||
|
<label>Enable Legacy Certificate Matching<br>
|
||||||
|
<small>Use legacy filename / hostname matching for loading certificates</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
|
<input type="checkbox" class="Tls_EnableAutoHTTPS">
|
||||||
|
<label>Enable Auto HTTPS<br>
|
||||||
|
<small>Automatically request a certificate for the domain</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<button class="ui basic small button getCertificateBtn" style="margin-left: 0.4em; margin-top: 0.4em;"><i class="green lock icon"></i> Get Certificate</button>
|
<button class="ui basic small button getCertificateBtn" style="margin-left: 0.4em; margin-top: 0.4em;"><i class="green lock icon"></i> Get Certificate</button>
|
||||||
</div>
|
</div>
|
||||||
@ -711,6 +740,66 @@
|
|||||||
$("#httpProxyList").find(".editBtn").removeClass("disabled");
|
$("#httpProxyList").find(".editBtn").removeClass("disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveTlsConfigs(uuid){
|
||||||
|
let enableSNI = $("#httprpEditModal .Tls_EnableSNI")[0].checked;
|
||||||
|
let enableLegacyCertificateMatching = $("#httprpEditModal .Tls_EnableLegacyCertificateMatching")[0].checked;
|
||||||
|
let enableAutoHTTPS = $("#httprpEditModal .Tls_EnableAutoHTTPS")[0].checked;
|
||||||
|
let newTlsOption = {
|
||||||
|
"DisableSNI": !enableSNI,
|
||||||
|
"DisableLegacyCertificateMatching": !enableLegacyCertificateMatching,
|
||||||
|
"EnableAutoHTTPS": enableAutoHTTPS
|
||||||
|
}
|
||||||
|
$.cjax({
|
||||||
|
url: "/api/proxy/setTlsConfig",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
"ep": uuid,
|
||||||
|
"tlsConfig": JSON.stringify(newTlsOption)
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error !== undefined){
|
||||||
|
msgbox(data.error, false, 3000);
|
||||||
|
}else{
|
||||||
|
msgbox("TLS Config updated");
|
||||||
|
}
|
||||||
|
updateTlsResolveList(uuid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTlsResolveList(uuid){
|
||||||
|
let editor = $("#httprpEditModalWrapper");
|
||||||
|
//Update the TLS resolve list
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/cert/resolve?domain=" + uuid,
|
||||||
|
method: "GET",
|
||||||
|
success: function(data) {
|
||||||
|
// Populate the TLS resolve list
|
||||||
|
let resolveList = editor.find(".Tls_resolve_list tbody");
|
||||||
|
resolveList.empty(); // Clear existing entries
|
||||||
|
let primaryDomain = data.domain;
|
||||||
|
let aliasDomains = data.alias_domains || [];
|
||||||
|
let certMap = data.domain_key_pair;
|
||||||
|
|
||||||
|
// Add primary domain entry
|
||||||
|
resolveList.append(`
|
||||||
|
<tr>
|
||||||
|
<td>${primaryDomain}</td>
|
||||||
|
<td>${certMap[primaryDomain] || "Fallback Certificate"}</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
aliasDomains.forEach(alias => {
|
||||||
|
resolveList.append(`
|
||||||
|
<tr>
|
||||||
|
<td>${alias}</td>
|
||||||
|
<td>${certMap[alias] || "Fallback Certificate"}</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function saveProxyInlineEdit(uuid){
|
function saveProxyInlineEdit(uuid){
|
||||||
let editor = $("#httprpEditModal");
|
let editor = $("#httprpEditModal");
|
||||||
|
|
||||||
@ -1245,6 +1334,20 @@
|
|||||||
editor.find(".RateLimit").off("change").on("change", rateLimitChangeEvent);
|
editor.find(".RateLimit").off("change").on("change", rateLimitChangeEvent);
|
||||||
|
|
||||||
/* ------------ TLS ------------ */
|
/* ------------ TLS ------------ */
|
||||||
|
updateTlsResolveList(uuid);
|
||||||
|
editor.find(".Tls_EnableSNI").prop("checked", !subd.TlsOptions.DisableSNI);
|
||||||
|
editor.find(".Tls_EnableLegacyCertificateMatching").prop("checked", !subd.TlsOptions.DisableLegacyCertificateMatching);
|
||||||
|
editor.find(".Tls_EnableAutoHTTPS").prop("checked", !!subd.TlsOptions.EnableAutoHTTPS);
|
||||||
|
|
||||||
|
editor.find(".Tls_EnableSNI").off("change").on("change", function() {
|
||||||
|
saveTlsConfigs(uuid);
|
||||||
|
});
|
||||||
|
editor.find(".Tls_EnableLegacyCertificateMatching").off("change").on("change", function() {
|
||||||
|
saveTlsConfigs(uuid);
|
||||||
|
});
|
||||||
|
editor.find(".Tls_EnableAutoHTTPS").off("change").on("change", function() {
|
||||||
|
saveTlsConfigs(uuid);
|
||||||
|
});
|
||||||
|
|
||||||
/* ------------ Tags ------------ */
|
/* ------------ Tags ------------ */
|
||||||
(()=>{
|
(()=>{
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,7 @@
|
|||||||
<th>Target Address</th>
|
<th>Target Address</th>
|
||||||
<th>Mode</th>
|
<th>Mode</th>
|
||||||
<th>Timeout (s)</th>
|
<th>Timeout (s)</th>
|
||||||
|
<th>Enable Logging</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -73,6 +74,22 @@
|
|||||||
<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="ui toggle checkbox">
|
||||||
|
<input type="checkbox" tabindex="0" name="enableLogging" class="hidden">
|
||||||
|
<label>Enable Logging<br>
|
||||||
|
<small>Enable logging of connection status and errors for this rule</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 +212,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));
|
||||||
@ -207,6 +228,10 @@
|
|||||||
row.append($('<td>').text(config.ProxyTargetAddr));
|
row.append($('<td>').text(config.ProxyTargetAddr));
|
||||||
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(config.EnableLogging ?
|
||||||
|
'<i class="green check icon" title="Logging Enabled"></i>' :
|
||||||
|
'<i class="red times icon" title="Logging Disabled"></i>'
|
||||||
|
));
|
||||||
row.append($('<td>').html(`
|
row.append($('<td>').html(`
|
||||||
${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 circular basic mini icon button" title="Edit Config"><i class="edit icon"></i></button>
|
||||||
@ -252,6 +277,22 @@
|
|||||||
$(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 == "EnableLogging"){
|
||||||
|
let checkboxEle = $("#streamProxyForm input[name=enableLogging]").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 +342,8 @@
|
|||||||
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 ,
|
||||||
|
enableLogging: $("#streamProxyForm input[name=enableLogging]")[0].checked ,
|
||||||
timeout: parseInt($("#streamProxyForm input[name=timeout]").val().trim()),
|
timeout: parseInt($("#streamProxyForm input[name=timeout]").val().trim()),
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
|
Reference in New Issue
Block a user