- Added experimental proxmox fixes
- Fixed upstream error resp code not logging bug
This commit is contained in:
Toby Chui 2024-07-27 17:33:41 +08:00
parent c1e16d55ab
commit ca37bfbfa6
7 changed files with 144 additions and 50 deletions

View File

@ -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
}

View File

@ -11,6 +11,7 @@ import (
"strings" "strings"
"time" "time"
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy" "imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
) )
@ -50,7 +51,6 @@ type ReverseProxy struct {
ModifyResponse func(*http.Response) error ModifyResponse func(*http.Response) error
//Prepender is an optional prepend text for URL rewrite //Prepender is an optional prepend text for URL rewrite
//
Prepender string Prepender string
Verbal bool Verbal bool
@ -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 transport := p.Transport
outreq := new(http.Request) outreq := new(http.Request)
@ -313,6 +313,11 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
// Rewrite outbound UA, must be after user headers // Rewrite outbound UA, must be after user headers
rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version) 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) res, err := transport.RoundTrip(outreq)
if err != nil { if err != nil {
if p.Verbal { if p.Verbal {
@ -320,7 +325,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
} }
//rw.WriteHeader(http.StatusBadGateway) //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. // Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers.
@ -341,7 +346,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
} }
//rw.WriteHeader(http.StatusBadGateway) //rw.WriteHeader(http.StatusBadGateway)
return err return http.StatusBadGateway, err
} }
} }
@ -388,7 +393,6 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
copyHeader(rw.Header(), res.Header) copyHeader(rw.Header(), res.Header)
// inject permission policy headers // inject permission policy headers
//TODO: Load permission policy from rrr
permissionpolicy.InjectPermissionPolicyHeader(rw, nil) permissionpolicy.InjectPermissionPolicyHeader(rw, nil)
// The "Trailer" header isn't included in the Transport's response, Build it up from Trailer. // The "Trailer" header isn't included in the Transport's response, Build it up from Trailer.
@ -418,14 +422,14 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
res.Body.Close() res.Body.Close()
copyHeader(rw.Header(), res.Trailer) 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) hij, ok := rw.(http.Hijacker)
if !ok { if !ok {
p.logf("http server does not support hijacker") 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() clientConn, _, err := hij.Hijack()
@ -433,7 +437,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
if p.Verbal { if p.Verbal {
p.logf("http: proxy error: %v", err) p.logf("http: proxy error: %v", err)
} }
return err return http.StatusInternalServerError, err
} }
proxyConn, err := net.Dial("tcp", req.URL.Host) proxyConn, err := net.Dial("tcp", req.URL.Host)
@ -442,7 +446,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
p.logf("http: proxy error: %v", err) p.logf("http: proxy error: %v", err)
} }
return err return http.StatusInternalServerError, err
} }
// The returned net.Conn may have read or write deadlines // The returned net.Conn may have read or write deadlines
@ -461,7 +465,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
if p.Verbal { if p.Verbal {
p.logf("http: proxy error: %v", err) p.logf("http: proxy error: %v", err)
} }
return err return http.StatusGatewayTimeout, err
} }
err = proxyConn.SetDeadline(deadline) err = proxyConn.SetDeadline(deadline)
@ -470,7 +474,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
p.logf("http: proxy error: %v", 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")) _, err = clientConn.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
@ -479,7 +483,7 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
p.logf("http: proxy error: %v", err) p.logf("http: proxy error: %v", err)
} }
return err return http.StatusInternalServerError, err
} }
go func() { go func() {
@ -492,15 +496,13 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) err
proxyConn.Close() proxyConn.Close()
clientConn.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" { if req.Method == "CONNECT" {
err := p.ProxyHTTPS(rw, req) return p.ProxyHTTPS(rw, req)
return err
} else { } else {
err := p.ProxyHTTP(rw, req, rrr) return p.ProxyHTTP(rw, req, rrr)
return err
} }
} }

View File

@ -1,7 +1,10 @@
package dpcore package dpcore
import ( import (
"bytes"
"io"
"net" "net"
"net/http"
"net/url" "net/url"
"strings" "strings"
) )
@ -92,3 +95,63 @@ func isExternalDomainName(hostname string) bool {
return true 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
}

