Skip to content

Commit 560b005

Browse files
authored
Merge pull request #566 from tobychui/v3.1.9
- Fixed netstat underflow bug - Fixed origin picker cookie bug - Added prototype plugin system - Added plugin examples - Added notice for build-in Zerotier network controller deprecation (and will be moved to plugins) - Added country code display for quickban list
2 parents 2e9d70d + 28a0a83 commit 560b005

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+6814
-60
lines changed

.gitignore

+5-1
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,8 @@ src/log/
4444

4545
# dev-tags
4646
/Dockerfile
47-
/Entrypoint.sh
47+
/Entrypoint.sh
48+
49+
# plugins
50+
example/plugins/ztnc/ztnc.db
51+
example/plugins/ztnc/authtoken.secret

example/plugins/build_all.sh

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
3+
# Iterate over all directories in the current directory
4+
for dir in */; do
5+
if [ -d "$dir" ]; then
6+
echo "Processing directory: $dir"
7+
cd "$dir"
8+
9+
# Execute go mod tidy
10+
echo "Running go mod tidy in $dir"
11+
go mod tidy
12+
13+
# Execute go build
14+
echo "Running go build in $dir"
15+
go build
16+
17+
# Return to the parent directory
18+
cd ..
19+
fi
20+
done
21+
22+
echo "Build process completed for all directories."

example/plugins/debugger/go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module aroz.org/zoraxy/debugger
2+
3+
go 1.23.6

