Skip to content

Commit

Permalink
Fixed #106
Browse files Browse the repository at this point in the history
- Added experimental proxmox fixes
- Fixed upstream error resp code not logging bug
  • Loading branch information
tobychui committed Jul 27, 2024
1 parent c1e16d5 commit ca37bfb
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 50 deletions.
21 changes: 21 additions & 0 deletions src/mod/dynamicproxy/domainsniff/proxmox.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package domainsniff

import "net/http"

/*
Promox API sniffer
This handler sniff proxmox API endpoint and
adjust the request accordingly to fix shits
in the proxmox API server
*/

func IsProxmox(r *http.Request) bool {
// Check if any of the cookies is named PVEAuthCookie
for _, cookie := range r.Cookies() {
if cookie.Name == "PVEAuthCookie" {
return true
}
}
return false
}
40 changes: 21 additions & 19 deletions src/mod/dynamicproxy/dpcore/dpcore.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"
"time"

"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
)

Expand Down Expand Up @@ -50,7 +51,6 @@ type ReverseProxy struct {
ModifyResponse func(*http.Response) error

//Prepender is an optional prepend text for URL rewrite
//
Prepender string

Verbal bool
Expand Down Expand Up @@ -258,7 +258,7 @@ func (p *ReverseProxy) logf(format string, args ...interface{}) {
}
}

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

outreq := new(http.Request)
Expand Down Expand Up @@ -313,14 +313,19 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
// Rewrite outbound UA, must be after user headers
rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version)

//Fix proxmox transfer encoding bug if detected Proxmox Cookie
if domainsniff.IsProxmox(req) {
outreq.TransferEncoding = []string{"identity"}
}

res, err := transport.RoundTrip(outreq)
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}

//rw.WriteHeader(http.StatusBadGateway)
return err
return http.StatusBadGateway, err
}

// Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers.
Expand All @@ -341,7 +346,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
}

//rw.WriteHeader(http.StatusBadGateway)
return err
return http.StatusBadGateway, err
}
}

Expand Down Expand Up @@ -388,7 +393,6 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
copyHeader(rw.Header(), res.Header)

// inject permission policy headers
//TODO: Load permission policy from rrr
permissionpolicy.InjectPermissionPolicyHeader(rw, nil)

// The "Trailer" header isn't included in the Transport's response, Build it up from Trailer.
Expand Down Expand Up @@ -418,22 +422,22 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
res.Body.Close()
copyHeader(rw.Header(), res.Trailer)

return nil
return res.StatusCode, nil
}

func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) error {
func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) (int, error) {
hij, ok := rw.(http.Hijacker)
if !ok {
p.logf("http server does not support hijacker")
return errors.New("http server does not support hijacker")
return http.StatusNotImplemented, errors.New("http server does not support hijacker")
}

clientConn, _, err := hij.Hijack()
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
return err
return http.StatusInternalServerError, err
}

proxyConn, err := net.Dial("tcp", req.URL.Host)
Expand All @@ -442,7 +446,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
p.logf("http: proxy error: %v", err)
}

return err
return http.StatusInternalServerError, err
}

// The returned net.Conn may have read or write deadlines
Expand All @@ -461,7 +465,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
return err
return http.StatusGatewayTimeout, err
}

err = proxyConn.SetDeadline(deadline)
Expand All @@ -470,7 +474,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
p.logf("http: proxy error: %v", err)
}

return err
return http.StatusGatewayTimeout, err
}

_, err = clientConn.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
Expand All @@ -479,7 +483,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
p.logf("http: proxy error: %v", err)
}

return err
return http.StatusInternalServerError, err
}

go func() {
Expand All @@ -492,15 +496,13 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
proxyConn.Close()
clientConn.Close()

return nil
return http.StatusOK, nil
}