View File

@ -61,8 +61,8 @@ func (u *Upstream) Clone() *Upstream {
return &newUpstream return &newUpstream
} }
// ServeHTTP uses this upstream proxy router to route the current request // 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) error { func (u *Upstream) ServeHTTP(w http.ResponseWriter, r *http.Request, rrr *dpcore.ResponseRewriteRuleSet) (int, error) {
//Auto rewrite to upstream origin if not set //Auto rewrite to upstream origin if not set
if rrr.ProxyDomain == "" { if rrr.ProxyDomain == "" {
rrr.ProxyDomain = u.OriginIpOrDomain rrr.ProxyDomain = u.OriginIpOrDomain

View File

@ -112,6 +112,8 @@ func (router *Router) rewriteURL(rooturl string, requestURL string) string {
func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) { 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-Host", r.Host)
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID) 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) selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession)
if err != nil { if err != nil {
http.ServeFile(w, r, "./web/rperror.html") http.ServeFile(w, r, "./web/rperror.html")
@ -119,30 +121,34 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
h.Parent.logRequest(r, false, 521, "subdomain-http", r.URL.Hostname()) h.Parent.logRequest(r, false, 521, "subdomain-http", r.URL.Hostname())
return return
} }
requestURL := r.URL.String()
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" { /* WebSocket automatic proxy */
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin if !target.DisableAutoWebSockeyProxy {
r.Header.Set("Zr-Origin-Upgrade", "websocket") requestURL := r.URL.String()
wsRedirectionEndpoint := selectedUpstream.OriginIpOrDomain if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" { //Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
//Append / to the end of the redirection endpoint if not exists r.Header.Set("Zr-Origin-Upgrade", "websocket")
wsRedirectionEndpoint = wsRedirectionEndpoint + "/" 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
} }
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
} }
originalHostHeader := r.Host originalHostHeader := r.Host
@ -156,7 +162,7 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
//Build downstream and upstream header rules //Build downstream and upstream header rules
upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders() upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders()
err = selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: selectedUpstream.OriginIpOrDomain, ProxyDomain: selectedUpstream.OriginIpOrDomain,
OriginalHost: originalHostHeader, OriginalHost: originalHostHeader,
UseTLS: selectedUpstream.RequireTLS, UseTLS: selectedUpstream.RequireTLS,
@ -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 // Handle vdir type request
@ -224,7 +230,7 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
//Build downstream and upstream header rules //Build downstream and upstream header rules
upstreamHeaders, downstreamHeaders := target.parent.SplitInboundOutboundHeaders() 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, ProxyDomain: target.Domain,
OriginalHost: originalHostHeader, OriginalHost: originalHostHeader,
UseTLS: target.RequireTLS, UseTLS: target.RequireTLS,
@ -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, 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)
} }

View File

@ -136,7 +136,8 @@ type ProxyEndpoint struct {
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
EnablePermissionPolicyHeader bool //Enable injection of permission policy header EnablePermissionPolicyHeader bool //Enable injection of permission policy header
PermissionPolicy *permissionpolicy.PermissionsPolicy //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 //Authentication
RequireBasicAuth bool //Set to true to request basic auth before proxy RequireBasicAuth bool //Set to true to request basic auth before proxy

View File

@ -1349,7 +1349,8 @@ func HandleHopByHop(w http.ResponseWriter, r *http.Request) {
//we need to clone and respawn this proxy endpoint //we need to clone and respawn this proxy endpoint
newProxyEndpoint := targetProxyEndpoint.Clone() newProxyEndpoint := targetProxyEndpoint.Clone()
//Storage file use false as default, so disable removal = not enable remover //Storage file use false as default, so disable removal = not enable remover
targetProxyEndpoint.DisableHopByHopHeaderRemoval = !enableHopByHopRemover newProxyEndpoint.DisableHopByHopHeaderRemoval = !enableHopByHopRemover
//Save proxy endpoint //Save proxy endpoint
err = SaveReverseProxyConfig(newProxyEndpoint) err = SaveReverseProxyConfig(newProxyEndpoint)
if err != nil { if err != nil {