Skip to content

Commit 83536a8

Browse files
authored
Merge pull request #192 from tobychui/v3.0.6
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
2 parents aa96d83 + 1183b0e commit 83536a8

39 files changed

+14892
-15892
lines changed

src/api.go

+7-8
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,13 @@ func initAPIs() {
141141
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
142142

143143
//TCP Proxy
144-
authRouter.HandleFunc("/api/tcpprox/config/add", tcpProxyManager.HandleAddProxyConfig)
145-
authRouter.HandleFunc("/api/tcpprox/config/edit", tcpProxyManager.HandleEditProxyConfigs)
146-
authRouter.HandleFunc("/api/tcpprox/config/list", tcpProxyManager.HandleListConfigs)
147-
authRouter.HandleFunc("/api/tcpprox/config/start", tcpProxyManager.HandleStartProxy)
148-
authRouter.HandleFunc("/api/tcpprox/config/stop", tcpProxyManager.HandleStopProxy)
149-
authRouter.HandleFunc("/api/tcpprox/config/delete", tcpProxyManager.HandleRemoveProxy)
150-
authRouter.HandleFunc("/api/tcpprox/config/status", tcpProxyManager.HandleGetProxyStatus)
151-
authRouter.HandleFunc("/api/tcpprox/config/validate", tcpProxyManager.HandleConfigValidate)
144+
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
145+
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
146+
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
147+
authRouter.HandleFunc("/api/streamprox/config/start", streamProxyManager.HandleStartProxy)
148+
authRouter.HandleFunc("/api/streamprox/config/stop", streamProxyManager.HandleStopProxy)
149+
authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy)
150+
authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus)
152151

153152
//mDNS APIs
154153
authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)

src/main.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
"imuslab.com/zoraxy/mod/sshprox"
2929
"imuslab.com/zoraxy/mod/statistic"
3030
"imuslab.com/zoraxy/mod/statistic/analytic"
31-
"imuslab.com/zoraxy/mod/tcpprox"
31+
"imuslab.com/zoraxy/mod/streamproxy"
3232
"imuslab.com/zoraxy/mod/tlscert"
3333
"imuslab.com/zoraxy/mod/uptime"
3434
"imuslab.com/zoraxy/mod/utils"
@@ -52,7 +52,7 @@ var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
5252