example/plugins/debugger/main.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"strconv"
7+
8+
plugin "aroz.org/zoraxy/debugger/mod/zoraxy_plugin"
9+
)
10+
11+
const (
12+
PLUGIN_ID = "org.aroz.zoraxy.debugger"
13+
UI_PATH = "/debug"
14+
)
15+
16+
func main() {
17+
// Serve the plugin intro spect
18+
// This will print the plugin intro spect and exit if the -introspect flag is provided
19+
runtimeCfg, err := plugin.ServeAndRecvSpec(&plugin.IntroSpect{
20+
ID: "org.aroz.zoraxy.debugger",
21+
Name: "Plugin Debugger",
22+
Author: "aroz.org",
23+
AuthorContact: "https://aroz.org",
24+
Description: "A debugger for Zoraxy <-> plugin communication pipeline",
25+
URL: "https://zoraxy.aroz.org",
26+
Type: plugin.PluginType_Router,
27+
VersionMajor: 1,
28+
VersionMinor: 0,
29+
VersionPatch: 0,
30+
31+
GlobalCapturePaths: []plugin.CaptureRule{
32+
{
33+
CapturePath: "/debug_test", //Capture all traffic of all HTTP proxy rule
34+
IncludeSubPaths: true,
35+
},
36+
},
37+
GlobalCaptureIngress: "",
38+
AlwaysCapturePaths: []plugin.CaptureRule{},
39+
AlwaysCaptureIngress: "",
40+
41+
UIPath: UI_PATH,
42+
43+
/*
44+
SubscriptionPath: "/subept",
45+
SubscriptionsEvents: []plugin.SubscriptionEvent{
46+
*/
47+
})
48+
if err != nil {
49+
//Terminate or enter standalone mode here
50+
panic(err)
51+
}
52+
53+
// Register the shutdown handler
54+
plugin.RegisterShutdownHandler(func() {
55+
// Do cleanup here if needed
56+
fmt.Println("Debugger Terminated")
57+
})
58+
59+
http.HandleFunc(UI_PATH+"/", RenderDebugUI)
60+
http.HandleFunc("/gcapture", HandleIngressCapture)
61+
fmt.Println("Debugger started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port))
62+
http.ListenAndServe("127.0.0.1:"+strconv.Itoa(runtimeCfg.Port), nil)
63+
}
64+
65+
// Handle the captured request
66+
func HandleIngressCapture(w http.ResponseWriter, r *http.Request) {
67+
fmt.Fprint(w, "Capture request received")
68+
w.Header().Set("Content-Type", "text/html")
69+
w.Write([]byte("This request is captured by the debugger"))
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Zoraxy Plugin
2+
3+
## Overview
4+
This module serves as a template for building your own plugins for the Zoraxy Reverse Proxy. By copying this module to your plugin mod folder, you can create a new plugin with the necessary structure and components.
5+
6+
## Instructions
7+
8+
1. **Copy the Module:**
9+
- Copy the entire `zoraxy_plugin` module to your plugin mod folder.
10+
11+
2. **Include the Structure:**
12+
- Ensure that you maintain the directory structure and file organization as provided in this module.
13+
14+
3. **Modify as Needed:**
15+
- Customize the copied module to implement the desired functionality for your plugin.
16+
17+
## Directory Structure
18+
zoraxy_plugin: Handle -introspect and -configuration process required for plugin loading and startup
19+
embed_webserver: Handle embeded web server routing and injecting csrf token to your plugin served UI pages
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package zoraxy_plugin
2+
3+
import (
4+
"embed"
5+
"fmt"
6+
"io/fs"
7+
"net/http"
8+
"net/url"
9+
"strings"
10+
"time"
11+
)
12+
13+
type PluginUiRouter struct {
14+
PluginID string //The ID of the plugin
15+
TargetFs *embed.FS //The embed.FS where the UI files are stored
16+
TargetFsPrefix string //The prefix of the embed.FS where the UI files are stored, e.g. /web
17+
HandlerPrefix string //The prefix of the handler used to route this router, e.g. /ui
18+
}
19+
20+
// NewPluginEmbedUIRouter creates a new PluginUiRouter with embed.FS
21+
// The targetFsPrefix is the prefix of the embed.FS where the UI files are stored
22+
// The targetFsPrefix should be relative to the root of the embed.FS
23+
// The targetFsPrefix should start with a slash (e.g. /web) that corresponds to the root folder of the embed.FS
24+
// The handlerPrefix is the prefix of the handler used to route this router
25+
// The handlerPrefix should start with a slash (e.g. /ui) that matches the http.Handle path
26+
// All prefix should not end with a slash
27+
func NewPluginEmbedUIRouter(pluginID string, targetFs *embed.FS, targetFsPrefix string, handlerPrefix string) *PluginUiRouter {
28+
//Make sure all prefix are in /prefix format
29+
if !strings.HasPrefix(targetFsPrefix, "/") {
30+
targetFsPrefix = "/" + targetFsPrefix
31+
}
32+
targetFsPrefix = strings.TrimSuffix(targetFsPrefix, "/")
33+
34+
if !strings.HasPrefix(handlerPrefix, "/") {
35+
handlerPrefix = "/" + handlerPrefix
36+
}
37+
handlerPrefix = strings.TrimSuffix(handlerPrefix, "/")
38+
39+
//Return the PluginUiRouter
40+
return &PluginUiRouter{
41+
PluginID: pluginID,
42+
TargetFs: targetFs,
43+
TargetFsPrefix: targetFsPrefix,
44+
HandlerPrefix: handlerPrefix,
45+
}
46+
}
47+
48+
func (p *PluginUiRouter) populateCSRFToken(r *http.Request, fsHandler http.Handler) http.Handler {
49+
//Get the CSRF token from header
50+
csrfToken := r.Header.Get("X-Zoraxy-Csrf")
51+
if csrfToken == "" {
52+
csrfToken = "missing-csrf-token"
53+
}
54+
55+
//Return the middleware
56+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
57+
// Check if the request is for an HTML file
58+
if strings.HasSuffix(r.URL.Path, "/") {
59+
// Redirect to the index.html
60+
http.Redirect(w, r, r.URL.Path+"index.html", http.StatusFound)
61+
return
62+
}
63+
if strings.HasSuffix(r.URL.Path, ".html") {
64+
//Read the target file from embed.FS
65+
targetFilePath := strings.TrimPrefix(r.URL.Path, "/")
66+
targetFilePath = p.TargetFsPrefix + "/" + targetFilePath
67+
targetFilePath = strings.TrimPrefix(targetFilePath, "/")
68+
targetFileContent, err := fs.ReadFile(*p.TargetFs, targetFilePath)
69+
if err != nil {
70+
http.Error(w, "File not found", http.StatusNotFound)
71+
return
72+
}
73+
body := string(targetFileContent)
74+
body = strings.ReplaceAll(body, "{{.csrfToken}}", csrfToken)
75+
http.ServeContent(w, r, r.URL.Path, time.Now(), strings.NewReader(body))
76+
return
77+
}
78+
79+
//Call the next handler
80+
fsHandler.ServeHTTP(w, r)
81+
})
82+
83+
}
84+
85+
// GetHttpHandler returns the http.Handler for the PluginUiRouter
86+
func (p *PluginUiRouter) Handler() http.Handler {
87+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
88+
//Remove the plugin UI handler path prefix
89+
rewrittenURL := r.RequestURI
90+
rewrittenURL = strings.TrimPrefix(rewrittenURL, p.HandlerPrefix)
91+
rewrittenURL = strings.ReplaceAll(rewrittenURL, "//", "/")
92+
r.URL, _ = url.Parse(rewrittenURL)
93+
r.RequestURI = rewrittenURL
94+
95+
//Serve the file from the embed.FS
96+
subFS, err := fs.Sub(*p.TargetFs, strings.TrimPrefix(p.TargetFsPrefix, "/"))
97+
if err != nil {
98+
fmt.Println(err.Error())
99+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
100+
return
101+
}
102+
103+
// Replace {{csrf_token}} with the actual CSRF token and serve the file
104+
p.populateCSRFToken(r, http.FileServer(http.FS(subFS))).ServeHTTP(w, r)
105+
})
106+
}

0 commit comments

Comments
 (0)