Skip to content

Commit

Permalink
Merge pull request #192 from tobychui/v3.0.6
Browse files Browse the repository at this point in the history
V3.0.6 Update

- Added fastly_client_ip to X-Real-IP auto rewrite
- Added atomic accumulator to TCP proxy
- Added white logo for future dark theme
- Added multi selection for white / blacklist #176 
- Moved custom header rewrite to dpcore 
- Restructure dpcore header rewrite sequence
- Added advance custom header settings (zoraxy to upstream and zoraxy to downstream mode)
- Added header remove feature
- Removed password requirement for SMTP #162 #80 
- Restructured TCP proxy into Stream Proxy (Support both TCP and UDP) #147 
- Added stream proxy auto start #169 
- Optimized UX for reminding user to click Apply after port change
- Added version number to footer #160
  • Loading branch information
tobychui authored Jun 10, 2024
2 parents aa96d83 + 1183b0e commit 83536a8
Show file tree
Hide file tree
Showing 39 changed files with 14,892 additions and 15,892 deletions.
15 changes: 7 additions & 8 deletions src/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,13 @@ func initAPIs() {
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)

//TCP Proxy
authRouter.HandleFunc("/api/tcpprox/config/add", tcpProxyManager.HandleAddProxyConfig)
authRouter.HandleFunc("/api/tcpprox/config/edit", tcpProxyManager.HandleEditProxyConfigs)
authRouter.HandleFunc("/api/tcpprox/config/list", tcpProxyManager.HandleListConfigs)
authRouter.HandleFunc("/api/tcpprox/config/start", tcpProxyManager.HandleStartProxy)
authRouter.HandleFunc("/api/tcpprox/config/stop", tcpProxyManager.HandleStopProxy)
authRouter.HandleFunc("/api/tcpprox/config/delete", tcpProxyManager.HandleRemoveProxy)
authRouter.HandleFunc("/api/tcpprox/config/status", tcpProxyManager.HandleGetProxyStatus)
authRouter.HandleFunc("/api/tcpprox/config/validate", tcpProxyManager.HandleConfigValidate)
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
authRouter.HandleFunc("/api/streamprox/config/start", streamProxyManager.HandleStartProxy)
authRouter.HandleFunc("/api/streamprox/config/stop", streamProxyManager.HandleStopProxy)
authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy)
authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus)

//mDNS APIs
authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)
Expand Down
6 changes: 3 additions & 3 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"imuslab.com/zoraxy/mod/sshprox"
"imuslab.com/zoraxy/mod/statistic"
"imuslab.com/zoraxy/mod/statistic/analytic"
"imuslab.com/zoraxy/mod/tcpprox"
"imuslab.com/zoraxy/mod/streamproxy"
"imuslab.com/zoraxy/mod/tlscert"
"imuslab.com/zoraxy/mod/uptime"
"imuslab.com/zoraxy/mod/utils"
Expand All @@ -52,7 +52,7 @@ var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")

