- 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"
"time"
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
)
@ -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
@ -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)
@ -313,6 +313,11 @@ 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 {
@ -320,7 +325,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
}
//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.
@ -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
}
}
@ -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.
@ -418,14 +422,14 @@ 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()
@ -433,7 +437,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.StatusInternalServerError, err
}
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)
}
return err
return http.StatusInternalServerError, err
}
// 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 {
p.logf("http: proxy error: %v", err)
}
return err
return http.StatusGatewayTimeout, err
}
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)
}
return err
return http.StatusGatewayTimeout, err
}
_, 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)
}
return err
return http.StatusInternalServerError, err
}
go func() {
@ -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)
}
}

View File

@ -1,7 +1,10 @@
package dpcore
import (
"bytes"
"io"
"net"
"net/http"
"net/url"
"strings"
)
@ -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
}

View File

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

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) {
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")
@ -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())
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 + "/"
/* 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
}
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
@ -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,
@ -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
@ -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,
@ -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)
}

View File

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

View File

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