func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) error {
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request, rrr *ResponseRewriteRuleSet) (int, error) {
if req.Method == "CONNECT" {
err := p.ProxyHTTPS(rw, req)
return err
return p.ProxyHTTPS(rw, req)
} else {
err := p.ProxyHTTP(rw, req, rrr)
return err
return p.ProxyHTTP(rw, req, rrr)
}
}
63 changes: 63 additions & 0 deletions src/mod/dynamicproxy/dpcore/utils.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package dpcore

import (
"bytes"
"io"
"net"
"net/http"
"net/url"
"strings"
)
Expand Down Expand Up @@ -92,3 +95,63 @@ func isExternalDomainName(hostname string) bool {

return true
}

// DeepCopyRequest returns a deep copy of the given http.Request.
func DeepCopyRequest(req *http.Request) (*http.Request, error) {
// Copy the URL
urlCopy := *req.URL

// Copy the headers
headersCopy := make(http.Header, len(req.Header))
for k, vv := range req.Header {
vvCopy := make([]string, len(vv))
copy(vvCopy, vv)
headersCopy[k] = vvCopy
}

// Copy the cookies
cookiesCopy := make([]*http.Cookie, len(req.Cookies()))
for i, cookie := range req.Cookies() {
cookieCopy := *cookie
cookiesCopy[i] = &cookieCopy
}

// Copy the body, if present
var bodyCopy io.ReadCloser
if req.Body != nil {
var buf bytes.Buffer
if _, err := buf.ReadFrom(req.Body); err != nil {
return nil, err
}
// Reset the request body so it can be read again
if err := req.Body.Close(); err != nil {
return nil, err
}
req.Body = io.NopCloser(&buf)
bodyCopy = io.NopCloser(bytes.NewReader(buf.Bytes()))
}

// Create the new request
reqCopy := &http.Request{
Method: req.Method,
URL: &urlCopy,
Proto: req.Proto,
ProtoMajor: req.ProtoMajor,
ProtoMinor: req.ProtoMinor,
Header: headersCopy,
Body: bodyCopy,
ContentLength: req.ContentLength,
TransferEncoding: append([]string(nil), req.TransferEncoding...),
Close: req.Close,
Host: req.Host,
Form: req.Form,
PostForm: req.PostForm,
MultipartForm: req.MultipartForm,
Trailer: req.Trailer,
RemoteAddr: req.RemoteAddr,
TLS: req.TLS,
// Cancel and Context are not copied as it might cause issues
}

return reqCopy, nil
}
4 changes: 2 additions & 2 deletions src/mod/dynamicproxy/loadbalance/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ func (u *Upstream) Clone() *Upstream {
return &newUpstream
}