var (
name = "Zoraxy"
version = "3.0.5"
version = "3.0.6"
nodeUUID = "generic"
development = false //Set this to false to use embedded web fs
bootTime = time.Now().Unix()
Expand All @@ -79,7 +79,7 @@ var (
mdnsScanner *mdns.MDNSHost //mDNS discovery services
ganManager *ganserv.NetworkManager //Global Area Network Manager
webSshManager *sshprox.Manager //Web SSH connection service
tcpProxyManager *tcpprox.Manager //TCP Proxy Manager
streamProxyManager *streamproxy.Manager //Stream Proxy Manager for TCP / UDP forwarding
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
Expand Down
14 changes: 8 additions & 6 deletions src/mod/dynamicproxy/Server.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ import (
Main server for dynamic proxy core
Routing Handler Priority (High to Low)
- Blacklist
- Whitelist
- Special Routing Rule (e.g. acme)
- Redirectable
- Subdomain Routing
- Vitrual Directory Routing
- Access Router
- Blacklist
- Whitelist
- Basic Auth
- Vitrual Directory Proxy
- Subdomain Proxy
- Root router (default site router)
*/

func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Expand All @@ -34,9 +39,6 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

//Inject headers
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)

/*
Redirection Routing
*/
Expand Down
46 changes: 46 additions & 0 deletions src/mod/dynamicproxy/customHeader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package dynamicproxy

/*
CustomHeader.go
This script handle parsing and injecting custom headers
into the dpcore routing logic
*/

//SplitInboundOutboundHeaders split user defined headers into upstream and downstream headers
//return upstream header and downstream header key-value pairs
//if the header is expected to be deleted, the value will be set to empty string
func (ept *ProxyEndpoint) SplitInboundOutboundHeaders() ([][]string, [][]string) {
if len(ept.UserDefinedHeaders) == 0 {
//Early return if there are no defined headers
return [][]string{}, [][]string{}
}

//Use pre-allocation for faster performance
upstreamHeaders := make([][]string, len(ept.UserDefinedHeaders))
downstreamHeaders := make([][]string, len(ept.UserDefinedHeaders))
upstreamHeaderCounter := 0
downstreamHeaderCounter := 0

//Sort the headers into upstream or downstream
for _, customHeader := range ept.UserDefinedHeaders {
thisHeaderSet := make([]string, 2)
thisHeaderSet[0] = customHeader.Key
thisHeaderSet[1] = customHeader.Value
if customHeader.IsRemove {
//Prevent invalid config
thisHeaderSet[1] = ""
}

//Assign to slice
if customHeader.Direction == HeaderDirection_ZoraxyToUpstream {
upstreamHeaders[upstreamHeaderCounter] = thisHeaderSet
upstreamHeaderCounter++
} else if customHeader.Direction == HeaderDirection_ZoraxyToDownstream {
downstreamHeaders[downstreamHeaderCounter] = thisHeaderSet
downstreamHeaderCounter++
}
}

return upstreamHeaders, downstreamHeaders
}
102 changes: 23 additions & 79 deletions src/mod/dynamicproxy/dpcore/dpcore.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,14 @@ type ReverseProxy struct {
}

type ResponseRewriteRuleSet struct {
ProxyDomain string
OriginalHost string
UseTLS bool
NoCache bool
PathPrefix string //Vdir prefix for root, / will be rewrite to this
ProxyDomain string
OriginalHost string
UseTLS bool
NoCache bool
PathPrefix string //Vdir prefix for root, / will be rewrite to this
UpstreamHeaders [][]string
DownstreamHeaders [][]string
Version string //Version number of Zoraxy, use for X-Proxy-By
}

type requestCanceler interface {
Expand Down Expand Up @@ -248,78 +251,6 @@ func (p *ReverseProxy) logf(format string, args ...interface{}) {
}
}

func removeHeaders(header http.Header, noCache bool) {
// Remove hop-by-hop headers listed in the "Connection" header.
if c := header.Get("Connection"); c != "" {
for _, f := range strings.Split(c, ",") {
if f = strings.TrimSpace(f); f != "" {
header.Del(f)
}
}
}

// Remove hop-by-hop headers
for _, h := range hopHeaders {
if header.Get(h) != "" {
header.Del(h)
}
}

//Restore the Upgrade header if any
if header.Get("Zr-Origin-Upgrade") != "" {
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
header.Del("Zr-Origin-Upgrade")
}

//Disable cache if nocache is set
if noCache {
header.Del("Cache-Control")
header.Set("Cache-Control", "no-store")
}

//Hide Go-HTTP-Client UA if the client didnt sent us one
if _, ok := header["User-Agent"]; !ok {
// If the outbound request doesn't have a User-Agent header set,
// don't send the default Go HTTP client User-Agent.
header.Set("User-Agent", "")
}

}

func addXForwardedForHeader(req *http.Request) {
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := req.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
req.Header.Set("X-Forwarded-For", clientIP)
if req.TLS != nil {
req.Header.Set("X-Forwarded-Proto", "https")
} else {
req.Header.Set("X-Forwarded-Proto", "http")
}

if req.Header.Get("X-Real-Ip") == "" {
//Check if CF-Connecting-IP header exists
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
if CF_Connecting_IP != "" {
//Use CF Connecting IP
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
} else {
// Not exists. Fill it in with first entry in X-Forwarded-For
ips := strings.Split(clientIP, ",")
if len(ips) > 0 {
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
}
}

}

}
}

func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) error {
transport := p.Transport

Expand Down Expand Up @@ -358,12 +289,18 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
outreq.Header = make(http.Header)
copyHeader(outreq.Header, req.Header)

// Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
// Remove hop-by-hop headers.
removeHeaders(outreq.Header, rrr.NoCache)

// Add X-Forwarded-For Header.
addXForwardedForHeader(outreq)

// Add user defined headers (to upstream)
injectUserDefinedHeaders(outreq.Header, rrr.UpstreamHeaders)

// Rewrite outbound UA, must be after user headers
rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version)

