mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-03 06:07:20 +02:00
Fixed #297
- Added UI to showcase ZeroSSL do not support DNS challenge - Added test case for origin picker - Updated zerotier struct info (wip)
This commit is contained in:
parent
8b4c601d50
commit
3392013a5c
@ -59,9 +59,9 @@ var enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
name = "Zoraxy"
|
name = "Zoraxy"
|
||||||
version = "3.1.1"
|
version = "3.1.2"
|
||||||
nodeUUID = "generic" //System uuid, in uuidv4 format
|
nodeUUID = "generic" //System uuid, in uuidv4 format
|
||||||
development = false //Set this to false to use embedded web fs
|
development = true //Set this to false to use embedded web fs
|
||||||
bootTime = time.Now().Unix()
|
bootTime = time.Now().Unix()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
package domainsniff
|
package domainsniff
|
||||||
|
|
||||||
|
/*
|
||||||
|
Domainsniff
|
||||||
|
|
||||||
|
This package contain codes that perform project / domain specific behavior in Zoraxy
|
||||||
|
If you want Zoraxy to handle a particular domain or open source project in a special way,
|
||||||
|
you can add the checking logic here.
|
||||||
|
|
||||||
|
*/
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Check if the domain is reachable and return err if not reachable
|
// Check if the domain is reachable and return err if not reachable
|
||||||
func DomainReachableWithError(domain string) error {
|
func DomainReachableWithError(domain string) error {
|
||||||
timeout := 1 * time.Second
|
timeout := 1 * time.Second
|
||||||
conn, err := net.DialTimeout("tcp", domain, timeout)
|
conn, err := net.DialTimeout("tcp", domain, timeout)
|
||||||
@ -17,7 +25,7 @@ func DomainReachableWithError(domain string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if domain reachable
|
// Check if domain reachable
|
||||||
func DomainReachable(domain string) bool {
|
func DomainReachable(domain string) bool {
|
||||||
return DomainReachableWithError(domain) == nil
|
return DomainReachableWithError(domain) == nil
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,67 @@
|
|||||||
package dpcore_test
|
package dpcore_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReplaceLocationHost(t *testing.T) {
|
func TestReplaceLocationHost(t *testing.T) {
|
||||||
urlString := "http://private.com/test/newtarget/"
|
tests := []struct {
|
||||||
rrr := &dpcore.ResponseRewriteRuleSet{
|
name string
|
||||||
OriginalHost: "test.example.com",
|
urlString string
|
||||||
ProxyDomain: "private.com/test",
|
rrr *dpcore.ResponseRewriteRuleSet
|
||||||
UseTLS: true,
|
useTLS bool
|
||||||
}
|
expectedResult string
|
||||||
useTLS := true
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Basic HTTP to HTTPS redirection",
|
||||||
|
urlString: "http://example.com/resource",
|
||||||
|
rrr: &dpcore.ResponseRewriteRuleSet{ProxyDomain: "example.com", OriginalHost: "proxy.example.com", UseTLS: true},
|
||||||
|
useTLS: true,
|
||||||
|
expectedResult: "https://proxy.example.com/resource",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
|
||||||
expectedResult := "https://test.example.com/newtarget/"
|
{
|
||||||
|
name: "Basic HTTPS to HTTP redirection",
|
||||||
result, err := dpcore.ReplaceLocationHost(urlString, rrr, useTLS)
|
urlString: "https://proxy.example.com/resource",
|
||||||
if err != nil {
|
rrr: &dpcore.ResponseRewriteRuleSet{ProxyDomain: "proxy.example.com", OriginalHost: "proxy.example.com", UseTLS: false},
|
||||||
t.Errorf("Error occurred: %v", err)
|
useTLS: false,
|
||||||
|
expectedResult: "http://proxy.example.com/resource",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No rewrite on mismatched domain",
|
||||||
|
urlString: "http://anotherdomain.com/resource",
|
||||||
|
rrr: &dpcore.ResponseRewriteRuleSet{ProxyDomain: "proxy.example.com", OriginalHost: "proxy.example.com", UseTLS: true},
|
||||||
|
useTLS: true,
|
||||||
|
expectedResult: "http://anotherdomain.com/resource",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Subpath trimming with HTTPS",
|
||||||
|
urlString: "https://blog.example.com/post?id=1",
|
||||||
|
rrr: &dpcore.ResponseRewriteRuleSet{ProxyDomain: "blog.example.com", OriginalHost: "proxy.example.com/blog", UseTLS: true},
|
||||||
|
useTLS: true,
|
||||||
|
expectedResult: "https://proxy.example.com/blog/post?id=1",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if result != expectedResult {
|
for _, tt := range tests {
|
||||||
t.Errorf("Expected: %s, but got: %s", expectedResult, result)
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := dpcore.ReplaceLocationHost(tt.urlString, tt.rrr, tt.useTLS)
|
||||||
|
if (err != nil) != tt.expectError {
|
||||||
|
t.Errorf("Expected error: %v, got: %v", tt.expectError, err)
|
||||||
|
}
|
||||||
|
if result != tt.expectedResult {
|
||||||
|
result, _ = url.QueryUnescape(result)
|
||||||
|
t.Errorf("Expected result: %s, got: %s", tt.expectedResult, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +74,7 @@ func TestReplaceLocationHostRelative(t *testing.T) {
|
|||||||
}
|
}
|
||||||
useTLS := true
|
useTLS := true
|
||||||
|
|
||||||
expectedResult := "https://test.example.com/api/"
|
expectedResult := "api/"
|
||||||
|
|
||||||
result, err := dpcore.ReplaceLocationHost(urlString, rrr, useTLS)
|
result, err := dpcore.ReplaceLocationHost(urlString, rrr, useTLS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -60,7 +60,7 @@ func replaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS b
|
|||||||
return u.String(), nil
|
return u.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug functions
|
// Debug functions for replaceLocationHost
|
||||||
func ReplaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) {
|
func ReplaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) {
|
||||||
return replaceLocationHost(urlString, rrr, useTLS)
|
return replaceLocationHost(urlString, rrr, useTLS)
|
||||||
}
|
}
|
||||||
|
100
src/mod/dynamicproxy/loadbalance/originPicker_test.go
Normal file
100
src/mod/dynamicproxy/loadbalance/originPicker_test.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package loadbalance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) { ... }
|
||||||
|
func TestRandomUpstreamSelection(t *testing.T) {
|
||||||
|
rand.Seed(time.Now().UnixNano()) // Seed for randomness
|
||||||
|
|
||||||
|
// Define some test upstreams
|
||||||
|
upstreams := []*Upstream{
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: "192.168.1.1:8080",
|
||||||
|
RequireTLS: false,
|
||||||
|
SkipCertValidations: false,
|
||||||
|
SkipWebSocketOriginCheck: false,
|
||||||
|
Weight: 1,
|
||||||
|
MaxConn: 0, // No connection limit for now
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: "192.168.1.2:8080",
|
||||||
|
RequireTLS: false,
|
||||||
|
SkipCertValidations: false,
|
||||||
|
SkipWebSocketOriginCheck: false,
|
||||||
|
Weight: 1,
|
||||||
|
MaxConn: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: "192.168.1.3:8080",
|
||||||
|
RequireTLS: true,
|
||||||
|
SkipCertValidations: true,
|
||||||
|
SkipWebSocketOriginCheck: true,
|
||||||
|
Weight: 1,
|
||||||
|
MaxConn: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OriginIpOrDomain: "192.168.1.4:8080",
|
||||||
|
RequireTLS: true,
|
||||||
|
SkipCertValidations: true,
|
||||||
|
SkipWebSocketOriginCheck: true,
|
||||||
|
Weight: 1,
|
||||||
|
MaxConn: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track how many times each upstream is selected
|
||||||
|
selectionCount := make(map[string]int)
|
||||||
|
totalPicks := 10000 // Number of times to call getRandomUpstreamByWeight
|
||||||
|
//expectedPickCount := totalPicks / len(upstreams) // Ideal count for each upstream
|
||||||
|
|
||||||
|
// Pick upstreams and record their selection count
|
||||||
|
for i := 0; i < totalPicks; i++ {
|
||||||
|
upstream, _, err := getRandomUpstreamByWeight(upstreams)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error getting random upstream: %v", err)
|
||||||
|
}
|
||||||
|
selectionCount[upstream.OriginIpOrDomain]++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Condition 1: Ensure every upstream has been picked at least once
|
||||||
|
for _, upstream := range upstreams {
|
||||||
|
if selectionCount[upstream.OriginIpOrDomain] == 0 {
|
||||||
|
t.Errorf("Upstream %s was never selected", upstream.OriginIpOrDomain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Condition 2: Check that the distribution is within 1-2 standard deviations
|
||||||
|
counts := make([]float64, len(upstreams))
|
||||||
|
for i, upstream := range upstreams {
|
||||||
|
counts[i] = float64(selectionCount[upstream.OriginIpOrDomain])
|
||||||
|
}
|
||||||
|
|
||||||
|
mean := float64(totalPicks) / float64(len(upstreams))
|
||||||
|
stddev := calculateStdDev(counts, mean)
|
||||||
|
|
||||||
|
tolerance := 2 * stddev // Allowing up to 2 standard deviations
|
||||||
|
for i, count := range counts {
|
||||||
|
if math.Abs(count-mean) > tolerance {
|
||||||
|
t.Errorf("Selection of upstream %s is outside acceptable range: %v picks (mean: %v, stddev: %v)", upstreams[i].OriginIpOrDomain, count, mean, stddev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Selection count:", selectionCount)
|
||||||
|
fmt.Printf("Mean: %.2f, StdDev: %.2f\n", mean, stddev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to calculate standard deviation
|
||||||
|
func calculateStdDev(data []float64, mean float64) float64 {
|
||||||
|
var sumOfSquares float64
|
||||||
|
for _, value := range data {
|
||||||
|
sumOfSquares += (value - mean) * (value - mean)
|
||||||
|
}
|
||||||
|
variance := sumOfSquares / float64(len(data))
|
||||||
|
return math.Sqrt(variance)
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package ganserv
|
package ganserv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
@ -85,6 +86,7 @@ func NewNetworkManager(option *NetworkManagerOptions) *NetworkManager {
|
|||||||
//Get controller info
|
//Get controller info
|
||||||
instanceInfo, err := getControllerInfo(option.AuthToken, option.ApiPort)
|
instanceInfo, err := getControllerInfo(option.AuthToken, option.ApiPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Println("ZeroTier connection failed: ", err.Error())
|
||||||
return &NetworkManager{
|
return &NetworkManager{
|
||||||
authToken: option.AuthToken,
|
authToken: option.AuthToken,
|
||||||
apiPort: option.ApiPort,
|
apiPort: option.ApiPort,
|
||||||
|
@ -28,11 +28,17 @@ type NodeInfo struct {
|
|||||||
Clock int64 `json:"clock"`
|
Clock int64 `json:"clock"`
|
||||||
Config struct {
|
Config struct {
|
||||||
Settings struct {
|
Settings struct {
|
||||||
AllowTCPFallbackRelay bool `json:"allowTcpFallbackRelay"`
|
AllowTCPFallbackRelay bool `json:"allowTcpFallbackRelay,omitempty"`
|
||||||
PortMappingEnabled bool `json:"portMappingEnabled"`
|
ForceTCPRelay bool `json:"forceTcpRelay,omitempty"`
|
||||||
PrimaryPort int `json:"primaryPort"`
|
HomeDir string `json:"homeDir,omitempty"`
|
||||||
SoftwareUpdate string `json:"softwareUpdate"`
|
ListeningOn []string `json:"listeningOn,omitempty"`
|
||||||
SoftwareUpdateChannel string `json:"softwareUpdateChannel"`
|
PortMappingEnabled bool `json:"portMappingEnabled,omitempty"`
|
||||||
|
PrimaryPort int `json:"primaryPort,omitempty"`
|
||||||
|
SecondaryPort int `json:"secondaryPort,omitempty"`
|
||||||
|
SoftwareUpdate string `json:"softwareUpdate,omitempty"`
|
||||||
|
SoftwareUpdateChannel string `json:"softwareUpdateChannel,omitempty"`
|
||||||
|
SurfaceAddresses []string `json:"surfaceAddresses,omitempty"`
|
||||||
|
TertiaryPort int `json:"tertiaryPort,omitempty"`
|
||||||
} `json:"settings"`
|
} `json:"settings"`
|
||||||
} `json:"config"`
|
} `json:"config"`
|
||||||
Online bool `json:"online"`
|
Online bool `json:"online"`
|
||||||
@ -46,7 +52,6 @@ type NodeInfo struct {
|
|||||||
VersionMinor int `json:"versionMinor"`
|
VersionMinor int `json:"versionMinor"`
|
||||||
VersionRev int `json:"versionRev"`
|
VersionRev int `json:"versionRev"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrResp struct {
|
type ErrResp struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
@ -91,8 +91,11 @@
|
|||||||
<div class="ui form">
|
<div class="ui form">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Domain(s)</label>
|
<label>Domain(s)</label>
|
||||||
<input id="domainsInput" type="text" placeholder="example.com" onkeyup="checkIfInputDomainIsMultiple();">
|
<input id="domainsInput" type="text" placeholder="example.com" onkeyup="handlePostInputAutomation();">
|
||||||
<small>If you have more than one domain in a single certificate, enter the domains separated by commas (e.g. s1.dev.example.com,s2.dev.example.com)</small>
|
<small>If you have more than one domain in a single certificate, enter the domains separated by commas (e.g. s1.dev.example.com,s2.dev.example.com)
|
||||||
|
<span id="caNoDNSSupportWarning" style="color: #ffaf2e; display:none;"><br> <i class="exclamation triangle icon"></i> Current selected CA do not support DNS challenge</span>
|
||||||
|
</small>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field multiDomainOnly" style="display:none;">
|
<div class="field multiDomainOnly" style="display:none;">
|
||||||
<label>Matching Rule</label>
|
<label>Matching Rule</label>
|
||||||
@ -113,7 +116,6 @@
|
|||||||
<div class="item" data-value="Buypass">Buypass</div>
|
<div class="item" data-value="Buypass">Buypass</div>
|
||||||
<div class="item" data-value="ZeroSSL">ZeroSSL</div>
|
<div class="item" data-value="ZeroSSL">ZeroSSL</div>
|
||||||
<div class="item" data-value="Custom ACME Server">Custom ACME Server</div>
|
<div class="item" data-value="Custom ACME Server">Custom ACME Server</div>
|
||||||
<!-- <div class="item" data-value="Google">Google</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -389,7 +391,7 @@
|
|||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//On CA change in dropdown
|
//On CA change in dropdown
|
||||||
$("input[name=ca]").on('change', function() {
|
$("input[name=ca]").on('change', function() {
|
||||||
if(this.value == "Custom ACME Server") {
|
if(this.value == "Custom ACME Server") {
|
||||||
@ -740,6 +742,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check if the entered domain contains multiple domains
|
||||||
function checkIfInputDomainIsMultiple(){
|
function checkIfInputDomainIsMultiple(){
|
||||||
var inputDomains = $("#domainsInput").val();
|
var inputDomains = $("#domainsInput").val();
|
||||||
if (inputDomains.includes(",")){
|
if (inputDomains.includes(",")){
|
||||||
@ -749,6 +752,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Validate if the current combinations of domain and CA supports DNS challenge
|
||||||
|
function validateDNSChallengeSupport(){
|
||||||
|
if ($("#domainsInput").val().includes("*")){
|
||||||
|
var ca = $("#ca").dropdown("get value");
|
||||||
|
if (ca == "Let's Encrypt" || ca == ""){
|
||||||
|
$("#caNoDNSSupportWarning").hide();
|
||||||
|
}else{
|
||||||
|
$("#caNoDNSSupportWarning").show();
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
$("#caNoDNSSupportWarning").hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//call to validateDNSChallengeSupport() on #ca value change
|
||||||
|
$("#ca").dropdown({
|
||||||
|
onChange: function(value, text, $selectedItem) {
|
||||||
|
validateDNSChallengeSupport();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Handle the input change event on domain input
|
||||||
|
function handlePostInputAutomation(){
|
||||||
|
checkIfInputDomainIsMultiple();
|
||||||
|
validateDNSChallengeSupport();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function toggleDnsChallenge(){
|
function toggleDnsChallenge(){
|
||||||
if ( $("#useDnsChallenge")[0].checked){
|
if ( $("#useDnsChallenge")[0].checked){
|
||||||
$(".dnsChallengeOnly").show();
|
$(".dnsChallengeOnly").show();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user