5353
var (
5454
name = "Zoraxy"
55-
version = "3.0.5"
55+
version = "3.0.6"
5656
nodeUUID = "generic"
5757
development = false //Set this to false to use embedded web fs
5858
bootTime = time.Now().Unix()
@@ -79,7 +79,7 @@ var (
7979
mdnsScanner *mdns.MDNSHost //mDNS discovery services
8080
ganManager *ganserv.NetworkManager //Global Area Network Manager
8181
webSshManager *sshprox.Manager //Web SSH connection service
82-
tcpProxyManager *tcpprox.Manager //TCP Proxy Manager
82+
streamProxyManager *streamproxy.Manager //Stream Proxy Manager for TCP / UDP forwarding
8383
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
8484
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
8585
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs

src/mod/dynamicproxy/Server.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@ import (
1414
Main server for dynamic proxy core
1515
1616
Routing Handler Priority (High to Low)
17-
- Blacklist
18-
- Whitelist
17+
- Special Routing Rule (e.g. acme)
1918
- Redirectable
2019
- Subdomain Routing
21-
- Vitrual Directory Routing
20+
- Access Router
21+
- Blacklist
22+
- Whitelist
23+
- Basic Auth
24+
- Vitrual Directory Proxy
25+
- Subdomain Proxy
26+
- Root router (default site router)
2227
*/
2328

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

37-
//Inject headers
38-
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)
39-
4042
/*
4143
Redirection Routing
4244
*/

src/mod/dynamicproxy/customHeader.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package dynamicproxy
2+
3+
/*
4+
CustomHeader.go
5+
6+
This script handle parsing and injecting custom headers
7+
into the dpcore routing logic
8+
*/
9+
10+
//SplitInboundOutboundHeaders split user defined headers into upstream and downstream headers
11+
//return upstream header and downstream header key-value pairs
12+
//if the header is expected to be deleted, the value will be set to empty string
13+
func (ept *ProxyEndpoint) SplitInboundOutboundHeaders() ([][]string, [][]string) {
14+
if len(ept.UserDefinedHeaders) == 0 {
15+
//Early return if there are no defined headers
16+
return [][]string{}, [][]string{}
17+
}
18+
19+
//Use pre-allocation for faster performance
20+
upstreamHeaders := make([][]string, len(ept.UserDefinedHeaders))
21+
downstreamHeaders := make([][]string, len(ept.UserDefinedHeaders))
22+
upstreamHeaderCounter := 0
23+
downstreamHeaderCounter := 0
24+
25+
//Sort the headers into upstream or downstream
26+
for _, customHeader := range ept.UserDefinedHeaders {
27+
thisHeaderSet := make([]string, 2)
28+
thisHeaderSet[0] = customHeader.Key
29+
thisHeaderSet[1] = customHeader.Value
30+
if customHeader.IsRemove {
31+
//Prevent invalid config
32+
thisHeaderSet[1] = ""
33+
}
34+
35+
//Assign to slice
36+
if customHeader.Direction == HeaderDirection_ZoraxyToUpstream {
37+
upstreamHeaders[upstreamHeaderCounter] = thisHeaderSet
38+
upstreamHeaderCounter++
39+
} else if customHeader.Direction == HeaderDirection_ZoraxyToDownstream {
40+
downstreamHeaders[downstreamHeaderCounter] = thisHeaderSet
41+
downstreamHeaderCounter++
42+
}
43+
}
44+
45+
return upstreamHeaders, downstreamHeaders
46+
}

src/mod/dynamicproxy/dpcore/dpcore.go

+23-79
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,14 @@ type ReverseProxy struct {
5757
}
5858

5959
type ResponseRewriteRuleSet struct {
60-
ProxyDomain string
61-
OriginalHost string
62-
UseTLS bool
63-
NoCache bool
64-
PathPrefix string //Vdir prefix for root, / will be rewrite to this
60+
ProxyDomain string
61+
OriginalHost string
62+
UseTLS bool
63+
NoCache bool
64+
PathPrefix string //Vdir prefix for root, / will be rewrite to this
65+
UpstreamHeaders [][]string
66+
DownstreamHeaders [][]string
67+
Version string //Version number of Zoraxy, use for X-Proxy-By
6568
}
6669

6770
type requestCanceler interface {
@@ -248,78 +251,6 @@ func (p *ReverseProxy) logf(format string, args ...interface{}) {
248251
}
249252
}
250253

251-
func removeHeaders(header http.Header, noCache bool) {
252-
// Remove hop-by-hop headers listed in the "Connection" header.
253-
if c := header.Get("Connection"); c != "" {
254-
for _, f := range strings.Split(c, ",") {
255-
if f = strings.TrimSpace(f); f != "" {
256-
header.Del(f)
257-
}
258-
}
259-
}
260-
261-
// Remove hop-by-hop headers
262-
for _, h := range hopHeaders {
263-
if header.Get(h) != "" {
264-
header.Del(h)
265-
}
266-
}
267-
268-
//Restore the Upgrade header if any
269-
if header.Get("Zr-Origin-Upgrade") != "" {
270-
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
271-
header.Del("Zr-Origin-Upgrade")
272-
}
273-
274-
//Disable cache if nocache is set
275-
if noCache {
276-
header.Del("Cache-Control")
277-
header.Set("Cache-Control", "no-store")
278-
}
279-
280-
//Hide Go-HTTP-Client UA if the client didnt sent us one
281-
if _, ok := header["User-Agent"]; !ok {
282-
// If the outbound request doesn't have a User-Agent header set,
283-
// don't send the default Go HTTP client User-Agent.
284-
header.Set("User-Agent", "")
285-
}
286-
287-
}
288-
289-
func addXForwardedForHeader(req *http.Request) {
290-
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
291-
// If we aren't the first proxy retain prior
292-
// X-Forwarded-For information as a comma+space
293-
// separated list and fold multiple headers into one.
294-
if prior, ok := req.Header["X-Forwarded-For"]; ok {
295-
clientIP = strings.Join(prior, ", ") + ", " + clientIP
296-
}
297-
req.Header.Set("X-Forwarded-For", clientIP)
298-
if req.TLS != nil {
299-
req.Header.Set("X-Forwarded-Proto", "https")
300-
} else {
301-
req.Header.Set("X-Forwarded-Proto", "http")
302-
}
303-
304-
if req.Header.Get("X-Real-Ip") == "" {
305-
//Check if CF-Connecting-IP header exists
306-
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
307-
if CF_Connecting_IP != "" {
308-
//Use CF Connecting IP
309-
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
310-
} else {
311-
// Not exists. Fill it in with first entry in X-Forwarded-For
312-
ips := strings.Split(clientIP, ",")
313-
if len(ips) > 0 {
314-
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
315-
}
316-
}
317-
318-
}
319-
320-
}
321-
}
322-
323254
func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) error {
324255
transport := p.Transport
325256

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

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

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

298+
// Add user defined headers (to upstream)
299+
injectUserDefinedHeaders(outreq.Header, rrr.UpstreamHeaders)
300+
301+
// Rewrite outbound UA, must be after user headers
302+
rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version)
303+
367304
res, err := transport.RoundTrip(outreq)
368305
if err != nil {
369306
if p.Verbal {
@@ -394,13 +331,17 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
394331
}
395332
}
396333

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