res, err := transport.RoundTrip(outreq)
if err != nil {
if p.Verbal {
Expand Down Expand Up @@ -394,13 +331,17 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
}
}

//TODO: Figure out a way to proxy for proxmox
//if res.StatusCode == 501 || res.StatusCode == 500 {
// fmt.Println(outreq.Proto, outreq.RemoteAddr, outreq.RequestURI)
// fmt.Println(">>>", outreq.Method, res.Header, res.ContentLength, res.StatusCode)
// fmt.Println(outreq.Header, req.Host)
//}

//Custom header rewriter functions
//Add debug X-Proxy-By tracker
res.Header.Set("x-proxy-by", "zoraxy/"+rrr.Version)

//Custom Location header rewriter functions
if res.Header.Get("Location") != "" {
locationRewrite := res.Header.Get("Location")
originLocation := res.Header.Get("Location")
Expand All @@ -426,6 +367,9 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
res.Header.Set("Location", locationRewrite)
}

// Add user defined headers (to downstream)
injectUserDefinedHeaders(res.Header, rrr.DownstreamHeaders)

// Copy header from response to client.
copyHeader(rw.Header(), res.Header)

Expand Down
121 changes: 121 additions & 0 deletions src/mod/dynamicproxy/dpcore/header.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package dpcore

import (
"net"
"net/http"
"strings"
)

/*
Header.go
This script handles headers rewrite and remove
in dpcore.
Added in Zoraxy v3.0.6 by tobychui
*/

// removeHeaders Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
func removeHeaders(header http.Header, noCache bool) {
// Remove hop-by-hop headers listed in the "Connection" header.
if c := header.Get("Connection"); c != "" {
for _, f := range strings.Split(c, ",") {
if f = strings.TrimSpace(f); f != "" {
header.Del(f)
}
}
}

// Remove hop-by-hop headers
for _, h := range hopHeaders {
if header.Get(h) != "" {
header.Del(h)
}
}

//Restore the Upgrade header if any
if header.Get("Zr-Origin-Upgrade") != "" {
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
header.Del("Zr-Origin-Upgrade")
}

//Disable cache if nocache is set
if noCache {
header.Del("Cache-Control")
header.Set("Cache-Control", "no-store")
}

}

// rewriteUserAgent rewrite the user agent based on incoming request
func rewriteUserAgent(header http.Header, UA string) {
//Hide Go-HTTP-Client UA if the client didnt sent us one
if header.Get("User-Agent") == "" {
// If the outbound request doesn't have a User-Agent header set,
// don't send the default Go HTTP client User-Agent
header.Del("User-Agent")
header.Set("User-Agent", UA)
}
}

// Add X-Forwarded-For Header and rewrite X-Real-Ip according to sniffing logics
func addXForwardedForHeader(req *http.Request) {
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := req.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
req.Header.Set("X-Forwarded-For", clientIP)
if req.TLS != nil {
req.Header.Set("X-Forwarded-Proto", "https")
} else {
req.Header.Set("X-Forwarded-Proto", "http")
}

if req.Header.Get("X-Real-Ip") == "" {
//Check if CF-Connecting-IP header exists
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
Fastly_Client_IP := req.Header.Get("Fastly-Client-IP")
if CF_Connecting_IP != "" {
//Use CF Connecting IP
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
} else if Fastly_Client_IP != "" {
//Use Fastly Client IP
req.Header.Set("X-Real-Ip", Fastly_Client_IP)
} else {
// Not exists. Fill it in with first entry in X-Forwarded-For
ips := strings.Split(clientIP, ",")
if len(ips) > 0 {
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
}
}

}

}
}

// injectUserDefinedHeaders inject the user headers from slice
// if a value is empty string, the key will be removed from header.
// if a key is empty string, the function will return immediately
func injectUserDefinedHeaders(header http.Header, userHeaders [][]string) {
for _, userHeader := range userHeaders {
if len(userHeader) == 0 {
//End of header slice
return
}
headerKey := userHeader[0]
headerValue := userHeader[1]
if headerValue == "" {
//Remove header from head
header.Del(headerKey)
continue
}

//Default: Set header value
header.Del(headerKey) //Remove header if it already exists
header.Set(headerKey, headerValue)
}
}
Loading

0 comments on commit 83536a8

Please sign in to comment.