package main import ( "encoding/json" "net/http" "sort" "strings" "imuslab.com/zoraxy/mod/dynamicproxy/loadbalance" "imuslab.com/zoraxy/mod/utils" ) /* Upstreams.go This script handle upstream and load balancer related API */ // List upstreams from a endpoint func ReverseProxyUpstreamList(w http.ResponseWriter, r *http.Request) { endpoint, err := utils.GetPara(r, "ep") if err != nil { utils.SendErrorResponse(w, "endpoint not defined") return } targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint) if err != nil { utils.SendErrorResponse(w, "target endpoint not found") return } activeUpstreams := targetEndpoint.ActiveOrigins inactiveUpstreams := targetEndpoint.InactiveOrigins // Sort the upstreams slice by weight, then by origin domain alphabetically sort.Slice(activeUpstreams, func(i, j int) bool { if activeUpstreams[i].Weight != activeUpstreams[j].Weight { return activeUpstreams[i].Weight > activeUpstreams[j].Weight } return activeUpstreams[i].OriginIpOrDomain < activeUpstreams[j].OriginIpOrDomain }) sort.Slice(inactiveUpstreams, func(i, j int) bool { if inactiveUpstreams[i].Weight != inactiveUpstreams[j].Weight { return inactiveUpstreams[i].Weight > inactiveUpstreams[j].Weight } return inactiveUpstreams[i].OriginIpOrDomain < inactiveUpstreams[j].OriginIpOrDomain }) type UpstreamCombinedList struct { ActiveOrigins []*loadbalance.Upstream InactiveOrigins []*loadbalance.Upstream } js, _ := json.Marshal(UpstreamCombinedList{ ActiveOrigins: activeUpstreams, InactiveOrigins: inactiveUpstreams, }) utils.SendJSONResponse(w, string(js)) } // Add an upstream to a given proxy upstream endpoint func ReverseProxyUpstreamAdd(w http.ResponseWriter, r *http.Request) { endpoint, err := utils.PostPara(r, "ep") if err != nil { utils.SendErrorResponse(w, "endpoint not defined") return } targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint) if err != nil { utils.SendErrorResponse(w, "target endpoint not found") return } upstreamOrigin, err := utils.PostPara(r, "origin") if err != nil { utils.SendErrorResponse(w, "upstream origin not set") return } //Response timeout in seconds, set to 0 for default respTimeout, err := utils.PostInt(r, "respt") if err != nil { respTimeout = 0 } //Idle timeout in seconds, set to 0 for default idleTimeout, err := utils.PostInt(r, "idlet") if err != nil { idleTimeout = 0 } //Max concurrent connection to dpcore instance, set to 0 for default maxConn, err := utils.PostInt(r, "maxconn") if err != nil { maxConn = 0 } requireTLS, _ := utils.PostBool(r, "tls") skipTlsValidation, _ := utils.PostBool(r, "tlsval") bpwsorg, _ := utils.PostBool(r, "bpwsorg") preactivate, _ := utils.PostBool(r, "active") //Create a new upstream object newUpstream := loadbalance.Upstream{ OriginIpOrDomain: upstreamOrigin, RequireTLS: requireTLS, SkipCertValidations: skipTlsValidation, SkipWebSocketOriginCheck: bpwsorg, Weight: 1, MaxConn: maxConn, RespTimeout: int64(respTimeout), IdleTimeout: int64(idleTimeout), } //Add the new upstream to endpoint err = targetEndpoint.AddUpstreamOrigin(&newUpstream, preactivate) if err != nil { utils.SendErrorResponse(w, err.Error()) return } //Save changes to configs err = SaveReverseProxyConfig(targetEndpoint) if err != nil { SystemWideLogger.PrintAndLog("INFO", "Unable to save new upstream to proxy config", err) utils.SendErrorResponse(w, "Failed to save new upstream config") return } //Update Uptime Monitor UpdateUptimeMonitorTargets() utils.SendOK(w) } // Update the connection configuration of this origin // pass in the whole new upstream origin json via "payload" POST variable // for missing fields, original value will be used instead func ReverseProxyUpstreamUpdate(w http.ResponseWriter, r *http.Request) { endpoint, err := utils.PostPara(r, "ep") if err != nil { utils.SendErrorResponse(w, "endpoint not defined") return } targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint) if err != nil { utils.SendErrorResponse(w, "target endpoint not found") return } //Editing upstream origin IP originIP, err := utils.PostPara(r, "origin") if err != nil { utils.SendErrorResponse(w, "origin ip or matching address not set") return } originIP = strings.TrimSpace(originIP) //Update content payload payload, err := utils.PostPara(r, "payload") if err != nil { utils.SendErrorResponse(w, "update payload not set") return } isActive, _ := utils.PostBool(r, "active") targetUpstream, err := targetEndpoint.GetUpstreamOriginByMatchingIP(originIP) if err != nil { utils.SendErrorResponse(w, err.Error()) return } //Deep copy the upstream so other request handling goroutine won't be effected newUpstream := targetUpstream.Clone() //Overwrite the new value into the old upstream err = json.Unmarshal([]byte(payload), &newUpstream) if err != nil { utils.SendErrorResponse(w, err.Error()) return } //Replace the old upstream with the new one err = targetEndpoint.RemoveUpstreamOrigin(originIP) if err != nil { utils.SendErrorResponse(w, err.Error()) return } err = targetEndpoint.AddUpstreamOrigin(newUpstream, isActive) if err != nil { utils.SendErrorResponse(w, err.Error()) return } //Save changes to configs err = SaveReverseProxyConfig(targetEndpoint) if err != nil { SystemWideLogger.PrintAndLog("INFO", "Unable to save upstream update to proxy config", err) utils.SendErrorResponse(w, "Failed to save updated upstream config") return } //Update Uptime Monitor UpdateUptimeMonitorTargets() utils.SendOK(w) } func ReverseProxyUpstreamSetPriority(w http.ResponseWriter, r *http.Request) { endpoint, err := utils.PostPara(r, "ep") if err != nil { utils.SendErrorResponse(w, "endpoint not defined") return } targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint) if err != nil { utils.SendErrorResponse(w, "target endpoint not found") return } weight, err := utils.PostInt(r, "weight") if err != nil { utils.SendErrorResponse(w, "priority not defined") return } if weight < 0 { utils.SendErrorResponse(w, "invalid weight given") return } //Editing upstream origin IP originIP, err := utils.PostPara(r, "origin") if err != nil { utils.SendErrorResponse(w, "origin ip or matching address not set") return } originIP = strings.TrimSpace(originIP) editingUpstream, err := targetEndpoint.GetUpstreamOriginByMatchingIP(originIP) editingUpstream.Weight = weight // The editing upstream is a pointer to the runtime object // and the change of weight do not requre a respawn of the proxy object // so no need to remove & re-prepare the upstream on weight changes err = SaveReverseProxyConfig(targetEndpoint) if err != nil { SystemWideLogger.PrintAndLog("INFO", "Unable to update upstream weight", err) utils.SendErrorResponse(w, "Failed to update upstream weight") return } utils.SendOK(w) } func ReverseProxyUpstreamDelete(w http.ResponseWriter, r *http.Request) { endpoint, err := utils.PostPara(r, "ep") if err != nil { utils.SendErrorResponse(w, "endpoint not defined") return } targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint) if err != nil { utils.SendErrorResponse(w, "target endpoint not found") return } //Editing upstream origin IP originIP, err := utils.PostPara(r, "origin") if err != nil { utils.SendErrorResponse(w, "origin ip or matching address not set") return } originIP = strings.TrimSpace(originIP) if !targetEndpoint.UpstreamOriginExists(originIP) { utils.SendErrorResponse(w, "target upstream not found") return } err = targetEndpoint.RemoveUpstreamOrigin(originIP) if err != nil { utils.SendErrorResponse(w, err.Error()) return } //Save changes to configs err = SaveReverseProxyConfig(targetEndpoint) if err != nil { SystemWideLogger.PrintAndLog("INFO", "Unable to remove upstream", err) utils.SendErrorResponse(w, "Failed to remove upstream from proxy rule") return } //Update uptime monitor UpdateUptimeMonitorTargets() utils.SendOK(w) }