403-
//Custom header rewriter functions
341+
//Add debug X-Proxy-By tracker
342+
res.Header.Set("x-proxy-by", "zoraxy/"+rrr.Version)
343+
344+
//Custom Location header rewriter functions
404345
if res.Header.Get("Location") != "" {
405346
locationRewrite := res.Header.Get("Location")
406347
originLocation := res.Header.Get("Location")
@@ -426,6 +367,9 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
426367
res.Header.Set("Location", locationRewrite)
427368
}
428369

370+
// Add user defined headers (to downstream)
371+
injectUserDefinedHeaders(res.Header, rrr.DownstreamHeaders)
372+
429373
// Copy header from response to client.
430374
copyHeader(rw.Header(), res.Header)
431375

src/mod/dynamicproxy/dpcore/header.go

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package dpcore
2+
3+
import (
4+
"net"
5+
"net/http"
6+
"strings"
7+
)
8+
9+
/*
10+
Header.go
11+
12+
This script handles headers rewrite and remove
13+
in dpcore.
14+
15+
Added in Zoraxy v3.0.6 by tobychui
16+
*/
17+
18+
// removeHeaders Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
19+
func removeHeaders(header http.Header, noCache bool) {
20+
// Remove hop-by-hop headers listed in the "Connection" header.
21+
if c := header.Get("Connection"); c != "" {
22+
for _, f := range strings.Split(c, ",") {
23+
if f = strings.TrimSpace(f); f != "" {
24+
header.Del(f)
25+
}
26+
}
27+
}
28+
29+
// Remove hop-by-hop headers
30+
for _, h := range hopHeaders {
31+
if header.Get(h) != "" {
32+
header.Del(h)
33+
}
34+
}
35+
36+
//Restore the Upgrade header if any
37+
if header.Get("Zr-Origin-Upgrade") != "" {
38+
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
39+
header.Del("Zr-Origin-Upgrade")
40+
}
41+
42+
//Disable cache if nocache is set
43+
if noCache {
44+
header.Del("Cache-Control")
45+
header.Set("Cache-Control", "no-store")
46+
}
47+
48+
}
49+
50+
// rewriteUserAgent rewrite the user agent based on incoming request
51+
func rewriteUserAgent(header http.Header, UA string) {
52+
//Hide Go-HTTP-Client UA if the client didnt sent us one
53+
if header.Get("User-Agent") == "" {
54+
// If the outbound request doesn't have a User-Agent header set,
55+
// don't send the default Go HTTP client User-Agent
56+
header.Del("User-Agent")
57+
header.Set("User-Agent", UA)
58+
}
59+
}
60+
61+
// Add X-Forwarded-For Header and rewrite X-Real-Ip according to sniffing logics
62+
func addXForwardedForHeader(req *http.Request) {
63+
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
64+
// If we aren't the first proxy retain prior
65+
// X-Forwarded-For information as a comma+space
66+
// separated list and fold multiple headers into one.
67+
if prior, ok := req.Header["X-Forwarded-For"]; ok {
68+
clientIP = strings.Join(prior, ", ") + ", " + clientIP
69+
}
70+
req.Header.Set("X-Forwarded-For", clientIP)
71+
if req.TLS != nil {
72+
req.Header.Set("X-Forwarded-Proto", "https")
73+
} else {
74+
req.Header.Set("X-Forwarded-Proto", "http")
75+
}
76+
77+
if req.Header.Get("X-Real-Ip") == "" {
78+
//Check if CF-Connecting-IP header exists
79+
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
80+
Fastly_Client_IP := req.Header.Get("Fastly-Client-IP")
81+
if CF_Connecting_IP != "" {
82+
//Use CF Connecting IP
83+
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
84+
} else if Fastly_Client_IP != "" {
85+
//Use Fastly Client IP
86+
req.Header.Set("X-Real-Ip", Fastly_Client_IP)
87+
} else {
88+
// Not exists. Fill it in with first entry in X-Forwarded-For
89+
ips := strings.Split(clientIP, ",")
90+
if len(ips) > 0 {
91+
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
92+
}
93+
}
94+
95+
}
96+
97+
}
98+
}
99+
100+
// injectUserDefinedHeaders inject the user headers from slice
101+
// if a value is empty string, the key will be removed from header.
102+
// if a key is empty string, the function will return immediately
103+
func injectUserDefinedHeaders(header http.Header, userHeaders [][]string) {
104+
for _, userHeader := range userHeaders {
105+
if len(userHeader) == 0 {
106+
//End of header slice
107+
return
108+
}
109+
headerKey := userHeader[0]
110+
headerValue := userHeader[1]
111+
if headerValue == "" {
112+
//Remove header from head
113+
header.Del(headerKey)
114+
continue
115+
}
116+
117+
//Default: Set header value
118+
header.Del(headerKey) //Remove header if it already exists
119+
header.Set(headerKey, headerValue)
120+
}
121+
}

0 commit comments

Comments
 (0)