mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-03 06:07:20 +02:00
Updates 2.6.4
+ Added force TLS v1.2 above toggle + Added trace route + Added ICMP ping + Added special routing rules module for up-coming acme integration + Fixed IPv6 check bug in black/whitelist + Optimized UI for TCP Proxy +
This commit is contained in:
parent
a73a7944ec
commit
48dc85ea3e
35
src/acme.go
Normal file
35
src/acme.go
Normal file
@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||
)
|
||||
|
||||
/*
|
||||
acme.go
|
||||
|
||||
This script handle special routing required for acme auto cert renew functions
|
||||
*/
|
||||
|
||||
func acmeRegisterSpecialRoutingRule() {
|
||||
err := dynamicProxyRouter.AddRoutingRules(&dynamicproxy.RoutingRule{
|
||||
ID: "acme-autorenew",
|
||||
MatchRule: func(r *http.Request) bool {
|
||||
if r.RequestURI == "/.well-known/" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
RoutingHandler: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("HELLO WORLD, THIS IS ACME REQUEST HANDLER"))
|
||||
},
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Println("[Err] " + err.Error())
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"imuslab.com/zoraxy/mod/auth"
|
||||
"imuslab.com/zoraxy/mod/netstat"
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@ -55,6 +56,7 @@ func initAPIs() {
|
||||
|
||||
//TLS / SSL config
|
||||
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
|
||||
authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
|
||||
authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
|
||||
authRouter.HandleFunc("/api/cert/list", handleListCertificate)
|
||||
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
|
||||
@ -81,6 +83,11 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/whitelist/ip/remove", handleIpWhitelistRemove)
|
||||
authRouter.HandleFunc("/api/whitelist/enable", handleWhitelistEnable)
|
||||
|
||||
//Path Blocker APIs
|
||||
authRouter.HandleFunc("/api/pathrule/add", pathRuleHandler.HandleAddBlockingPath)
|
||||
authRouter.HandleFunc("/api/pathrule/list", pathRuleHandler.HandleListBlockingPath)
|
||||
authRouter.HandleFunc("/api/pathrule/remove", pathRuleHandler.HandleRemoveBlockingPath)
|
||||
|
||||
//Statistic & uptime monitoring API
|
||||
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
|
||||
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
|
||||
@ -126,6 +133,8 @@ func initAPIs() {
|
||||
|
||||
//Network utilities
|
||||
authRouter.HandleFunc("/api/tools/ipscan", HandleIpScan)
|
||||
authRouter.HandleFunc("/api/tools/traceroute", netutils.HandleTraceRoute)
|
||||
authRouter.HandleFunc("/api/tools/ping", netutils.HandlePing)
|
||||
authRouter.HandleFunc("/api/tools/webssh", HandleCreateProxySession)
|
||||
authRouter.HandleFunc("/api/tools/websshSupported", HandleWebSshSupportCheck)
|
||||
authRouter.HandleFunc("/api/tools/wol", HandleWakeOnLan)
|
||||
|
27
src/cert.go
27
src/cert.go
@ -130,6 +130,33 @@ func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the GET and SET of reverse proxy TLS versions
|
||||
func handleSetTlsRequireLatest(w http.ResponseWriter, r *http.Request) {
|
||||
newState, err := utils.PostPara(r, "set")
|
||||
if err != nil {
|
||||
//GET
|
||||
var reqLatestTLS bool = false
|
||||
if sysdb.KeyExists("settings", "forceLatestTLS") {
|
||||
sysdb.Read("settings", "forceLatestTLS", &reqLatestTLS)
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(reqLatestTLS)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
if newState == "true" {
|
||||
sysdb.Write("settings", "forceLatestTLS", true)
|
||||
log.Println("Updating minimum TLS version to v1.2 or above")
|
||||
dynamicProxyRouter.UpdateTLSVersion(true)
|
||||
} else if newState == "false" {
|
||||
sysdb.Write("settings", "forceLatestTLS", false)
|
||||
log.Println("Updating minimum TLS version to v1.0 or above")
|
||||
dynamicProxyRouter.UpdateTLSVersion(false)
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid state given")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle upload of the certificate
|
||||
func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
||||
// check if request method is POST
|
||||
|
@ -12,5 +12,6 @@ require (
|
||||
github.com/microcosm-cc/bluemonday v1.0.24
|
||||
github.com/oschwald/geoip2-golang v1.8.0
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
golang.org/x/net v0.10.0
|
||||
golang.org/x/sys v0.8.0
|
||||
)
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/mdns"
|
||||
"imuslab.com/zoraxy/mod/netstat"
|
||||
"imuslab.com/zoraxy/mod/pathrule"
|
||||
"imuslab.com/zoraxy/mod/sshprox"
|
||||
"imuslab.com/zoraxy/mod/statistic"
|
||||
"imuslab.com/zoraxy/mod/statistic/analytic"
|
||||
@ -38,7 +39,7 @@ var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local no
|
||||
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
||||
var (
|
||||
name = "Zoraxy"
|
||||
version = "2.6.3"
|
||||
version = "2.6.4"
|
||||
nodeUUID = "generic"
|
||||
development = false //Set this to false to use embedded web fs
|
||||
bootTime = time.Now().Unix()
|
||||
@ -57,6 +58,7 @@ var (
|
||||
authAgent *auth.AuthAgent //Authentication agent
|
||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||
geodbStore *geodb.Store //GeoIP database, also handle black list and whitelist features
|
||||
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
||||
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
||||
@ -149,6 +151,9 @@ func main() {
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
//Start the finalize sequences
|
||||
finalSequence()
|
||||
|
||||
log.Println("Zoraxy started. Visit control panel at http://localhost" + handler.Port)
|
||||
err = http.ListenAndServe(handler.Port, nil)
|
||||
|
||||
|
@ -115,7 +115,6 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.proxyRequest(w, r, targetProxyEndpoint)
|
||||
} else if !strings.HasSuffix(proxyingPath, "/") {
|
||||
potentialProxtEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath + "/")
|
||||
|
||||
if potentialProxtEndpoint != nil {
|
||||
//Missing tailing slash. Redirect to target proxy endpoint
|
||||
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
|
||||
|
@ -45,6 +45,13 @@ func (router *Router) UpdateTLSSetting(tlsEnabled bool) {
|
||||
router.Restart()
|
||||
}
|
||||
|
||||
// Update TLS Version in runtime. Will restart proxy server if running.
|
||||
// Set this to true to force TLS 1.2 or above
|
||||
func (router *Router) UpdateTLSVersion(requireLatest bool) {
|
||||
router.Option.ForceTLSLatest = requireLatest
|
||||
router.Restart()
|
||||
}
|
||||
|
||||
// Update https redirect, which will require updates
|
||||
func (router *Router) UpdateHttpToHttpsRedirectSetting(useRedirect bool) {
|
||||
router.Option.ForceHttpsRedirect = useRedirect
|
||||
@ -62,8 +69,13 @@ func (router *Router) StartProxyService() error {
|
||||
return errors.New("Reverse proxy router root not set")
|
||||
}
|
||||
|
||||
minVersion := tls.VersionTLS10
|
||||
if router.Option.ForceTLSLatest {
|
||||
minVersion = tls.VersionTLS12
|
||||
}
|
||||
config := &tls.Config{
|
||||
GetCertificate: router.Option.TlsManager.GetCert,
|
||||
MinVersion: uint16(minVersion),
|
||||
}
|
||||
|
||||
if router.Option.UseTls {
|
||||
@ -171,18 +183,22 @@ func (router *Router) StopProxyService() error {
|
||||
}
|
||||
|
||||
// Restart the current router if it is running.
|
||||
// Startup the server if it is not running initially
|
||||
func (router *Router) Restart() error {
|
||||
//Stop the router if it is already running
|
||||
var err error = nil
|
||||
if router.Running {
|
||||
err := router.StopProxyService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start the server
|
||||
err = router.StartProxyService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//Start the server
|
||||
err := router.StartProxyService()
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -15,12 +15,12 @@ import (
|
||||
type RoutingRule struct {
|
||||
ID string
|
||||
MatchRule func(r *http.Request) bool
|
||||
RoutingHandler http.Handler
|
||||
RoutingHandler func(http.ResponseWriter, *http.Request)
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
//Router functions
|
||||
//Check if a routing rule exists given its id
|
||||
// Router functions
|
||||
// Check if a routing rule exists given its id
|
||||
func (router *Router) GetRoutingRuleById(rrid string) (*RoutingRule, error) {
|
||||
for _, rr := range router.routingRules {
|
||||
if rr.ID == rrid {
|
||||
@ -31,19 +31,19 @@ func (router *Router) GetRoutingRuleById(rrid string) (*RoutingRule, error) {
|
||||
return nil, errors.New("routing rule with given id not found")
|
||||
}
|
||||
|
||||
//Add a routing rule to the router
|
||||
// Add a routing rule to the router
|
||||
func (router *Router) AddRoutingRules(rr *RoutingRule) error {
|
||||
_, err := router.GetRoutingRuleById(rr.ID)
|
||||
if err != nil {
|
||||
if err == nil {
|
||||
//routing rule with given id already exists
|
||||
return err
|
||||
return errors.New("routing rule with same id already exists")
|
||||
}
|
||||
|
||||
router.routingRules = append(router.routingRules, rr)
|
||||
return nil
|
||||
}
|
||||
|
||||
//Remove a routing rule from the router
|
||||
// Remove a routing rule from the router
|
||||
func (router *Router) RemoveRoutingRule(rrid string) {
|
||||
newRoutingRules := []*RoutingRule{}
|
||||
for _, rr := range router.routingRules {
|
||||
@ -55,13 +55,13 @@ func (router *Router) RemoveRoutingRule(rrid string) {
|
||||
router.routingRules = newRoutingRules
|
||||
}
|
||||
|
||||
//Get all routing rules
|
||||
// Get all routing rules
|
||||
func (router *Router) GetAllRoutingRules() []*RoutingRule {
|
||||
return router.routingRules
|
||||
}
|
||||
|
||||
//Get the matching routing rule that describe this request.
|
||||
//Return nil if no routing rule is match
|
||||
// Get the matching routing rule that describe this request.
|
||||
// Return nil if no routing rule is match
|
||||
func (router *Router) GetMatchingRoutingRule(r *http.Request) *RoutingRule {
|
||||
for _, thisRr := range router.routingRules {
|
||||
if thisRr.IsMatch(r) {
|
||||
@ -71,8 +71,8 @@ func (router *Router) GetMatchingRoutingRule(r *http.Request) *RoutingRule {
|
||||
return nil
|
||||
}
|
||||
|
||||
//Routing Rule functions
|
||||
//Check if a request object match the
|
||||
// Routing Rule functions
|
||||
// Check if a request object match the
|
||||
func (e *RoutingRule) IsMatch(r *http.Request) bool {
|
||||
if !e.Enabled {
|
||||
return false
|
||||
@ -81,5 +81,5 @@ func (e *RoutingRule) IsMatch(r *http.Request) bool {
|
||||
}
|
||||
|
||||
func (e *RoutingRule) Route(w http.ResponseWriter, r *http.Request) {
|
||||
e.RoutingHandler.ServeHTTP(w, r)
|
||||
e.RoutingHandler(w, r)
|
||||
}
|
||||
|
@ -22,13 +22,14 @@ type ProxyHandler struct {
|
||||
}
|
||||
|
||||
type RouterOption struct {
|
||||
HostUUID string
|
||||
Port int
|
||||
UseTls bool
|
||||
ForceHttpsRedirect bool
|
||||
HostUUID string //The UUID of Zoraxy, use for heading mod
|
||||
Port int //Incoming port
|
||||
UseTls bool //Use TLS to serve incoming requsts
|
||||
ForceTLSLatest bool //Force TLS1.2 or above
|
||||
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
||||
TlsManager *tlscert.Manager
|
||||
RedirectRuleTable *redirection.RuleTable
|
||||
GeodbStore *geodb.Store
|
||||
GeodbStore *geodb.Store //GeoIP blacklist and whitelist
|
||||
StatisticCollector *statistic.Collector
|
||||
}
|
||||
|
||||
|
16
src/mod/expose/expose.go
Normal file
16
src/mod/expose/expose.go
Normal file
@ -0,0 +1,16 @@
|
||||
package expose
|
||||
|
||||
/*
|
||||
Service Expose Proxy
|
||||
|
||||
A tunnel for getting your local server online in one line
|
||||
(No, this is not ngrok)
|
||||
*/
|
||||
|
||||
type Router struct {
|
||||
}
|
||||
|
||||
//Create a new service expose router
|
||||
func NewServiceExposeRouter() {
|
||||
|
||||
}
|
111
src/mod/expose/security.go
Normal file
111
src/mod/expose/security.go
Normal file
@ -0,0 +1,111 @@
|
||||
package expose
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"log"
|
||||
)
|
||||
|
||||
// GenerateKeyPair generates a new key pair
|
||||
func GenerateKeyPair(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) {
|
||||
privkey, err := rsa.GenerateKey(rand.Reader, bits)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return privkey, &privkey.PublicKey, nil
|
||||
}
|
||||
|
||||
// PrivateKeyToBytes private key to bytes
|
||||
func PrivateKeyToBytes(priv *rsa.PrivateKey) []byte {
|
||||
privBytes := pem.EncodeToMemory(
|
||||
&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(priv),
|
||||
},
|
||||
)
|
||||
|
||||
return privBytes
|
||||
}
|
||||
|
||||
// PublicKeyToBytes public key to bytes
|
||||
func PublicKeyToBytes(pub *rsa.PublicKey) ([]byte, error) {
|
||||
pubASN1, err := x509.MarshalPKIXPublicKey(pub)
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
|
||||
pubBytes := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "RSA PUBLIC KEY",
|
||||
Bytes: pubASN1,
|
||||
})
|
||||
|
||||
return pubBytes, nil
|
||||
}
|
||||
|
||||
// BytesToPrivateKey bytes to private key
|
||||
func BytesToPrivateKey(priv []byte) (*rsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode(priv)
|
||||
enc := x509.IsEncryptedPEMBlock(block)
|
||||
b := block.Bytes
|
||||
var err error
|
||||
if enc {
|
||||
log.Println("is encrypted pem block")
|
||||
b, err = x509.DecryptPEMBlock(block, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
key, err := x509.ParsePKCS1PrivateKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// BytesToPublicKey bytes to public key
|
||||
func BytesToPublicKey(pub []byte) (*rsa.PublicKey, error) {
|
||||
block, _ := pem.Decode(pub)
|
||||
enc := x509.IsEncryptedPEMBlock(block)
|
||||
b := block.Bytes
|
||||
var err error
|
||||
if enc {
|
||||
log.Println("is encrypted pem block")
|
||||
b, err = x509.DecryptPEMBlock(block, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ifc, err := x509.ParsePKIXPublicKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, ok := ifc.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("key not valid")
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// EncryptWithPublicKey encrypts data with public key
|
||||
func EncryptWithPublicKey(msg []byte, pub *rsa.PublicKey) ([]byte, error) {
|
||||
hash := sha512.New()
|
||||
ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil)
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
// DecryptWithPrivateKey decrypts data with private key
|
||||
func DecryptWithPrivateKey(ciphertext []byte, priv *rsa.PrivateKey) ([]byte, error) {
|
||||
hash := sha512.New()
|
||||
plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, priv, ciphertext, nil)
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
69
src/mod/netutils/netutils.go
Normal file
69
src/mod/netutils/netutils.go
Normal file
@ -0,0 +1,69 @@
|
||||
package netutils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
/*
|
||||
This script handles basic network utilities like
|
||||
- traceroute
|
||||
- ping
|
||||
*/
|
||||
|
||||
func HandleTraceRoute(w http.ResponseWriter, r *http.Request) {
|
||||
targetIpOrDomain, err := utils.GetPara(r, "target")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid target (domain or ip) address given")
|
||||
return
|
||||
}
|
||||
|
||||
maxhopsString, err := utils.GetPara(r, "maxhops")
|
||||
if err != nil {
|
||||
maxhopsString = "64"
|
||||
}
|
||||
|
||||
maxHops, err := strconv.Atoi(maxhopsString)
|
||||
if err != nil {
|
||||
maxHops = 64
|
||||
}
|
||||
|
||||
results, err := TraceRoute(targetIpOrDomain, maxHops)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(results)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func TraceRoute(targetIpOrDomain string, maxHops int) ([]string, error) {
|
||||
return traceroute(targetIpOrDomain, maxHops)
|
||||
}
|
||||
|
||||
func HandlePing(w http.ResponseWriter, r *http.Request) {
|
||||
targetIpOrDomain, err := utils.GetPara(r, "target")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid target (domain or ip) address given")
|
||||
return
|
||||
}
|
||||
|
||||
results := []string{}
|
||||
for i := 0; i < 4; i++ {
|
||||
realIP, pingTime, ttl, err := PingIP(targetIpOrDomain)
|
||||
if err != nil {
|
||||
results = append(results, "Reply from "+realIP+": "+err.Error())
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("Reply from %s: Time=%dms TTL=%d", realIP, pingTime.Milliseconds(), ttl))
|
||||
}
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(results)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
|
||||
}
|
28
src/mod/netutils/netutils_test.go
Normal file
28
src/mod/netutils/netutils_test.go
Normal file
@ -0,0 +1,28 @@
|
||||
package netutils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
)
|
||||
|
||||
func TestHandleTraceRoute(t *testing.T) {
|
||||
results, err := netutils.TraceRoute("imuslab.com", 64)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(results)
|
||||
}
|
||||
|
||||
func TestHandlePing(t *testing.T) {
|
||||
ipOrDomain := "example.com"
|
||||
|
||||
realIP, pingTime, ttl, err := netutils.PingIP(ipOrDomain)
|
||||
if err != nil {
|
||||
t.Fatal("Error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Log(realIP, pingTime, ttl)
|
||||
}
|
48
src/mod/netutils/pingip.go
Normal file
48
src/mod/netutils/pingip.go
Normal file
@ -0,0 +1,48 @@
|
||||
package netutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func PingIP(ipOrDomain string) (string, time.Duration, int, error) {
|
||||
ipAddr, err := net.ResolveIPAddr("ip", ipOrDomain)
|
||||
if err != nil {
|
||||
return "", 0, 0, fmt.Errorf("failed to resolve IP address: %v", err)
|
||||
}
|
||||
|
||||
ip := ipAddr.IP.String()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
conn, err := net.Dial("ip:icmp", ip)
|
||||
if err != nil {
|
||||
return ip, 0, 0, fmt.Errorf("failed to establish ICMP connection: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
icmpMsg := []byte{8, 0, 0, 0, 0, 1, 0, 0}
|
||||
_, err = conn.Write(icmpMsg)
|
||||
if err != nil {
|
||||
return ip, 0, 0, fmt.Errorf("failed to send ICMP message: %v", err)
|
||||
}
|
||||
|
||||
reply := make([]byte, 1500)
|
||||
err = conn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||||
if err != nil {
|
||||
return ip, 0, 0, fmt.Errorf("failed to set read deadline: %v", err)
|
||||
}
|
||||
|
||||
_, err = conn.Read(reply)
|
||||
if err != nil {
|
||||
return ip, 0, 0, fmt.Errorf("failed to read ICMP reply: %v", err)
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
pingTime := elapsed.Round(time.Millisecond)
|
||||
|
||||
ttl := int(reply[8])
|
||||
|
||||
return ip, pingTime, ttl, nil
|
||||
}
|
212
src/mod/netutils/traceroute.go
Normal file
212
src/mod/netutils/traceroute.go
Normal file
@ -0,0 +1,212 @@
|
||||
package netutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
const (
|
||||
protocolICMP = 1
|
||||
)
|
||||
|
||||
// liveTraceRoute return realtime tracing information to live response handler
|
||||
func liveTraceRoute(dst string, maxHops int, liveRespHandler func(string)) error {
|
||||
timeout := time.Second * 3
|
||||
// resolve the host name to an IP address
|
||||
ipAddr, err := net.ResolveIPAddr("ip4", dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve IP address for %s: %v", dst, err)
|
||||
}
|
||||
// create a socket to listen for incoming ICMP packets
|
||||
conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create ICMP listener: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
id := os.Getpid() & 0xffff
|
||||
seq := 0
|
||||
loop_ttl:
|
||||
for ttl := 1; ttl <= maxHops; ttl++ {
|
||||
// set the TTL on the socket
|
||||
if err := conn.IPv4PacketConn().SetTTL(ttl); err != nil {
|
||||
return fmt.Errorf("failed to set TTL: %v", err)
|
||||
}
|
||||
seq++
|
||||
// create an ICMP message
|
||||
msg := icmp.Message{
|
||||
Type: ipv4.ICMPTypeEcho,
|
||||
Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: id,
|
||||
Seq: seq,
|
||||
Data: []byte("zoraxy_trace"),
|
||||
},
|
||||
}
|
||||
// serialize the ICMP message
|
||||
msgBytes, err := msg.Marshal(nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to serialize ICMP message: %v", err)
|
||||
}
|
||||
// send the ICMP message
|
||||
start := time.Now()
|
||||
if _, err := conn.WriteTo(msgBytes, ipAddr); err != nil {
|
||||
//log.Printf("%d: %v", ttl, err)
|
||||
liveRespHandler(fmt.Sprintf("%d: %v", ttl, err))
|
||||
continue loop_ttl
|
||||
}
|
||||
// listen for the reply
|
||||
replyBytes := make([]byte, 1500)
|
||||
if err := conn.SetReadDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return fmt.Errorf("failed to set read deadline: %v", err)
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
n, peer, err := conn.ReadFrom(replyBytes)
|
||||
if err != nil {
|
||||
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
|
||||
//fmt.Printf("%d: *\n", ttl)
|
||||
liveRespHandler(fmt.Sprintf("%d: *\n", ttl))
|
||||
continue loop_ttl
|
||||
} else {
|
||||
liveRespHandler(fmt.Sprintf("%d: Failed to parse ICMP message: %v", ttl, err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
// parse the ICMP message
|
||||
replyMsg, err := icmp.ParseMessage(protocolICMP, replyBytes[:n])
|
||||
if err != nil {
|
||||
liveRespHandler(fmt.Sprintf("%d: Failed to parse ICMP message: %v", ttl, err))
|
||||
continue
|
||||
}
|
||||
// check if the reply is an echo reply
|
||||
if replyMsg.Type == ipv4.ICMPTypeEchoReply {
|
||||
echoReply, ok := msg.Body.(*icmp.Echo)
|
||||
if !ok || echoReply.ID != id || echoReply.Seq != seq {
|
||||
continue
|
||||
}
|
||||
liveRespHandler(fmt.Sprintf("%d: %v %v\n", ttl, peer, time.Since(start)))
|
||||
break loop_ttl
|
||||
}
|
||||
if replyMsg.Type == ipv4.ICMPTypeTimeExceeded {
|
||||
echoReply, ok := msg.Body.(*icmp.Echo)
|
||||
if !ok || echoReply.ID != id || echoReply.Seq != seq {
|
||||
continue
|
||||
}
|
||||
var raddr = peer.String()
|
||||
names, _ := net.LookupAddr(raddr)
|
||||
if len(names) > 0 {
|
||||
raddr = names[0] + " (" + raddr + ")"
|
||||
} else {
|
||||
raddr = raddr + " (" + raddr + ")"
|
||||
}
|
||||
liveRespHandler(fmt.Sprintf("%d: %v %v\n", ttl, raddr, time.Since(start)))
|
||||
continue loop_ttl
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Standard traceroute, return results after complete
|
||||
func traceroute(dst string, maxHops int) ([]string, error) {
|
||||
results := []string{}
|
||||
timeout := time.Second * 3
|
||||
// resolve the host name to an IP address
|
||||
ipAddr, err := net.ResolveIPAddr("ip4", dst)
|
||||
if err != nil {
|
||||
return results, fmt.Errorf("failed to resolve IP address for %s: %v", dst, err)
|
||||
}
|
||||
// create a socket to listen for incoming ICMP packets
|
||||
conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
if err != nil {
|
||||
return results, fmt.Errorf("failed to create ICMP listener: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
id := os.Getpid() & 0xffff
|
||||
seq := 0
|
||||
loop_ttl:
|
||||
for ttl := 1; ttl <= maxHops; ttl++ {
|
||||
// set the TTL on the socket
|
||||
if err := conn.IPv4PacketConn().SetTTL(ttl); err != nil {
|
||||
return results, fmt.Errorf("failed to set TTL: %v", err)
|
||||
}
|
||||
seq++
|
||||
// create an ICMP message
|
||||
msg := icmp.Message{
|
||||
Type: ipv4.ICMPTypeEcho,
|
||||
Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: id,
|
||||
Seq: seq,
|
||||
Data: []byte("zoraxy_trace"),
|
||||
},
|
||||
}
|
||||
// serialize the ICMP message
|
||||
msgBytes, err := msg.Marshal(nil)
|
||||
if err != nil {
|
||||
return results, fmt.Errorf("failed to serialize ICMP message: %v", err)
|
||||
}
|
||||
// send the ICMP message
|
||||
start := time.Now()
|
||||
if _, err := conn.WriteTo(msgBytes, ipAddr); err != nil {
|
||||
//log.Printf("%d: %v", ttl, err)
|
||||
results = append(results, fmt.Sprintf("%d: %v", ttl, err))
|
||||
continue loop_ttl
|
||||
}
|
||||
// listen for the reply
|
||||
replyBytes := make([]byte, 1500)
|
||||
if err := conn.SetReadDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return results, fmt.Errorf("failed to set read deadline: %v", err)
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
n, peer, err := conn.ReadFrom(replyBytes)
|
||||
if err != nil {
|
||||
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
|
||||
//fmt.Printf("%d: *\n", ttl)
|
||||
results = append(results, fmt.Sprintf("%d: *", ttl))
|
||||
continue loop_ttl
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("%d: Failed to parse ICMP message: %v", ttl, err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
// parse the ICMP message
|
||||
replyMsg, err := icmp.ParseMessage(protocolICMP, replyBytes[:n])
|
||||
if err != nil {
|
||||
results = append(results, fmt.Sprintf("%d: Failed to parse ICMP message: %v", ttl, err))
|
||||
continue
|
||||
}
|
||||
// check if the reply is an echo reply
|
||||
if replyMsg.Type == ipv4.ICMPTypeEchoReply {
|
||||
echoReply, ok := msg.Body.(*icmp.Echo)
|
||||
if !ok || echoReply.ID != id || echoReply.Seq != seq {
|
||||
continue
|
||||
}
|
||||
results = append(results, fmt.Sprintf("%d: %v %v", ttl, peer, time.Since(start)))
|
||||
break loop_ttl
|
||||
}
|
||||
if replyMsg.Type == ipv4.ICMPTypeTimeExceeded {
|
||||
echoReply, ok := msg.Body.(*icmp.Echo)
|
||||
if !ok || echoReply.ID != id || echoReply.Seq != seq {
|
||||
continue
|
||||
}
|
||||
var raddr = peer.String()
|
||||
names, _ := net.LookupAddr(raddr)
|
||||
if len(names) > 0 {
|
||||
raddr = names[0] + " (" + raddr + ")"
|
||||
} else {
|
||||
raddr = raddr + " (" + raddr + ")"
|
||||
}
|
||||
results = append(results, fmt.Sprintf("%d: %v %v", ttl, raddr, time.Since(start)))
|
||||
continue loop_ttl
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return results, nil
|
||||
}
|
100
src/mod/pathrule/handler.go
Normal file
100
src/mod/pathrule/handler.go
Normal file
@ -0,0 +1,100 @@
|
||||
package pathrule
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
/*
|
||||
handler.go
|
||||
|
||||
This script handles pathblock api
|
||||
*/
|
||||
|
||||
func (h *Handler) HandleListBlockingPath(w http.ResponseWriter, r *http.Request) {
|
||||
js, _ := json.Marshal(h.BlockingPaths)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func (h *Handler) HandleAddBlockingPath(w http.ResponseWriter, r *http.Request) {
|
||||
matchingPath, err := utils.PostPara(r, "matchingPath")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid matching path given")
|
||||
return
|
||||
}
|
||||
|
||||
exactMatch, err := utils.PostPara(r, "exactMatch")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid exact match value given")
|
||||
return
|
||||
}
|
||||
|
||||
statusCodeString, err := utils.PostPara(r, "statusCode")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid status code given")
|
||||
return
|
||||
}
|
||||
|
||||
statusCode, err := strconv.Atoi(statusCodeString)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid status code given")
|
||||
return
|
||||
}
|
||||
|
||||
enabled, err := utils.PostPara(r, "enabled")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid enabled value given")
|
||||
return
|
||||
}
|
||||
|
||||
caseSensitive, err := utils.PostPara(r, "caseSensitive")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid case sensitive value given")
|
||||
return
|
||||
}
|
||||
|
||||
targetBlockingPath := BlockingPath{
|
||||
UUID: uuid.NewV4().String(),
|
||||
MatchingPath: matchingPath,
|
||||
ExactMatch: exactMatch == "true",
|
||||
StatusCode: statusCode,
|
||||
CustomHeaders: http.Header{},
|
||||
CustomHTML: []byte(""),
|
||||
Enabled: enabled == "true",
|
||||
CaseSenitive: caseSensitive == "true",
|
||||
}
|
||||
|
||||
err = h.AddBlockingPath(&targetBlockingPath)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func (h *Handler) HandleRemoveBlockingPath(w http.ResponseWriter, r *http.Request) {
|
||||
blockerUUID, err := utils.PostPara(r, "uuid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid uuid given")
|
||||
return
|
||||
}
|
||||
|
||||
targetRule := h.GetPathBlockerFromUUID(blockerUUID)
|
||||
if targetRule == nil {
|
||||
//Not found
|
||||
utils.SendErrorResponse(w, "target path blocker not found")
|
||||
return
|
||||
}
|
||||
|
||||
err = h.RemoveBlockingPathByUUID(blockerUUID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
utils.SendOK(w)
|
||||
}
|
175
src/mod/pathrule/pathrule.go
Normal file
175
src/mod/pathrule/pathrule.go
Normal file
@ -0,0 +1,175 @@
|
||||
package pathrule
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
/*
|
||||
Pathblock.go
|
||||
|
||||
This script block off some of the specific pathname in access
|
||||
For example, this module can help you block request for a particular
|
||||
apache directory or functional endpoints like /.well-known/ when you
|
||||
are not using it
|
||||
*/
|
||||
|
||||
type Options struct {
|
||||
ConfigFolder string //The folder to store the path blocking config files
|
||||
}
|
||||
|
||||
type BlockingPath struct {
|
||||
UUID string
|
||||
MatchingPath string
|
||||
ExactMatch bool
|
||||
StatusCode int
|
||||
CustomHeaders http.Header
|
||||
CustomHTML []byte
|
||||
Enabled bool
|
||||
CaseSenitive bool
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
Options *Options
|
||||
BlockingPaths []*BlockingPath
|
||||
}
|
||||
|
||||
// Create a new path blocker handler
|
||||
func NewPathBlocker(options *Options) *Handler {
|
||||
//Create folder if not exists
|
||||
if !utils.FileExists(options.ConfigFolder) {
|
||||
os.Mkdir(options.ConfigFolder, 0775)
|
||||
}
|
||||
|
||||
//Load the configs from file
|
||||
//TODO
|
||||
|
||||
return &Handler{
|
||||
Options: options,
|
||||
BlockingPaths: []*BlockingPath{},
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) ListBlockingPath() []*BlockingPath {
|
||||
return h.BlockingPaths
|
||||
}
|
||||
|
||||
// Get the blocker from matching path (path match, ignore tailing slash)
|
||||
func (h *Handler) GetPathBlockerFromMatchingPath(matchingPath string) *BlockingPath {
|
||||
for _, blocker := range h.BlockingPaths {
|
||||
if blocker.MatchingPath == matchingPath {
|
||||
return blocker
|
||||
} else if strings.TrimSuffix(blocker.MatchingPath, "/") == strings.TrimSuffix(matchingPath, "/") {
|
||||
return blocker
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) GetPathBlockerFromUUID(UUID string) *BlockingPath {
|
||||
for _, blocker := range h.BlockingPaths {
|
||||
if blocker.UUID == UUID {
|
||||
return blocker
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) AddBlockingPath(pathBlocker *BlockingPath) error {
|
||||
//Check if the blocker exists
|
||||
blockerPath := pathBlocker.MatchingPath
|
||||
targetBlocker := h.GetPathBlockerFromMatchingPath(blockerPath)
|
||||
if targetBlocker != nil {
|
||||
//Blocker with the same matching path already exists
|
||||
return errors.New("path blocker with the same path already exists")
|
||||
}
|
||||
|
||||
h.BlockingPaths = append(h.BlockingPaths, pathBlocker)
|
||||
|
||||
//Write the new config to file
|
||||
return h.SaveBlockerToFile(pathBlocker)
|
||||
}
|
||||
|
||||
func (h *Handler) RemoveBlockingPathByUUID(uuid string) error {
|
||||
newBlockingList := []*BlockingPath{}
|
||||
for _, thisBlocker := range h.BlockingPaths {
|
||||
if thisBlocker.UUID != uuid {
|
||||
newBlockingList = append(newBlockingList, thisBlocker)
|
||||
}
|
||||
}
|
||||
|
||||
if len(h.BlockingPaths) == len(newBlockingList) {
|
||||
//Nothing is removed
|
||||
return errors.New("given matching path blocker not exists")
|
||||
}
|
||||
|
||||
h.BlockingPaths = newBlockingList
|
||||
|
||||
return h.RemoveBlockerFromFile(uuid)
|
||||
}
|
||||
|
||||
func (h *Handler) SaveBlockerToFile(pathBlocker *BlockingPath) error {
|
||||
saveFilename := filepath.Join(h.Options.ConfigFolder, pathBlocker.UUID)
|
||||
js, _ := json.MarshalIndent(pathBlocker, "", " ")
|
||||
return os.WriteFile(saveFilename, js, 0775)
|
||||
}
|
||||
|
||||
func (h *Handler) RemoveBlockerFromFile(uuid string) error {
|
||||
expectedConfigFile := filepath.Join(h.Options.ConfigFolder, uuid)
|
||||
if !utils.FileExists(expectedConfigFile) {
|
||||
return errors.New("config file not found on disk")
|
||||
}
|
||||
|
||||
return os.Remove(expectedConfigFile)
|
||||
}
|
||||
|
||||
// Get all the matching blockers for the given URL path
|
||||
// return all the path blockers and the max length matching rule
|
||||
func (h *Handler) GetMatchingBlockers(urlPath string) ([]*BlockingPath, *BlockingPath) {
|
||||
urlPath = strings.TrimSuffix(urlPath, "/")
|
||||
matchingBlockers := []*BlockingPath{}
|
||||
var longestMatchingPrefix *BlockingPath = nil
|
||||
for _, thisBlocker := range h.BlockingPaths {
|
||||
if thisBlocker.Enabled == false {
|
||||
//This blocker is not enabled. Ignore this
|
||||
continue
|
||||
}
|
||||
|
||||
incomingURLPath := urlPath
|
||||
matchingPath := strings.TrimSuffix(thisBlocker.MatchingPath, "/")
|
||||
|
||||
if !thisBlocker.CaseSenitive {
|
||||
//This is not case sensitive
|
||||
incomingURLPath = strings.ToLower(incomingURLPath)
|
||||
matchingPath = strings.ToLower(matchingPath)
|
||||
}
|
||||
|
||||
if matchingPath == incomingURLPath {
|
||||
//This blocker have exact url path match
|
||||
matchingBlockers = append(matchingBlockers, thisBlocker)
|
||||
if longestMatchingPrefix == nil || len(thisBlocker.MatchingPath) > len(longestMatchingPrefix.MatchingPath) {
|
||||
longestMatchingPrefix = thisBlocker
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !thisBlocker.ExactMatch && strings.HasPrefix(incomingURLPath, matchingPath) {
|
||||
//This blocker have prefix url match
|
||||
matchingBlockers = append(matchingBlockers, thisBlocker)
|
||||
if longestMatchingPrefix == nil || len(thisBlocker.MatchingPath) > len(longestMatchingPrefix.MatchingPath) {
|
||||
longestMatchingPrefix = thisBlocker
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return matchingBlockers, longestMatchingPrefix
|
||||
}
|
@ -38,6 +38,14 @@ func ReverseProxtInit() {
|
||||
log.Println("TLS mode disabled. Serving proxy request with plain http")
|
||||
}
|
||||
|
||||
forceLatestTLSVersion := false
|
||||
sysdb.Read("settings", "forceLatestTLS", &forceLatestTLSVersion)
|
||||
if forceLatestTLSVersion {
|
||||
log.Println("Force latest TLS mode enabled. Minimum TLS LS version is set to v1.2")
|
||||
} else {
|
||||
log.Println("Force latest TLS mode disabled. Minimum TLS version is set to v1.0")
|
||||
}
|
||||
|
||||
forceHttpsRedirect := false
|
||||
sysdb.Read("settings", "redirect", &forceHttpsRedirect)
|
||||
if forceHttpsRedirect {
|
||||
@ -50,6 +58,7 @@ func ReverseProxtInit() {
|
||||
HostUUID: nodeUUID,
|
||||
Port: inboundPort,
|
||||
UseTls: useTls,
|
||||
ForceTLSLatest: forceLatestTLSVersion,
|
||||
ForceHttpsRedirect: forceHttpsRedirect,
|
||||
TlsManager: tlsCertManager,
|
||||
RedirectRuleTable: redirectTable,
|
||||
|
20
src/start.go
20
src/start.go
@ -15,6 +15,7 @@ import (
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/mdns"
|
||||
"imuslab.com/zoraxy/mod/netstat"
|
||||
"imuslab.com/zoraxy/mod/pathrule"
|
||||
"imuslab.com/zoraxy/mod/sshprox"
|
||||
"imuslab.com/zoraxy/mod/statistic"
|
||||
"imuslab.com/zoraxy/mod/statistic/analytic"
|
||||
@ -67,7 +68,7 @@ func startupSequence() {
|
||||
}
|
||||
|
||||
//Create a redirection rule table
|
||||
redirectTable, err = redirection.NewRuleTable("./rules")
|
||||
redirectTable, err = redirection.NewRuleTable("./rules/redirect")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -93,6 +94,17 @@ func startupSequence() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
/*
|
||||
Path Blocker
|
||||
|
||||
This section of starutp script start the pathblocker
|
||||
from file.
|
||||
*/
|
||||
|
||||
pathRuleHandler = pathrule.NewPathBlocker(&pathrule.Options{
|
||||
ConfigFolder: "./rules/pathblock",
|
||||
})
|
||||
|
||||
/*
|
||||
MDNS Discovery Service
|
||||
|
||||
@ -177,3 +189,9 @@ func startupSequence() {
|
||||
//Create an analytic loader
|
||||
AnalyticLoader = analytic.NewDataLoader(sysdb, statisticCollector)
|
||||
}
|
||||
|
||||
// This sequence start after everything is initialized
|
||||
func finalSequence() {
|
||||
//Start ACME renew agent
|
||||
acmeRegisterSpecialRoutingRule()
|
||||
}
|
||||
|
@ -1082,24 +1082,51 @@
|
||||
|
||||
//Check if a input is a valid IP address, wildcard of a IP address or a CIDR string
|
||||
function isValidIpFilter(input) {
|
||||
// Check if input is a valid IP address
|
||||
const isValidIp = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/.test(input);
|
||||
// Check if input is a valid IPv4 address
|
||||
const isValidIPv4 = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/.test(input);
|
||||
|
||||
if (isValidIp) {
|
||||
if (isValidIPv4) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if input is a wildcard IP address
|
||||
const isValidWildcardIp = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])$/.test(input);
|
||||
// Check if input is a valid IPv4 wildcard address
|
||||
const isValidIPv4Wildcard = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])$/.test(input);
|
||||
|
||||
if (isValidWildcardIp) {
|
||||
if (isValidIPv4Wildcard) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if input is a valid CIDR address string
|
||||
const isValidCidr = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\/([0-9]|[1-2][0-9]|3[0-2])$/.test(input);
|
||||
// Check if input is a valid IPv4 CIDR address
|
||||
const isValidIPv4CIDR = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\/([0-9]|[1-2][0-9]|3[0-2])$/.test(input);
|
||||
|
||||
if (isValidCidr) {
|
||||
if (isValidIPv4CIDR) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Check if input is loopback ipv6
|
||||
if (input == "::1"){
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if input is a valid IPv6 address
|
||||
const isValidIPv6 = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/.test(input);
|
||||
|
||||
if (isValidIPv6) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Pure magic, I have no idea how this works
|
||||
//src: https://stackoverflow.com/questions/70348674/alternate-solution-validate-ipv4-and-ipv6-with-wildcard-characters-using-r
|
||||
function evalIp6(t){var e=t.split(":"),n=t.split("::").length-1;if(8<e.length&&(9!=t.split(":").length||""!=e[e.length-1]||1!=n))return!1;if(1<n)return!1;if(-1!=t.indexOf("::*")||-1!=t.indexOf("*::"))return!1;var r=!1;for(let t=0;t<e.length;t++){if(!isIPV6Group(e[t]))return!1;"*"==e[t]&&(r=!0)}return!(!r&&0==n&&8!=e.length)}function isIPV6Group(t){var e="^(([0-9A-Fa-f]{1,4})|\\*|)$";return(e=new RegExp(e)).test(t)}
|
||||
if (evalIp6(input)){
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Check if input is a valid IPv6 CIDR address
|
||||
const isValidIPv6CIDR = /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/.test(input);
|
||||
if (isValidIPv6CIDR) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -12,13 +12,41 @@
|
||||
</div>
|
||||
|
||||
<div class="ui bottom attached tab segment nettoolstab active" data-tab="tab1">
|
||||
<!-- MDNS Scanner-->
|
||||
<h2>Multicast DNS (mDNS) Scanner</h2>
|
||||
<p>Discover mDNS enabled service in this gateway forwarded network</p>
|
||||
<button class="ui basic larger circular button" onclick="launchToolWithSize('./tools/mdns.html',1000, 640);">View Discovery</button>
|
||||
<div class="ui divider"></div>
|
||||
<!-- IP Scanner-->
|
||||
<h2>IP Scanner</h2>
|
||||
<p>Discover local area network devices by pinging them one by one</p>
|
||||
<button class="ui basic larger circular button" onclick="launchToolWithSize('./tools/ipscan.html',1000, 640);">Start Scanner</button>
|
||||
<div class="ui divider"></div>
|
||||
<!-- Traceroute-->
|
||||
<h2>Traceroute / Ping</h2>
|
||||
<p>Trace the network nodes that your packets hops through</p>
|
||||
<div class="ui form">
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>Target domain or IP</label>
|
||||
<input type="text" id="traceroute_domain" placeholder="1.1.1.1">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Max Hops</label>
|
||||
<input type="number" min="1" step="1" id="traceroute_maxhops" placeholder="64" value="64">
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui basic button" onclick="traceroute();"><i class="ui blue location arrow icon"></i> Start Tracing</button>
|
||||
<button class="ui basic button" onclick="ping();"><i class="ui teal circle outline icon"></i> Ping</button>
|
||||
<br><br>
|
||||
<div class="field">
|
||||
<label>Results</label>
|
||||
<textarea id="traceroute_results" rows="10" style=""></textarea>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class=""></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="ui bottom attached tab segment nettoolstab" data-tab="tab2">
|
||||
@ -435,7 +463,32 @@ function updateMDNSListForWoL(){
|
||||
}
|
||||
updateMDNSListForWoL();
|
||||
|
||||
function traceroute(){
|
||||
let domain = $("#traceroute_domain").val().trim();
|
||||
let maxhops = $("#traceroute_maxhops").val().trim();
|
||||
$("#traceroute_results").val("Loading...");
|
||||
$.get("/api/tools/traceroute?target=" + domain + "&maxhops=" + maxhops, function(data){
|
||||
if (data.error != undefined){
|
||||
$("#traceroute_results").val("");
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
$("#traceroute_results").val(data.join("\n"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ping(){
|
||||
let domain = $("#traceroute_domain").val().trim();
|
||||
$("#traceroute_results").val("Loading...");
|
||||
$.get("/api/tools/ping?target=" + domain, function(data){
|
||||
if (data.error != undefined){
|
||||
$("#traceroute_results").val("");
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
$("#traceroute_results").val(data.join("\n"));
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
@ -1,78 +1,100 @@
|
||||
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Redirection Rules</h2>
|
||||
<p>Add exception case for redirecting any matching URLs</p>
|
||||
</div>
|
||||
<div style="width: 100%; overflow-x: auto;">
|
||||
<table class="ui sortable unstackable celled table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Redirection URL</th>
|
||||
<th>Destination URL</th>
|
||||
<th class="no-sort">Copy Pathname</th>
|
||||
<th class="no-sort">Status Code</th>
|
||||
<th class="no-sort">Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="redirectionRuleList">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="ui green message" id="delRuleSucc" style="display:none;">
|
||||
<i class="ui green checkmark icon"></i> Redirection Rule Deleted
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h4>Add Redirection Rule</h4>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Redirection URL (From)</label>
|
||||
<input type="text" id="rurl" name="redirection-url" placeholder="Redirection URL">
|
||||
<small><i class="ui circle info icon"></i> Any matching prefix of the request URL will be redirected to the destination URL, e.g. redirect.example.com</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Destination URL (To)</label>
|
||||
<input type="text" name="destination-url" placeholder="Destination URL">
|
||||
<small><i class="ui circle info icon"></i> The target URL request being redirected to, e.g. dest.example.com/mysite</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="forward-childpath" tabindex="0" class="hidden" checked>
|
||||
<label>Forward Pathname</label>
|
||||
<div class="ui basic segment">
|
||||
<h2>Redirection Rules</h2>
|
||||
<p>Add exception case for redirecting any matching URLs</p>
|
||||
</div>
|
||||
<div style="width: 100%; overflow-x: auto;">
|
||||
<table class="ui sortable unstackable celled table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Redirection URL</th>
|
||||
<th>Destination URL</th>
|
||||
<th class="no-sort">Copy Pathname</th>
|
||||
<th class="no-sort">Status Code</th>
|
||||
<th class="no-sort">Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="redirectionRuleList">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="ui green message" id="delRuleSucc" style="display:none;">
|
||||
<i class="ui green checkmark icon"></i> Redirection Rule Deleted
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h4>Add Redirection Rule</h4>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Redirection URL (From)</label>
|
||||
<input type="text" id="rurl" name="redirection-url" placeholder="Redirection URL">
|
||||
<small><i class="ui circle info icon"></i> Any matching prefix of the request URL will be redirected to the destination URL, e.g. redirect.example.com</small>
|
||||
</div>
|
||||
<div class="ui message">
|
||||
<p>Append the current pathname after the redirect destination</p>
|
||||
<i class="check square outline icon"></i> old.example.com<b>/blog?post=13</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com<b>/blog?post=13</b> <br>
|
||||
<i class="square outline icon"></i> old.example.com<b>/blog?post=13</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com
|
||||
<div class="field">
|
||||
<label>Destination URL (To)</label>
|
||||
<input type="text" name="destination-url" placeholder="Destination URL">
|
||||
<small><i class="ui circle info icon"></i> The target URL request being redirected to, e.g. dest.example.com/mysite</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grouped fields">
|
||||
<label>Redirection Status Code</label>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="redirect-type" value="307" checked>
|
||||
<label>Temporary Redirect <br><small>Status Code: 307</small></label>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="forward-childpath" tabindex="0" class="hidden" checked>
|
||||
<label>Forward Pathname</label>
|
||||
</div>
|
||||
<div class="ui message">
|
||||
<p>Append the current pathname after the redirect destination</p>
|
||||
<i class="check square outline icon"></i> old.example.com<b>/blog?post=13</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com<b>/blog?post=13</b> <br>
|
||||
<i class="square outline icon"></i> old.example.com<b>/blog?post=13</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com
|
||||
</div>
|
||||
</div>
|
||||
<div class="grouped fields">
|
||||
<label>Redirection Status Code</label>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="redirect-type" value="307" checked>
|
||||
<label>Temporary Redirect <br><small>Status Code: 307</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="redirect-type" value="301">
|
||||
<label>Moved Permanently <br><small>Status Code: 301</small></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui basic button" onclick="addRules();"><i class="ui teal plus icon"></i> Add Redirection Rule</button>
|
||||
<div class="ui green message" id="ruleAddSucc" style="display:none;">
|
||||
<i class="ui green checkmark icon"></i> Redirection Rules Added
|
||||
</div>
|
||||
<br><br>
|
||||
<!--
|
||||
<div class="advancezone ui basic segment">
|
||||
<div class="ui accordion advanceSettings">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
Advance Options
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>If you need custom header, content or status code other than basic redirects, you can use the advance path rules editor.</p>
|
||||
<button class="ui black basic button" onclick="createAdvanceRules();"><i class="ui black external icon"></i> Open Advance Rules Editor</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="redirect-type" value="301">
|
||||
<label>Moved Permanently <br><small>Status Code: 301</small></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui basic button" onclick="addRules();"><i class="ui teal plus icon"></i> Add Redirection Rule</button>
|
||||
<div class="ui green message" id="ruleAddSucc" style="display:none;">
|
||||
<i class="ui green checkmark icon"></i> Redirection Rules Added
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(".advanceSettings").accordion();
|
||||
|
||||
/*
|
||||
Redirection functions
|
||||
*/
|
||||
$(".checkbox").checkbox();
|
||||
|
||||
function resetForm() {
|
||||
@ -129,6 +151,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function createAdvanceRules(){
|
||||
showSideWrapper("snippet/advancePathRules.html?t=" + Date.now(), true);
|
||||
}
|
||||
|
||||
function initRedirectionRuleList(){
|
||||
$("#redirectionRuleList").html("");
|
||||
$.get("/api/redirect/list", function(data){
|
||||
|
@ -72,11 +72,30 @@
|
||||
<label>Use TLS to serve proxy request</label>
|
||||
</div>
|
||||
<br>
|
||||
<div id="redirect" class="ui toggle notloopbackOnly checkbox" style="margin-top: 0.6em;">
|
||||
<div id="redirect" class="ui toggle notloopbackOnly tlsEnabledOnly checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox">
|
||||
<label>Force redirect HTTP request to HTTPS<br>
|
||||
<small>(Only apply when listening port is not 80)</small></label>
|
||||
</div>
|
||||
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
|
||||
<div class="ui accordion advanceSettings">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
Advance Settings
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>If you have no idea what are these, you can leave them as default :)</p>
|
||||
<div id="tlsMinVer" class="ui toggle notloopbackOnly tlsEnabledOnly checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox">
|
||||
<label>Force TLS v1.2 or above<br>
|
||||
<small>(Enhance security, but not compatible with legacy browsers)</small></label>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<br><br>
|
||||
<button id="startbtn" class="ui teal button" onclick="startService();">Start Service</button>
|
||||
<button id="stopbtn" class="ui red notloopbackOnly disabled button" onclick="stopService();">Stop Service</button>
|
||||
@ -128,6 +147,8 @@
|
||||
</div>
|
||||
<script>
|
||||
let loopbackProxiedInterface = false;
|
||||
$(".advanceSettings").accordion();
|
||||
|
||||
//Initial the start stop button if this is reverse proxied
|
||||
$.get("/api/proxy/requestIsProxied", function(data){
|
||||
if (data == true){
|
||||
@ -340,21 +361,50 @@
|
||||
}
|
||||
initHTTPtoHTTPSRedirectSetting();
|
||||
|
||||
function initTlsVersionSetting(){
|
||||
$.get("/api/cert/tlsRequireLatest", function(data){
|
||||
if (data == true){
|
||||
$("#tlsMinVer").checkbox("set checked");
|
||||
}else{
|
||||
$("#tlsMinVer").checkbox("set unchecked");
|
||||
}
|
||||
|
||||
//Bind events to the checkbox
|
||||
$("#tlsMinVer").find("input").on("change", function(){
|
||||
let thisValue = $("#tlsMinVer").checkbox("is checked");
|
||||
$.ajax({
|
||||
url: "/api/cert/tlsRequireLatest",
|
||||
data: {"set": thisValue},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
msgbox("TLS Version Setting Updated");
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
initTlsVersionSetting();
|
||||
|
||||
function initTlsSetting(){
|
||||
$.get("/api/cert/tls", function(data){
|
||||
if (data == true){
|
||||
$("#tls").checkbox("set checked");
|
||||
}else{
|
||||
$("#redirect").addClass('disabled');
|
||||
$(".tlsEnabledOnly").addClass('disabled');
|
||||
$(".tlsEnabledOnly").addClass('disabled');
|
||||
}
|
||||
|
||||
//Initiate the input listener on the checkbox
|
||||
$("#tls").find("input").on("change", function(){
|
||||
let thisValue = $("#tls").checkbox("is checked");
|
||||
if (thisValue){
|
||||
$("#redirect").removeClass('disabled');
|
||||
$(".tlsEnabledOnly").removeClass('disabled');
|
||||
}else{
|
||||
$("#redirect").addClass('disabled');
|
||||
$(".tlsEnabledOnly").addClass('disabled');
|
||||
}
|
||||
$.ajax({
|
||||
url: "/api/cert/tls",
|
||||
@ -364,7 +414,27 @@
|
||||
alert(data.error);
|
||||
}else{
|
||||
//Updated
|
||||
msgbox("Setting Updated");
|
||||
|
||||
//Check for case if the port is invalid default ports
|
||||
if ($("#incomingPort").val() == "80" && thisValue == true){
|
||||
confirmBox("Change listen port to :443?", function(choice){
|
||||
if (choice == true){
|
||||
$("#incomingPort").val("443");
|
||||
handlePortChange();
|
||||
}
|
||||
});
|
||||
|
||||
}else if ($("#incomingPort").val() == "443" && thisValue == false){
|
||||
confirmBox("Change listen port to :80?", function(choice){
|
||||
if (choice == true){
|
||||
$("#incomingPort").val("80");
|
||||
handlePortChange();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
msgbox("Setting Updated");
|
||||
}
|
||||
|
||||
initRPStaste();
|
||||
}
|
||||
}
|
||||
|
@ -42,11 +42,75 @@
|
||||
<button id="addTcpProxyButton" class="ui basic button" type="submit"><i class="ui blue add icon"></i> Create</button>
|
||||
<button id="editTcpProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event);"><i class="ui blue save icon"></i> Update</button>
|
||||
<button class="ui basic red button" onclick="event.preventDefault(); cancelTCPProxyEdit(event);"><i class="ui red remove icon"></i> Cancel</button>
|
||||
|
||||
<div class="ui basic inverted segment" style="background-color: #414141; border-radius: 0.6em;">
|
||||
<h3>Proxy Mode Instructions</h3>
|
||||
<p>TCP Proxy support the following TCP sockets proxy modes</p>
|
||||
<table class="ui celled padded inverted basic table">
|
||||
<thead>
|
||||
<tr><th class="single line">Mode</th>
|
||||
<th>Public-IP</th>
|
||||
<th>Concurrent Access</th>
|
||||
<th>Flow Diagram</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<h4 class="ui center aligned inverted header">Transport</h4>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui green check icon"></i><br>
|
||||
A: <i class="ui remove icon"></i><br>
|
||||
B: <i class="ui green check icon"></i> (or same LAN)<br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui green check icon"></i>
|
||||
</td>
|
||||
<td>Port A (e.g. 25565) <i class="arrow right icon"></i> Server<br>
|
||||
Server <i class="arrow right icon"></i> Port B (e.g. 192.168.0.2:25565)<br>
|
||||
<small>Traffic from Port A will be forward to Port B's (IP if provided and) Port</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h4 class="ui center aligned inverted header">Listen</h4>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui green check icon"></i><br>
|
||||
A: <i class="ui remove icon"></i><br>
|
||||
B: <i class="ui remove icon"></i><br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui red times icon"></i>
|
||||
</td>
|
||||
<td>Port A (e.g. 8080) <i class="arrow right icon"></i> Server<br>
|
||||
Port B (e.g. 8081) <i class="arrow right icon"></i> Server<br>
|
||||
<small>Server will act as a bridge to proxy traffic between Port A and B</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h4 class="ui center aligned inverted header">Starter</h4>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui times icon"></i><br>
|
||||
A: <i class="ui green check icon"></i><br>
|
||||
B: <i class="ui green check icon"></i><br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui red times icon"></i>
|
||||
</td>
|
||||
<td>Server <i class="arrow right icon"></i> Port A (e.g. remote.local.:8080) <br>
|
||||
Server <i class="arrow right icon"></i> Port B (e.g. recv.local.:8081) <br>
|
||||
<small>Port A and B will be actively bridged</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div class="ui basic segment">
|
||||
<div class="ui basic segment" style="margin-top: 0;">
|
||||
<h3>TCP Proxy Configs</h3>
|
||||
<p>A list of TCP proxy configs created on this host. To enable them, use the toggle button on the right.</p>
|
||||
<div style="overflow-x: auto; min-height: 400px;">
|
||||
@ -67,72 +131,7 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui basic inverted segment" style="background-color: #414141; border-radius: 0.6em;">
|
||||
<h3>Proxy Mode</h3>
|
||||
<p>TCP Proxy support the following TCP sockets proxy modes</p>
|
||||
<table class="ui celled padded inverted basic table">
|
||||
<thead>
|
||||
<tr><th class="single line">Mode</th>
|
||||
<th>Public-IP</th>
|
||||
<th>Concurrent Access</th>
|
||||
<th>Flow Diagram</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<h4 class="ui center aligned inverted header">Transport</h4>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui green check icon"></i><br>
|
||||
A: <i class="ui remove icon"></i><br>
|
||||
B: <i class="ui green check icon"></i> (or same LAN)<br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui green check icon"></i>
|
||||
</td>
|
||||
<td>Port A (e.g. 25565) <i class="arrow right icon"></i> Server<br>
|
||||
Server <i class="arrow right icon"></i> Port B (e.g. 192.168.0.2:25565)<br>
|
||||
<small>Traffic from Port A will be forward to Port B's (IP if provided and) Port</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h4 class="ui center aligned inverted header">Listen</h4>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui green check icon"></i><br>
|
||||
A: <i class="ui remove icon"></i><br>
|
||||
B: <i class="ui remove icon"></i><br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui red times icon"></i>
|
||||
</td>
|
||||
<td>Port A (e.g. 8080) <i class="arrow right icon"></i> Server<br>
|
||||
Port B (e.g. 8081) <i class="arrow right icon"></i> Server<br>
|
||||
<small>Server will act as a bridge to proxy traffic between Port A and B</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h4 class="ui center aligned inverted header">Starter</h4>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui times icon"></i><br>
|
||||
A: <i class="ui green check icon"></i><br>
|
||||
B: <i class="ui green check icon"></i><br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui red times icon"></i>
|
||||
</td>
|
||||
<td>Server <i class="arrow right icon"></i> Port A (e.g. remote.local.:8080) <br>
|
||||
Server <i class="arrow right icon"></i> Port B (e.g. recv.local.:8081) <br>
|
||||
<small>Port A and B will be actively bridged</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
let editingTCPProxyConfigUUID = ""; //The current editing TCP Proxy config UUID
|
||||
|
||||
|
@ -148,6 +148,17 @@
|
||||
<p><i class="green check circle icon"></i> There are no message</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="confirmBox" style="display:none;">
|
||||
<div class="ui top attached progress">
|
||||
<div class="bar" style="width: 100%; min-width: 0px;"></div>
|
||||
</div>
|
||||
<div class="confirmBoxBody">
|
||||
<button class="ui red basic mini circular icon right floated button" style="margin-left: 0.4em;"><i class="ui times icon"></i></button>
|
||||
<button class="ui green basic mini circular icon right floated button"><i class="ui check icon"></i></button>
|
||||
<div class="questionToConfirm">Confirm Exit?</div>
|
||||
</div>
|
||||
</div>
|
||||
<br><br>
|
||||
<script>
|
||||
$(".year").text(new Date().getFullYear());
|
||||
@ -280,6 +291,64 @@
|
||||
$("#messageBox").stop().finish().fadeIn("fast").delay(delayDuration).fadeOut("fast");
|
||||
}
|
||||
|
||||
function confirmBox(question_to_confirm, callback, delaytime=15) {
|
||||
|
||||
var progressBar = $("#confirmBox .bar");
|
||||
var questionElement = $("#confirmBox .questionToConfirm");
|
||||
|
||||
//Just to make sure there are no animation runnings
|
||||
progressBar.stop();
|
||||
|
||||
// Update the question to confirm
|
||||
questionElement.text(question_to_confirm);
|
||||
|
||||
// Start the progress bar animation
|
||||
progressBar.css("width", "100%");
|
||||
progressBar.animate({ width: "0%", easing: "linear" }, delaytime * 1000, function() {
|
||||
// Animation complete, invoke the callback with undefined
|
||||
callback(undefined);
|
||||
|
||||
//Unset the event listener
|
||||
$("#confirmBox .ui.green.button").off("click");
|
||||
|
||||
// Hide the confirm box
|
||||
$("#confirmBox").hide();
|
||||
});
|
||||
|
||||
// Bind click event to "Yes" button
|
||||
$("#confirmBox .ui.green.button").on("click", function() {
|
||||
// Stop the progress bar animation
|
||||
progressBar.stop();
|
||||
|
||||
// Invoke the callback with true
|
||||
callback(true);
|
||||
|
||||
// Hide the confirm box
|
||||
$("#confirmBox").hide();
|
||||
|
||||
//Unset the event listener
|
||||
$("#confirmBox .ui.green.button").off("click");
|
||||
});
|
||||
|
||||
// Bind click event to "No" button
|
||||
$("#confirmBox .ui.red.button").on("click", function() {
|
||||
// Stop the progress bar animation
|
||||
progressBar.stop();
|
||||
|
||||
// Invoke the callback with false
|
||||
callback(false);
|
||||
|
||||
// Hide the confirm box
|
||||
$("#confirmBox").hide();
|
||||
|
||||
//Unset the event listener
|
||||
$("#confirmBox .ui.red.button").off("click");
|
||||
});
|
||||
|
||||
// Show the confirm box
|
||||
$("#confirmBox").show().transition('jiggle');
|
||||
}
|
||||
|
||||
/*
|
||||
Toggles for side wrapper
|
||||
*/
|
||||
|
@ -6,6 +6,7 @@
|
||||
--theme_lgrey: #f6f6f6;
|
||||
--theme_green: #3c9c63;
|
||||
--theme_fcolor: #979797;
|
||||
--theme_advance: #f8f8f9;
|
||||
}
|
||||
body{
|
||||
background-color:#f6f6f6;
|
||||
@ -16,6 +17,15 @@ body{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.advance{
|
||||
background: var(--theme_advance) !important;
|
||||
}
|
||||
|
||||
.advancezone{
|
||||
background: var(--theme_advance) !important;
|
||||
border-radius: 1em !important;
|
||||
}
|
||||
|
||||
.menubar{
|
||||
width: 100%;
|
||||
padding: 0.4em;
|
||||
@ -89,6 +99,34 @@ body{
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
/* Confirm Box */
|
||||
#confirmBox{
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
bottom: 1em;
|
||||
right: 1em;
|
||||
min-width: 300px;
|
||||
background-color: #ffffff;
|
||||
color: rgb(65, 65, 65);
|
||||
box-shadow: 10px 10px 5px -2px rgba(0,0,0,0.13);
|
||||
}
|
||||
|
||||
#confirmBox .confirmBoxBody{
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#confirmBox .ui.progress .bar{
|
||||
background: #ffe32b !important;
|
||||
}
|
||||
|
||||
#confirmBox .confirmBoxBody .button{
|
||||
margin-top: -0.4em;
|
||||
}
|
||||
|
||||
#confirmBox .questionToConfirm{
|
||||
margin-top: -0.2em;
|
||||
}
|
||||
|
||||
/* Standard containers */
|
||||
.standardContainer{
|
||||
position: relative;
|
||||
@ -552,4 +590,18 @@ body{
|
||||
|
||||
.GANetMember.unauthorized{
|
||||
border-left: 6px solid #9c3c3c !important;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Network Utilities
|
||||
*/
|
||||
|
||||
#traceroute_results{
|
||||
resize: none;
|
||||
background-color: #202020;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#traceroute_results::selection {
|
||||
background: #a9d1f3;
|
||||
}
|
76
src/web/snippet/advancePathRules.html
Normal file
76
src/web/snippet/advancePathRules.html
Normal file
@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<!-- Path Rules -->
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
Special Path Rules
|
||||
<div class="sub header">Advanced customization for response on particular matching path or URL</div>
|
||||
</div>
|
||||
</div>
|
||||
<h4>Current list of special path rules.</h4>
|
||||
<div style="width: 100%; overflow-x: auto;">
|
||||
<table class="ui sortable unstackable celled table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Matching Path</th>
|
||||
<th>Status Code</th>
|
||||
<th class="no-sort">Exact Match</th>
|
||||
<th class="no-sort">Case Sensitive</th>
|
||||
<th class="no-sort">Enabled</th>
|
||||
<th class="no-sort">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="specialPathRules">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h4>Add Special Path Rule</h4>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Matching URI</label>
|
||||
<input type="text" name="matchingPath" placeholder="Matching URL">
|
||||
<small><i class="ui circle info icon"></i> Any matching prefix of the request URL will be handled by this rule, e.g. example.com/secret</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="exactMatch" tabindex="0" class="hidden">
|
||||
<label>Require Exact Match</label>
|
||||
</div>
|
||||
<div class="ui message">
|
||||
<p>Require exactly match but not prefix match (default). Enable this if you only want to block access to a directory but not the resources inside the directory. Assume you have entered a matching URI of <b>example.com/secret/</b> and set it to return 401</p>
|
||||
<i class="check square outline icon"></i> example.com/secret<b>/image.png</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> (content of image.png)<br>
|
||||
<i class="square outline icon"></i> example.com/secret<b>/image.png</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> HTTP 401
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Response Status Code</label>
|
||||
<input type="text"name="statusCode" placeholder="500">
|
||||
<small><i class="ui circle info icon"></i> HTTP Status Code to be served by this rule</small>
|
||||
</div>
|
||||
</div>
|
||||
<br><br>
|
||||
<button class="ui basic button iframeOnly" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Cancel</button>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user