// ServeHTTP uses this upstream proxy router to route the current request
func (u *Upstream) ServeHTTP(w http.ResponseWriter, r *http.Request, rrr *dpcore.ResponseRewriteRuleSet) error {
// ServeHTTP uses this upstream proxy router to route the current request, return the status code and error if any
func (u *Upstream) ServeHTTP(w http.ResponseWriter, r *http.Request, rrr *dpcore.ResponseRewriteRuleSet) (int, error) {
//Auto rewrite to upstream origin if not set
if rrr.ProxyDomain == "" {
rrr.ProxyDomain = u.OriginIpOrDomain
Expand Down
60 changes: 33 additions & 27 deletions src/mod/dynamicproxy/proxyRequestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,37 +112,43 @@ func (router *Router) rewriteURL(rooturl string, requestURL string) string {
func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
r.Header.Set("X-Forwarded-Host", r.Host)
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)

/* Load balancing */
selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession)
if err != nil {
http.ServeFile(w, r, "./web/rperror.html")
log.Println(err.Error())
h.Parent.logRequest(r, false, 521, "subdomain-http", r.URL.Hostname())
return
}
requestURL := r.URL.String()
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
r.Header.Set("Zr-Origin-Upgrade", "websocket")
wsRedirectionEndpoint := selectedUpstream.OriginIpOrDomain
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
//Append / to the end of the redirection endpoint if not exists
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
}
if len(requestURL) > 0 && requestURL[:1] == "/" {
//Remove starting / from request URL if exists
requestURL = requestURL[1:]
}
u, _ := url.Parse("ws://" + wsRedirectionEndpoint + requestURL)
if selectedUpstream.RequireTLS {
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)

/* WebSocket automatic proxy */
if !target.DisableAutoWebSockeyProxy {
requestURL := r.URL.String()
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
r.Header.Set("Zr-Origin-Upgrade", "websocket")
wsRedirectionEndpoint := selectedUpstream.OriginIpOrDomain
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
//Append / to the end of the redirection endpoint if not exists
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
}
if len(requestURL) > 0 && requestURL[:1] == "/" {
//Remove starting / from request URL if exists
requestURL = requestURL[1:]
}
u, _ := url.Parse("ws://" + wsRedirectionEndpoint + requestURL)
if selectedUpstream.RequireTLS {
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
}
h.Parent.logRequest(r, true, 101, "host-websocket", selectedUpstream.OriginIpOrDomain)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: selectedUpstream.SkipCertValidations,
SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck,
})
wspHandler.ServeHTTP(w, r)
return
}
h.Parent.logRequest(r, true, 101, "host-websocket", selectedUpstream.OriginIpOrDomain)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: selectedUpstream.SkipCertValidations,
SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck,
})
wspHandler.ServeHTTP(w, r)
return
}

originalHostHeader := r.Host
Expand All @@ -156,7 +162,7 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
//Build downstream and upstream header rules
upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders()

err = selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: selectedUpstream.OriginIpOrDomain,
OriginalHost: originalHostHeader,
UseTLS: selectedUpstream.RequireTLS,
Expand All @@ -182,7 +188,7 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
}
}

h.Parent.logRequest(r, true, 200, "host-http", r.URL.Hostname())
h.Parent.logRequest(r, true, statusCode, "host-http", r.URL.Hostname())
}

// Handle vdir type request
Expand Down Expand Up @@ -224,7 +230,7 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
//Build downstream and upstream header rules
upstreamHeaders, downstreamHeaders := target.parent.SplitInboundOutboundHeaders()

err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: target.Domain,
OriginalHost: originalHostHeader,
UseTLS: target.RequireTLS,
Expand All @@ -247,7 +253,7 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
h.Parent.logRequest(r, false, 521, "vdir-http", target.Domain)
}
}
h.Parent.logRequest(r, true, 200, "vdir-http", target.Domain)
h.Parent.logRequest(r, true, statusCode, "vdir-http", target.Domain)

}

Expand Down
3 changes: 2 additions & 1 deletion src/mod/dynamicproxy/typedef.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ type ProxyEndpoint struct {
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header
DisableHopByHopHeaderRemoval bool //TODO: Do not remove hop-by-hop headers
DisableHopByHopHeaderRemoval bool //Do not remove hop-by-hop headers
DisableAutoWebSockeyProxy bool //Disable auto sniffing logic for websocket upgrade

//Authentication
RequireBasicAuth bool //Set to true to request basic auth before proxy
Expand Down
3 changes: 2 additions & 1 deletion src/reverseproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -1349,7 +1349,8 @@ func HandleHopByHop(w http.ResponseWriter, r *http.Request) {
//we need to clone and respawn this proxy endpoint
newProxyEndpoint := targetProxyEndpoint.Clone()
//Storage file use false as default, so disable removal = not enable remover
targetProxyEndpoint.DisableHopByHopHeaderRemoval = !enableHopByHopRemover
newProxyEndpoint.DisableHopByHopHeaderRemoval = !enableHopByHopRemover

//Save proxy endpoint
err = SaveReverseProxyConfig(newProxyEndpoint)
if err != nil {
Expand Down

0 comments on commit ca37bfb

Please sign in to comment.