48 Commits
2.6.8 ... 3.0.1

Author SHA1 Message Date
05297d854b Merge pull request #117 from tobychui/v3.0.1
V3.0.1 Updates
- Added regex support for redirect (slow, don't use it unless you really needs it)
- Added new dpcore implementations for faster proxy speed
- Added support for CF-Connecting-IP to X-Real-IP auto rewrite
- Added better 404 page
- Added option to bypass websocket origin check
- Updated project homepage design
- Fixed recursive port detection logic
- Fixed UserAgent in resp bug
- Updated minimum required Go version to v1.22 (Notes: Windows 7 support is dropped)
2024-04-04 15:02:10 +08:00
0d7bce4d30 Updated to go 1.22 2024-04-04 14:48:17 +08:00
8db95dddc6 Added regexp redirect support 2024-04-04 14:24:38 +08:00
05daeded37 Updated dpcore
- Added immediate flush for buffer (i.e. no chunked encoding)
- Added support for stream mode proxy sniffing logic
2024-04-01 21:36:11 +08:00
8ce6471be5 Updated homepage design 2024-03-30 16:26:03 +08:00
e242c9288f Fixed recursive port detection logic
+ Fixed recursive port detection logic
+ Added support for CF-Connecting-IP #114
2024-03-30 15:52:10 +08:00
c55a29e7cf Better 404 page and rules disable toggle 2024-03-16 20:45:05 +08:00
6af047430c Added UI for WebSocket Origin Check bypass 2024-03-12 14:03:31 +08:00
200c924acd 3.0.1 init commit
- Removed Go HTTP client UA
- Added optional bypass of websocket origin check #107
- Added basic forward proxy for debug
- Fixed UI error in network utils tab
2024-03-10 14:49:18 +08:00
9b2168466c Update access.html
Fixed issue #103
2024-02-24 14:04:33 +08:00
7ae48bf370 Update README.md
Added quick download links for Github noobs
2024-02-20 22:00:58 +08:00
ee3d76fb96 Fix: Failure to build upon release
If we need these back, we can just re-add them.
2024-02-18 14:20:34 -05:00
40d192524b Merge pull request #102 from Morethanevil/main
Update CHANGELOG.md
2024-02-18 21:08:50 +08:00
c659e05005 Update CHANGELOG.md
Updated Changelog
2024-02-18 13:36:35 +01:00
676a45c222 Merge pull request #101 from tobychui/v3.0.0
V3.0.0 Updates
2024-02-18 20:00:20 +08:00
1da0761b13 Update vdir.html
Fixed minor issue on vdir not loading on start
2024-02-18 19:59:10 +08:00
32939874f2 Fix: Remove override for OpenSSL 2024-02-17 12:24:31 -05:00
43a4bf389a Updated README 2024-02-17 21:07:28 +08:00
33c7c5fa00 Custom header support
+ Added custom header
+ Removed unused files
2024-02-17 20:28:19 +08:00
216b53f224 Updated GAN features
+ Added add controller as memeber feature
+ Deprecated aroz subservice support
2024-02-16 21:16:14 +08:00
059b0a2e1c Update 1.png 2024-02-16 16:00:17 +08:00
3ab952f168 Update main.css
Fixed text color on rule instruction bug
2024-02-16 15:51:56 +08:00
4f676d6770 Updated system screenshots 2024-02-16 15:51:23 +08:00
e980bc847b Updated a lot of stuffs
+ Added comments for whitelist
+ Added automatic cert pick for multi-host certs (SNI)
+ Renamed .crt to .pem for cert store
+ Added best-fit selection for wildcard matching rules
+ Added x-proxy-by header
+ Added X-real-Ip header
+ Added Development Mode (Cache-Control: no-store)
+ Updated utm timeout to 10 seconds instead of 90
2024-02-16 15:44:09 +08:00
174efc9080 Added per host vdir implementation 2024-02-14 22:52:56 +08:00
3228789375 Restructured proxy routing logic
- Moved virtual directory into host routing object
- Generalized root and hosts routing struct
- Optimized UI
2024-02-13 21:46:43 +08:00
36e461795a Added zoraxy start paramters for reference 2024-01-30 15:22:59 +08:00
d6e7641364 Merge pull request #88 from PassiveLemon/EnglishCorrection
English correction
2024-01-01 21:38:42 +08:00
15cebd6e06 Update README.md 2023-12-04 21:03:45 -05:00
e9a074d4d1 Merge branch 'EnglishCorrection' of https://github.com/PassiveLemon/zoraxy-dev into EnglishCorrection 2023-12-04 21:01:28 -05:00
4b7fd39e57 Update web root 2023-12-04 20:59:50 -05:00
fa005f1327 Update README.md 2023-12-04 20:59:25 -05:00
c7a9f40baa Update README.md 2023-12-04 20:44:42 -05:00
d5b9726158 Update index.html 2023-12-04 20:44:41 -05:00
f659e66cf7 Update CHANGELOG.md 2023-12-04 20:44:39 -05:00
801bdbf298 Fix: main.yml
Some things somehow passed by me and needed fixing.
Updated the actions in the process.
2023-11-28 23:16:51 -05:00
09da93cfb3 Merge pull request #70 from PassiveLemon/Ghcr
Update container (2.6.8)
2023-11-28 18:46:42 +08:00
70ace02e80 Revert port update 2023-11-27 20:18:24 -05:00
1f758e953d Fix: OpenSSL CVE 2023-11-26 16:16:31 -05:00
ffad2cab81 Comment out GHCR 2023-11-25 12:20:52 -05:00
dbb10644de Update to new port 2023-11-25 12:14:26 -05:00
4848392185 Merge branch 'tobychui:main' into Ghcr 2023-11-25 12:09:43 -05:00
956f4ac30f Merge pull request #85 from Morethanevil/patch-7
Update CHANGELOG.md
2023-11-25 23:07:14 +08:00
c09ff28fd5 Update CHANGELOG.md 2023-11-25 16:00:54 +01:00
4d40e0aa38 Publish to GHCR 2023-10-03 16:36:53 -04:00
045e66b631 Revert "Publish to GitHub Container Registry"
This reverts commit 23bdaa1517.
2023-10-03 16:35:10 -04:00
62e60d78de Merge branch 'tobychui:main' into main 2023-10-03 16:34:43 -04:00
23bdaa1517 Publish to GitHub Container Registry 2023-10-03 16:34:23 -04:00
151 changed files with 5736 additions and 13329 deletions

View File

@ -9,18 +9,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to Dockerhub
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Login to Docker & GHCR
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Setup building file structure
run: |
@ -36,11 +37,5 @@ jobs:
--provenance=false \
--platform linux/amd64,linux/arm64 \
--tag zoraxydocker/zoraxy:${{ github.event.release.tag_name }} \
.
docker buildx build --push \
--build-arg VERSION=${{ github.event.release.tag_name }} \
--provenance=false \
--platform linux/amd64,linux/arm64 \
--tag zoraxydocker/zoraxy:latest \
.

View File

@ -1,3 +1,37 @@
# v3.0.0 Feb 18 2024
## IMPORTANT: V3 is a big rewrite and it is incompatible with V2! There is NO migration, if you want to stay on V2, please use V2 branch!
+ Added comments for whitelist [#97](https://github.com/tobychui/zoraxy/issues/97)
+ Added force-renew for certificates [#92](https://github.com/tobychui/zoraxy/issues/92)
+ Added automatic cert pick for multi-host certs (SNI)
+ Renamed .crt to .pem for cert store
+ Added best-fit selection for wildcard matching rules
+ Added x-proxy-by header / Added X-real-Ip header [#93](https://github.com/tobychui/zoraxy/issues/93)
+ Added Development Mode (Cache-Control: no-store)
+ Updated utm timeout to 10 seconds instead of 90
+ Added "Add controller as member" feature to Global Area Network editor
+ Added custom header
+ Deprecated aroz subservice support
+ Updated visuals, improving logical structure, less depressing colors [#95](https://github.com/tobychui/zoraxy/issues/95)
+ Added virtual directory into host routing object (each host now got its own sets of virtual directories)
+ Added support for wildcard host names (e.g. *.example.com)
+ Added best-fit selection for wildcard matching rules (e.g. *.a.example.com > *.example.com in routing)
+ Generalized root and hosts routing struct (no more conversion between runtime & save record object
+ Added "Default Site" to replace "Proxy Root" interface
+ Added Redirect & 404 page for "Default Site"
# v2.6.8 Nov 25 2023
+ Added opt-out for subdomains for global TLS settings: See [release notes](https://github.com/tobychui/zoraxy/releases/tag/2.6.8)
+ Optimized subdomain / vdir editing interface
+ Added system-wide logger (Work in progress)
+ Fixed issue for uptime monitor bug [#77](https://github.com/tobychui/zoraxy/issues/77)
+ Changed default static web port to 5487 (prevent already in use)
+ Added automatic HTTP/2 to TLS mode
+ Bug fix for webserver autostart [67](https://github.com/tobychui/zoraxy/issues/67)
# v2.6.7 Sep 26 2023
+ Added Static Web Server function [#56](https://github.com/tobychui/zoraxy/issues/56)
@ -14,20 +48,20 @@
+ Optimized memory usage (from 1.2GB to 61MB for low speed geoip lookup) [#52](https://github.com/tobychui/zoraxy/issues/52)
+ Added unset subdomain custom redirection feature [#46](https://github.com/tobychui/zoraxy/issues/46)
+ Fixed potential security issue in satori/go.uuid [#55](https://github.com/tobychui/zoraxy/issues/55)
+ Added custom acme feature in back-end, thx [@daluntw](https://github.com/daluntw)
+ Added custom ACME feature in backend, thx [@daluntw](https://github.com/daluntw)
+ Added bypass TLS check for custom acme server, thx [@daluntw](https://github.com/daluntw)
+ Introduce new startparameter `-fastgeoip=true`, see [Releases](https://github.com/tobychui/zoraxy/releases/tag/2.6.6)
+ Introduce new start parameter `-fastgeoip=true`: see [release notes](https://github.com/tobychui/zoraxy/releases/tag/2.6.6)
# v2.6.5.1 Jul 26 2023
+ Patch on memory leaking for Windows netstat module (do not effect any of the previous non Windows builds)
+ Fixed potential memory leak in acme handler logic
+ Added "Do you want to get a TLS certificate for this subdomain?" dialog when a new subdomain proxy rule is created
+ Fixed potential memory leak in ACME handler logic
+ Added "Do you want to get a TLS certificate for this subdomain?" dialogue when a new subdomain proxy rule is created
# v2.6.5 Jul 19 2023
+ Added Import / Export-Feature
+ Moved configurationfiles to a separate folder [#26](https://github.com/tobychui/zoraxy/issues/26)
+ Moved configuration files to a separate folder [#26](https://github.com/tobychui/zoraxy/issues/26)
+ Added auto-renew with ACME [#6](https://github.com/tobychui/zoraxy/issues/6)
+ Fixed Whitelistbug [#18](https://github.com/tobychui/zoraxy/issues/18)
+ Added Whois
@ -37,7 +71,7 @@
+ Added force TLS v1.2 above toggle
+ Added trace route
+ Added ICMP ping
+ Added special routing rules module for up-coming acme integration
+ Added special routing rules module for up-coming ACME integration
+ Fixed IPv6 check bug in black/whitelist
+ Optimized UI for TCP Proxy
@ -47,7 +81,7 @@
+ Split blacklist and whitelist from geodb script file
+ Optimized compile binary size
+ Added access control to TCP proxy
+ Added "invalid config detect" in up time monitor for isse [#7](https://github.com/tobychui/zoraxy/issues/7)
+ Added "invalid config detect" in up time monitor for issue [#7](https://github.com/tobychui/zoraxy/issues/7)
+ Fixed minor bugs in advance stats panel
+ Reduced file size of embedded materials
@ -74,6 +108,6 @@
+ Basic auth
+ Support TLS verification skip (for self signed certs)
+ Added trend analysis
+ Added referer and file type analysis
+ Added referrer and file type analysis
+ Added cert expire day display
+ Moved subdomain proxy logic to dpcore

128
README.md
View File

@ -2,24 +2,28 @@
# Zoraxy
General purpose request (reverse) proxy and forwarding tool for low power devices. Now written in Go!
General purpose request (reverse) proxy and forwarding tool for networking noobs. Now written in Go!
*Zoraxy v3 HTTP proxy config is not compatible with the older v2. If you are looking for the legacy version of Zoraxy, take a look at the [v2 branch](https://github.com/tobychui/zoraxy/tree/v2)*
### Features
- Simple to use interface with detail in-system instructions
- Reverse Proxy
- Subdomain Reverse Proxy
- Virtual Directory Reverse Proxy
- Virtual Directory
- Basic Auth
- Custom Headers
- Redirection Rules
- TLS / SSL setup and deploy
- Blacklist by country or IP address (single IP, CIDR or wildcard for beginners)
- ACME features like auto-renew to serve your sites in http**s**
- SNI support (one certificate contains multiple host names)
- Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)
- Global Area Network Controller Web UI (ZeroTier not included)
- TCP Tunneling / Proxy
- Integrated Up-time Monitor
- Web-SSH Terminal
- Utilities
- CIDR IP converters
- mDNS Scanner
- IP Scanner
@ -28,10 +32,17 @@ General purpose request (reverse) proxy and forwarding tool for low power device
- External permission management system for easy system integration
- SMTP config for password reset
## Build from Source
Require Go 1.20 or above
## Downloads
[Windows](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_windows_amd64.exe)
/[Linux (amd64)](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_amd64)
/[Linux (arm64)](https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_arm64)
```
For other systems or architectures, please see [Release](https://github.com/tobychui/zoraxy/releases/latest/)
## Build from Source
Requires Go 1.22 or higher
```bash
git clone https://github.com/tobychui/zoraxy
cd ./zoraxy/src/
go mod tidy
@ -42,11 +53,11 @@ sudo ./zoraxy -port=:8000
## Usage
Zoraxy provide basic authentication system for standalone mode. To use it in standalone mode, follow the instruction below for your desired deployment platform.
Zoraxy provides basic authentication system for standalone mode. To use it in standalone mode, follow the instructionss below for your desired deployment platform.
### Standalone Mode
Standalone mode is the default mode for Zoraxy. This allow single account to manage your reverse proxy server just like a home router. This mode is suitable for new owners for homelab or makers start growing their web services into multiple servers.
Standalone mode is the default mode for Zoraxy. This allows a single account to manage your reverse proxy server, just like a home router. This mode is suitable for new owners to homelabs or makers starting growing their web services into multiple servers.
#### Linux
@ -60,18 +71,49 @@ Download the binary executable and double click the binary file to start it.
#### Raspberry Pi
The installation method is same as Linux. If you are using Raspberry Pi 4 or newer models, pick the arm64 release. For older version of Pis, use the arm (armv6) version instead.
The installation method is same as Linux. If you are using a Raspberry Pi 4 or newer models, pick the arm64 release. For older version of Pis, use the arm (armv6) version instead.
#### Other ARM SBCs or Android phone with Termux
The installation method is same as Linux. For other ARM SBCs, please refer to your SBC's CPU architecture and pick the one that is suitable for your device.
#### Docker
See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder for more details
See the [/docker](https://github.com/tobychui/zoraxy/tree/main/docker) folder for more details.
### Start Paramters
```
Usage of zoraxy:
-autorenew int
ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400)
-fastgeoip
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
-info
Show information about this program in JSON
-log
Log terminal output to file (default true)
-mdns
Enable mDNS scanner and transponder (default true)
-noauth
Disable authentication for management interface
-port string
Management web interface listening port (default ":8000")
-sshlb
Allow loopback web ssh connection (DANGER)
-version
Show version of this server
-webfm
Enable web file manager for static web server root folder (default true)
-webroot string
Static web server root folder. Only allow chnage in start paramters (default "./www")
-ztauth string
ZeroTier authtoken for the local node
-ztport int
ZeroTier controller API port (default 9993)
```
### External Permission Management Mode
If you already have a up-stream reverse proxy server in place with permission management, you can use Zoraxy in noauth mode. To enable noauth mode, start Zoraxy with the following flag
If you already have an upstream reverse proxy server in place with permission management, you can use Zoraxy in noauth mode. To enable noauth mode, start Zoraxy with the following flag:
```bash
./zoraxy -noauth=true
@ -79,45 +121,12 @@ If you already have a up-stream reverse proxy server in place with permission ma
*Note: For security reaons, you should only enable no-auth if you are running Zoraxy in a trusted environment or with another authentication management proxy in front.*
#### Use with ArozOS
[ArozOS ](https://arozos.com)subservice is a build in permission managed reverse proxy server. To use zoraxy with arozos, connect to your arozos host via ssh and use the following command to install zoraxy
```bash
# cd into your arozos subservice folder. Sometime it is under ~/arozos/src/subservice
cd ~/arozos/subservices
mkdir zoraxy
cd ./zoraxy
# Download the release binary from Github release
wget {binary executable link from release page}
# Set permission. Change this if required
sudo chmod 775 -R ./
# Start zoraxy to see if the downloaded arch is correct.
./zoraxy
# After the unzip done, press Ctrl + C to kill it
# Rename it to valid arozos subservice binary format
mv ./zoraxy zoraxy_linux_amd64
# If you are using SBCs with different CPU arch, use the following names
# mv ./zoraxy zoraxy_linux_arm
# mv ./zoraxy zoraxy_linux_arm64
# Restart arozos
sudo systemctl restart arozos
```
To start the module, go to System Settings > Modules > Subservice and enable it in the menu. You should be able to see a new module named "Zoraxy" pop up in the start menu.
## Screenshots
![](img/screenshots/0_1.png)
![](img/screenshots/1.png)
![](img/screenshots/2.png)
More screenshots on the wikipage [Screenshots](https://github.com/tobychui/zoraxy/wiki/Screenshots)!
## FAQ
@ -128,22 +137,21 @@ There is a wikipage with [Frequently-Asked-Questions](https://github.com/tobychu
This project also compatible with [ZeroTier](https://www.zerotier.com/). However, due to licensing issues, ZeroTier is not included in the binary.
Assuming you already have a valid license, to use Zoraxy with ZeroTier, install ZeroTier on your host and then run Zoraxy in sudo mode (or Run As Administrator if you are on Windows). The program will automatically grab the authtoken at correct location in your host.
To use Zoraxy with ZeroTier, assuming you already have a valid license, install ZeroTier on your host and then run Zoraxy in sudo mode (or Run As Administrator if you are on Windows). The program will automatically grab the authtoken in the correct location on your host.
If you prefer not to run Zoraxy in sudo mode or you have some weird installation profile, you can also pass in the ZeroTier auth token using the following flags
If you prefer not to run Zoraxy in sudo mode or you have some weird installation profile, you can also pass in the ZeroTier auth token using the following flags::
```
```bash
./zoraxy -ztauth="your_zerotier_authtoken" -ztport=9993
```
The ZeroTier auth token can usually be found at ```/var/lib/zerotier-one/authtoken.secret``` or ```C:\ProgramData\ZeroTier\One\authtoken.secret```.
This allows you to have infinite number of network members in your Global Area Network controller. For more technical details, see [here](https://docs.zerotier.com/self-hosting/network-controllers/).
This allows you to have an infinite number of network members in your Global Area Network controller. For more technical details, see [here](https://docs.zerotier.com/self-hosting/network-controllers/).
## Web.SSH
Web SSH currently only support Linux based OS. The following platforms are supported
## Web SSH
Web SSH currently only supports Linux based OSes. The following platforms are supported:
- linux/amd64
- linux/arm64
- linux/armv6 (experimental)
@ -151,9 +159,9 @@ Web SSH currently only support Linux based OS. The following platforms are suppo
### Loopback Connection
Loopback web ssh connection, by default, is disabled. This means that if you are trying to connect to address like 127.0.0.1 or localhost, the system will reject your connection due to security issues. To enable loopback for testing or development purpose, use the following flags to override the loopback checking.
Loopback web SSH connection, by default, is disabled. This means that if you are trying to connect to an address like 127.0.0.1 or localhost, the system will reject your connection for security reasons. To enable loopback for testing or development purpose, use the following flags to override the loopback checking:
```
```bash
./zoraxy -sshlb=true
```
@ -165,5 +173,5 @@ If you like the project and want to support us, please consider a donation. You
## License
This project is open source under AGPL. I open source this project so everyone can check for security issues and benefit all users. **If your plans to use this project in commercial environment which violate the AGPL terms, please contact toby@imuslab.com for an alternative commercial license.**
This project is open-sourced under AGPL. I open-sourced this project so everyone can check for security issues and benefit all users. **If you plan to use this project in a commercial environment (which violate the AGPL terms), please contact toby@imuslab.com for an alternative license.**

View File

@ -39,7 +39,7 @@ services:
| `-p (ports)` | Yes | Depending on how your network is setup, you may need to portforward 80, 443, and the management port. |
| `-v (path to storage directory):/opt/zoraxy/config/` | Recommend | Sets the folder that holds your files. This should be the place you just chose. By default, it will create a Docker volume for the files for persistency but they will not be accessible. |
| `-e ARGS='(your arguments)'` | No | Sets the arguments to run Zoraxy with. Enter them as you would normally. By default, it is ran with `-noauth=false` but <b>you cannot change the management port.</b> This is required for the healthcheck to work. |
| `zoraxydocker/zoraxy:latest` | Yes | The repository on Docker hub. By default, it is the latest version that I have published. |
| `zoraxydocker/zoraxy:latest` | Yes | The repository on Docker hub. By default, it is the latest version that is published. |
## Examples: </br>
### Docker Run </br>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 MiB

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m772-635-43-100-104-46 104-45 43-95 43 95 104 45-104 46-43 100Zm0 595-43-96-104-45 104-45 43-101 43 101 104 45-104 45-43 96ZM333-194l-92-197-201-90 201-90 92-196 93 196 200 90-200 90-93 197Zm0-148 48-96 98-43-98-43-48-96-47 96-99 43 99 43 47 96Zm0-139Z"/></svg>
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m772-635-43-100-104-46 104-45 43-95 43 95 104 45-104 46-43 100Zm0 595-43-96-104-45 104-45 43-101 43 101 104 45-104 45-43 96ZM333-194l-92-197-201-90 201-90 92-196 93 196 200 90-200 90-93 197Zm0-148 48-96 98-43-98-43-48-96-47 96-99 43 99 43 47 96Zm0-139Z"/></svg>

Before

Width:  |  Height:  |  Size: 358 B

After

Width:  |  Height:  |  Size: 377 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M280-453h400v-60H280v60ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>
<svg fill="#ff7a7a" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M280-453h400v-60H280v60ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>

Before

Width:  |  Height:  |  Size: 433 B

After

Width:  |  Height:  |  Size: 448 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M320-242 80-482l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z"/></svg>
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M320-242 80-482l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z"/></svg>

Before

Width:  |  Height:  |  Size: 209 B

After

Width:  |  Height:  |  Size: 227 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M120-80v-270h120v-160h210v-100H330v-270h300v270H510v100h210v160h120v270H540v-270h120v-100H300v100h120v270H120Zm270-590h180v-150H390v150ZM180-140h180v-150H180v150Zm420 0h180v-150H600v150ZM480-670ZM360-290Zm240 0Z"/></svg>
<svg fill="#919191" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M120-80v-270h120v-160h210v-100H330v-270h300v270H510v100h210v160h120v270H540v-270h120v-100H300v100h120v270H120Zm270-590h180v-150H390v150ZM180-140h180v-150H180v150Zm420 0h180v-150H600v150ZM480-670ZM360-290Zm240 0Z"/></svg>

Before

Width:  |  Height:  |  Size: 317 B

After

Width:  |  Height:  |  Size: 332 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-180h150v-250h220v250h150v-390L480-765 220-570v390Zm-60 60v-480l320-240 320 240v480H530v-250H430v250H160Zm320-353Z"/></svg>
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-180h150v-250h220v250h150v-390L480-765 220-570v390Zm-60 60v-480l320-240 320 240v480H530v-250H430v250H160Zm320-353Z"/></svg>

Before

Width:  |  Height:  |  Size: 224 B

After

Width:  |  Height:  |  Size: 242 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M356-120H180q-24 0-42-18t-18-42v-176q44-5 75.5-34.5T227-463q0-43-31.5-72.5T120-570v-176q0-24 18-42t42-18h177q11-40 39.5-67t68.5-27q40 0 68.5 27t39.5 67h173q24 0 42 18t18 42v173q40 11 65.5 41.5T897-461q0 40-25.5 67T806-356v176q0 24-18 42t-42 18H570q-5-48-35.5-77.5T463-227q-41 0-71.5 29.5T356-120Zm-176-60h130q25-61 69.888-84 44.888-23 83-23T546-264q45 23 70 84h130v-235h45q20 0 33-13t13-33q0-20-13-33t-33-13h-45v-239H511v-48q0-20-13-33t-33-13q-20 0-33 13t-13 33v48H180v130q48.15 17.817 77.575 59.686Q287-514.445 287-462.777 287-412 257.5-370T180-310v130Zm329-330Z"/></svg>
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M356-120H180q-24 0-42-18t-18-42v-176q44-5 75.5-34.5T227-463q0-43-31.5-72.5T120-570v-176q0-24 18-42t42-18h177q11-40 39.5-67t68.5-27q40 0 68.5 27t39.5 67h173q24 0 42 18t18 42v173q40 11 65.5 41.5T897-461q0 40-25.5 67T806-356v176q0 24-18 42t-42 18H570q-5-48-35.5-77.5T463-227q-41 0-71.5 29.5T356-120Zm-176-60h130q25-61 69.888-84 44.888-23 83-23T546-264q45 23 70 84h130v-235h45q20 0 33-13t13-33q0-20-13-33t-33-13h-45v-239H511v-48q0-20-13-33t-33-13q-20 0-33 13t-13 33v48H180v130q48.15 17.817 77.575 59.686Q287-514.445 287-462.777 287-412 257.5-370T180-310v130Zm329-330Z"/></svg>

Before

Width:  |  Height:  |  Size: 669 B

After

Width:  |  Height:  |  Size: 688 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M273-160 80-353l193-193 42 42-121 121h316v60H194l121 121-42 42Zm414-254-42-42 121-121H450v-60h316L645-758l42-42 193 193-193 193Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48" fill="#fcba03"><path d="M273-160 80-353l193-193 42 42-121 121h316v60H194l121 121-42 42Zm414-254-42-42 121-121H450v-60h316L645-758l42-42 193 193-193 193Z"/></svg>

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 249 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M700-160v-410H275l153 153-42 43-226-226 226-226 42 42-153 154h485v470h-60Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="#0388fc" height="48" viewBox="0 -960 960 960" width="48"><path d="M700-160v-410H275l153 153-42 43-226-226 226-226 42 42-153 154h485v470h-60Z"/></svg>

Before

Width:  |  Height:  |  Size: 180 B

After

Width:  |  Height:  |  Size: 195 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M197-197q-54-54-85.5-126.5T80-480q0-84 31.5-156.5T197-763l43 43q-46 46-73 107.5T140-480q0 71 26.5 132T240-240l-43 43Zm113-113q-32-32-51-75.5T240-480q0-51 19-94.5t51-75.5l43 43q-24 24-38.5 56.5T300-480q0 38 14 70t39 57l-43 43Zm170-90q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm170 90-43-43q24-24 38.5-56.5T660-480q0-38-14-70t-39-57l43-43q32 32 51 75.5t19 94.5q0 50-19 93.5T650-310Zm113 113-43-43q46-46 73-107.5T820-480q0-71-26.5-132T720-720l43-43q54 55 85.5 127.5T880-480q0 83-31.5 155.5T763-197Z"/></svg>
<svg fill="#83f2c4" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M197-197q-54-54-85.5-126.5T80-480q0-84 31.5-156.5T197-763l43 43q-46 46-73 107.5T140-480q0 71 26.5 132T240-240l-43 43Zm113-113q-32-32-51-75.5T240-480q0-51 19-94.5t51-75.5l43 43q-24 24-38.5 56.5T300-480q0 38 14 70t39 57l-43 43Zm170-90q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm170 90-43-43q24-24 38.5-56.5T660-480q0-38-14-70t-39-57l43-43q32 32 51 75.5t19 94.5q0 50-19 93.5T650-310Zm113 113-43-43q46-46 73-107.5T820-480q0-71-26.5-132T720-720l43-43q54 55 85.5 127.5T880-480q0 83-31.5 155.5T763-197Z"/></svg>

Before

Width:  |  Height:  |  Size: 652 B

After

Width:  |  Height:  |  Size: 667 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M345-377h391L609-548 506-413l-68-87-93 123Zm-85 177q-24 0-42-18t-18-42v-560q0-24 18-42t42-18h560q24 0 42 18t18 42v560q0 24-18 42t-42 18H260Zm0-60h560v-560H260v560ZM140-80q-24 0-42-18t-18-42v-620h60v620h620v60H140Zm120-740v560-560Z"/></svg>
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M345-377h391L609-548 506-413l-68-87-93 123Zm-85 177q-24 0-42-18t-18-42v-560q0-24 18-42t42-18h560q24 0 42 18t18 42v560q0 24-18 42t-42 18H260Zm0-60h560v-560H260v560ZM140-80q-24 0-42-18t-18-42v-620h60v620h620v60H140Zm120-740v560-560Z"/></svg>

Before

Width:  |  Height:  |  Size: 336 B

After

Width:  |  Height:  |  Size: 355 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M109.912-150Q81-150 60.5-170.589 40-191.177 40-220.089 40-249 60.494-269.5t49.273-20.5q5.233 0 10.233.5 5 .5 13 2.5l200-200q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q371.177-580 400.089-580 429-580 449.5-559.366t20.5 49.61Q470-508 467-487l110 110q8-2 13-2.5t10-.5q5 0 10 .5t13 2.5l160-160q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q821.177-630 850.089-630 879-630 899.5-609.411q20.5 20.588 20.5 49.5Q920-531 899.506-510.5T850.233-490Q845-490 840-490.5q-5-.5-13-2.5L667-333q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q628.823-240 599.911-240 571-240 550.5-260.494T530-309.767q0-5.233.5-10.233.5-5 2.5-13L423-443q-8 2-13 2.5t-10.25.5q-1.75 0-22.75-3L177-243q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q138.823-150 109.912-150ZM160-592l-20.253-43.747L96-656l43.747-20.253L160-720l20.253 43.747L224-656l-43.747 20.253L160-592Zm440-51-30.717-66.283L503-740l66.283-30.717L600-837l30.717 66.283L697-740l-66.283 30.717L600-643Z"/></svg>
<svg fill="#edf230" xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M109.912-150Q81-150 60.5-170.589 40-191.177 40-220.089 40-249 60.494-269.5t49.273-20.5q5.233 0 10.233.5 5 .5 13 2.5l200-200q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q371.177-580 400.089-580 429-580 449.5-559.366t20.5 49.61Q470-508 467-487l110 110q8-2 13-2.5t10-.5q5 0 10 .5t13 2.5l160-160q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q821.177-630 850.089-630 879-630 899.5-609.411q20.5 20.588 20.5 49.5Q920-531 899.506-510.5T850.233-490Q845-490 840-490.5q-5-.5-13-2.5L667-333q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q628.823-240 599.911-240 571-240 550.5-260.494T530-309.767q0-5.233.5-10.233.5-5 2.5-13L423-443q-8 2-13 2.5t-10.25.5q-1.75 0-22.75-3L177-243q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q138.823-150 109.912-150ZM160-592l-20.253-43.747L96-656l43.747-20.253L160-720l20.253 43.747L224-656l-43.747 20.253L160-592Zm440-51-30.717-66.283L503-740l66.283-30.717L600-837l30.717 66.283L697-740l-66.283 30.717L600-643Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
docs/img/screenshots/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

BIN
docs/img/screenshots/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

BIN
docs/img/screenshots/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/img/screenshots/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/img/screenshots/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

BIN
docs/img/screenshots/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/img/screenshots/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/img/screenshots/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

BIN
docs/img/screenshots/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

BIN
docs/img/screenshots/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

View File

@ -8,7 +8,7 @@
<meta name="author" content="tobychui">
<!-- HTML Meta Tags -->
<title>Cluster Proxy Gateway | Zoraxy</title>
<title>Reverse Proxy Server | Zoraxy</title>
<meta name="description" content="A reverse proxy server and cluster network gateway for noobs">
<!-- Facebook Meta Tags -->
@ -74,21 +74,16 @@
</div>
<div class="right-content">
<!-- Hero Banner Section -->
<div class="dot-container">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
<div class="headbanner"></div>
<div id="home" class="herotext">
<div class="ui basic segment">
<div class="bannerHeaderWrapper">
<h1 class="bannerHeader">Zoraxy</h1>
<div class="ui divider"></div><br>
<p class="bannerSubheader">All in one homelab network routing solution</p>
</div>
<br><br>
<a class="ui black big button" href="#features">Learn More</a>
<a class="ui basic big button" style="background-color: white;" href="#features"><i class="ui blue arrow down icon"></i> Learn More</a>
<br><br>
<table class="ui very basic collapsing unstackable celled table">
<thead>
@ -126,6 +121,22 @@
</tr>
</table>
</div>
<div id="wavesWrapper">
<!-- CSS waves-->
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
<defs>
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
</defs>
<g class="parallax">
<use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(255,255,255,0.7" />
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(255,255,255,0.5)" />
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(255,255,255,0.3)" />
<use xlink:href="#gentle-wave" x="48" y="7" fill="#fff" />
</g>
</svg>
</div>
</div>
<!-- Features -->
@ -148,7 +159,7 @@
Reverse Proxy
</div>
</h3>
<p>Simple to use, noobs friendly reverse proxy server that can be easily set-up using a web form and a few toggle switches.</p>
<p>Simple to use noob-friendly reverse proxy server that can be easily set up using a web form and a few toggle switches.</p>
</div>
<div class="four wide column featureItem">
@ -158,7 +169,7 @@
Redirection
</div>
</h3>
<p>Direct and intuitive redirection rules with basic rewrite options. Suitable for most of the simple use cases.</p>
<p>Direct and intuitive redirection rules with basic rewrite options. Suitable for most simple use cases.</p>
</div>
<div class="four wide column featureItem">
@ -168,7 +179,7 @@
Geo-IP & Blacklist
</div>
</h3>
<p>Blacklist with GeoIP support. Allow easy setup for regional services.</p>
<p>Blacklist with GeoIP support. Allows easy setup for regional services.</p>
</div>
<div class="four wide column featureItem">
@ -189,7 +200,7 @@
Web SSH
</div>
</h3>
<p>Integrated with Gotty Web SSH terminal, allow one-stop management of your nodes inside private LAN via gateway nodes.</p>
<p>Integration with Gotty Web SSH terminal allows one-stop management of your nodes inside private LAN via gateway nodes.</p>
</div>
<div class="four wide column featureItem">
@ -199,7 +210,7 @@
Real Time Statistics
</div>
</h3>
<p>Traffic data collection and real time analytic tools, provide you the best insights of visitors data without cookies.</p>
<p>Traffic data collection and real-time analytic tools provide you the best insight of visitors data without cookies.</p>
</div>
<div class="four wide column featureItem">
@ -209,7 +220,7 @@
Scanner & Utilities
</div>
</h3>
<p>Build in IP scanner and mDNS discovering service, enable automatic service discovery within LAN.</p>
<p>Build in IP scanner and mDNS discovery service to enable automatic service discovery within LAN.</p>
</div>
<div class="four wide column featureItem">
@ -219,7 +230,7 @@
Open Source
</div>
</h3>
<p>Project is open source under AGPL on Github. Feel free to contribute on missing functions you need! </p>
<p>Project is open-source under AGPL on Github. Feel free to contribute on missing functions you need! </p>
</div>
</div>
</div>
@ -240,34 +251,34 @@
<div class="ui three column stackable grid">
<div class="column">
<a href="img/screenshots/1.webp" target="_blank"><img src="img/screenshots/1.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/1.png" target="_blank"><img src="img/screenshots/1.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/2.webp" target="_blank"><img src="img/screenshots/2.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/2.png" target="_blank"><img src="img/screenshots/2.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/3.webp" target="_blank"><img src="img/screenshots/3.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/3.png" target="_blank"><img src="img/screenshots/3.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/4.webp" target="_blank"><img src="img/screenshots/4.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/4.png" target="_blank"><img src="img/screenshots/4.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/5.webp" target="_blank"><img src="img/screenshots/5.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/5.png" target="_blank"><img src="img/screenshots/5.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/6.webp" target="_blank"><img src="img/screenshots/6.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/6.png" target="_blank"><img src="img/screenshots/6.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/7.webp" target="_blank"><img src="img/screenshots/7.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/7.png" target="_blank"><img src="img/screenshots/7.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/8.webp" target="_blank"><img src="img/screenshots/8.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/8.png" target="_blank"><img src="img/screenshots/8.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/9.webp" target="_blank"><img src="img/screenshots/9.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/9.png" target="_blank"><img src="img/screenshots/9.png" class="ui fluid image screenshot"></a>
</div>
<div class="column">
<a href="img/screenshots/10.webp" target="_blank"><img src="img/screenshots/10.webp" class="ui fluid image screenshot"></a>
<a href="img/screenshots/10.png" target="_blank"><img src="img/screenshots/10.png" class="ui fluid image screenshot"></a>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
body{
background: #f6f6f6 !important;
background: #ffffff !important;
margin: 0;
padding: 0;
overflow-y: hidden;
@ -18,7 +18,7 @@ body{
.left-menu {
width: 80px;
min-width: 80px;
background-color: #ffffff;
background-color: #fcfcfc;
min-height: 100vh;
padding-top: 1.5em;
}
@ -48,17 +48,19 @@ body{
text-align: center;
border-bottom: 1px solid #f6f6f6;
width: 100%;
border-right: 0.4em solid var(--themeTextColor);
transition: border-left ease-in-out 0.1s, background-color ease-in-out 0.1s;
}
.menu-item.active{
border-right: 0.4em solid var(--themeSkyblueColorDecondary);
background-color: #f0f8ff;
background: linear-gradient(60deg, rgba(84, 58, 183, 0.3) 0%, rgba(0, 172, 193, 0.3) 100%);
}
.menu-item .item-icon{
fill: #fcfcfc;
}
.menu-item:hover{
border-right: 0.4em solid var(--themeSkyblueColorDecondary);
background: rgba(35,35,35,0.1);
}
.menu-item img{
@ -69,18 +71,6 @@ body{
/* Head banner */
.headbanner{
background-image: url('img/bg.png');
background-repeat: no-repeat;
background-position: right center;
background-size: auto 100%;
position:absolute;
right: 0;
top: 0;
height: 100vh;
width: 100%;
z-index: -100;
}
.herotext{
padding-top: 15em;
@ -91,11 +81,13 @@ body{
.bannerHeader{
font-size: 8em;
font-weight: 600;
color: white;
}
.bannerSubheader{
font-weight: 400;
font-size: 1.2em;
color: #ebebeb;
margin-top: -20px;
}
@ -104,6 +96,21 @@ body{
display: inline-block;
}
#home{
background: linear-gradient(60deg, rgba(84,58,183,1) 0%, rgba(0,172,193,1) 100%);
}
#home .table th, #home .table h4{
color: white;
}
#home .table h4 .content, #home .table h4 .sub.header{
color: white;
}
#home .table td a{
color: #d6ddff;
}
/* features */
#features{
padding-top: 4em;
@ -173,56 +180,58 @@ body{
}
}
/* Decorative Animation */
.dot-container {
display: flex;
justify-content: center;
align-items: center;
height: 40px;
position: absolute;
top: 2em;
left: 2em;
}
.dot {
width: 6px;
height: 6px;
border-radius: 50%;
background-color: #d9d9d9;
margin-right: 6px;
animation-name: dot-animation;
animation-duration: 4s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
/*
Waves CSS
*/
#wavesWrapper{
position: absolute;
bottom: 0;
width: 100%;
left: 0;
}
.dot:nth-child(1) {
animation-delay: 0s;
.waves {
position:relative;
width: 100%;
height:15vh;
margin-bottom:-7px; /*Fix for safari gap*/
min-height:100px;
max-height:150px;
}
.dot:nth-child(2) {
animation-delay: 1s;
}
.dot:nth-child(3) {
animation-delay: 2s;
.parallax > use {
animation: move-forever 25s cubic-bezier(.55,.5,.45,.5) infinite;
}
.dot:nth-child(4) {
animation-delay: 3s;
.parallax > use:nth-child(1) {
animation-delay: -8s;
animation-duration: 28s;
}
@keyframes dot-animation {
0% {
background-color: #d9d9d9;
transform: scale(1);
}
50% {
background-color: #a9d1f3;
transform: scale(1.5);
}
100% {
background-color: #d9d9d9;
transform: scale(1);
}
.parallax > use:nth-child(2) {
animation-delay: -12s;
animation-duration: 40s;
}
.parallax > use:nth-child(3) {
animation-delay: -16s;
animation-duration: 52s;
}
.parallax > use:nth-child(4) {
animation-delay: -20s;
animation-duration: 80s;
}
@keyframes move-forever {
0% {
transform: translate3d(-90px,0,0);
}
100% {
transform: translate3d(85px,0,0);
}
}
/*Shrinking for mobile*/
@media (max-width: 768px) {
.waves {
height:40px;
min-height:40px;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

View File

@ -4,6 +4,8 @@ import (
"encoding/json"
"net/http"
"github.com/microcosm-cc/bluemonday"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/utils"
)
@ -115,7 +117,7 @@ func handleListWhitelisted(w http.ResponseWriter, r *http.Request) {
bltype = "country"
}
resulst := []string{}
resulst := []*geodb.WhitelistEntry{}
if bltype == "country" {
resulst = geodbStore.GetAllWhitelistedCountryCode()
} else if bltype == "ip" {
@ -134,7 +136,11 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
return
}
geodbStore.AddCountryCodeToWhitelist(countryCode)
comment, _ := utils.PostPara(r, "comment")
p := bluemonday.StrictPolicy()
comment = p.Sanitize(comment)
geodbStore.AddCountryCodeToWhitelist(countryCode, comment)
utils.SendOK(w)
}
@ -158,7 +164,11 @@ func handleIpWhitelistAdd(w http.ResponseWriter, r *http.Request) {
return
}
geodbStore.AddIPToWhiteList(ipAddr)
comment, _ := utils.PostPara(r, "comment")
p := bluemonday.StrictPolicy()
comment = p.Sanitize(comment)
geodbStore.AddIPToWhiteList(ipAddr, comment)
}
func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {

View File

@ -103,6 +103,9 @@ func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request)
utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)")
}
//Add a 3 second delay to make sure everything is settle down
time.Sleep(3 * time.Second)
// Pass over to the acmeHandler to deal with the communication
acmeHandler.HandleRenewCertificate(w, r)

View File

@ -47,6 +47,7 @@ func initAPIs() {
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
authRouter.HandleFunc("/api/proxy/toggle", ReverseProxyToggleRuleSet)
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
@ -56,9 +57,16 @@ func initAPIs() {
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
//Reverse proxy root related APIs
authRouter.HandleFunc("/api/proxy/root/listOptions", HandleRootRouteOptionList)
authRouter.HandleFunc("/api/proxy/root/updateOptions", HandleRootRouteOptionsUpdate)
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
//Reverse proxy virtual directory APIs
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
authRouter.HandleFunc("/api/proxy/vdir/del", ReverseProxyDeleteVdir)
authRouter.HandleFunc("/api/proxy/vdir/edit", ReverseProxyEditVdir)
//Reverse proxy user define header apis
authRouter.HandleFunc("/api/proxy/header/list", HandleCustomHeaderList)
authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd)
authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove)
//Reverse proxy auth related APIs
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
@ -77,6 +85,7 @@ func initAPIs() {
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
//Blacklist APIs
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
@ -115,6 +124,8 @@ func initAPIs() {
authRouter.HandleFunc("/api/gan/network/name", ganManager.HandleNetworkNaming)
//authRouter.HandleFunc("/api/gan/network/detail", ganManager.HandleNetworkDetails)
authRouter.HandleFunc("/api/gan/network/setRange", ganManager.HandleSetRanges)
authRouter.HandleFunc("/api/gan/network/join", ganManager.HandleServerJoinNetwork)
authRouter.HandleFunc("/api/gan/network/leave", ganManager.HandleServerLeaveNetwork)
authRouter.HandleFunc("/api/gan/members/list", ganManager.HandleMemberList)
authRouter.HandleFunc("/api/gan/members/ip", ganManager.HandleMemberIP)
authRouter.HandleFunc("/api/gan/members/name", ganManager.HandleMemberNaming)
@ -154,6 +165,8 @@ func initAPIs() {
authRouter.HandleFunc("/api/tools/smtp/set", HandleSMTPSet)
authRouter.HandleFunc("/api/tools/smtp/admin", HandleAdminEmailGet)
authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend)
authRouter.HandleFunc("/api/tools/fwdproxy/enable", forwardProxy.HandleToogle)
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort)
//Account Reset
http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
@ -175,7 +188,7 @@ func initAPIs() {
authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus)
authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer)
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
authRouter.HandleFunc("/api/webserv/setPort", staticWebServer.HandlePortChange)
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
if *allowWebFileManager {
//Web Directory Manager file operation functions

View File

@ -51,7 +51,7 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
results := []*CertInfo{}
for _, filename := range filenames {
certFilepath := filepath.Join(tlsCertManager.CertStore, filename+".crt")
certFilepath := filepath.Join(tlsCertManager.CertStore, filename+".pem")
//keyFilepath := filepath.Join(tlsCertManager.CertStore, filename+".key")
fileInfo, err := os.Stat(certFilepath)
if err != nil {
@ -248,7 +248,7 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) {
}
if keytype == "pub" {
overWriteFilename = domain + ".crt"
overWriteFilename = domain + ".pem"
} else if keytype == "pri" {
overWriteFilename = domain + ".key"
} else {
@ -287,6 +287,9 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) {
return
}
//Update cert list
tlsCertManager.UpdateLoadedCertList()
// send response
fmt.Fprintln(w, "File upload successful!")
}

View File

@ -3,6 +3,7 @@ package main
import (
"archive/zip"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
@ -35,97 +36,118 @@ type Record struct {
BasicAuthExceptionRules []*dynamicproxy.BasicAuthExceptionRule
}
// Save a reverse proxy config record to file
func SaveReverseProxyConfigToFile(proxyConfigRecord *Record) error {
//TODO: Make this accept new def types
os.MkdirAll("./conf/proxy/", 0775)
filename := getFilenameFromRootName(proxyConfigRecord.Rootname)
//Generate record
thisRecord := proxyConfigRecord
//Write to file
js, _ := json.MarshalIndent(thisRecord, "", " ")
return os.WriteFile(filepath.Join("./conf/proxy/", filename), js, 0775)
}
// Save a running reverse proxy endpoint to file (with automatic endpoint to record conversion)
func SaveReverseProxyEndpointToFile(proxyEndpoint *dynamicproxy.ProxyEndpoint) error {
recordToSave, err := ConvertProxyEndpointToRecord(proxyEndpoint)
/*
Load Reverse Proxy Config from file and append it to current runtime proxy router
*/
func LoadReverseProxyConfig(configFilepath string) error {
//Load the config file from disk
endpointConfig, err := os.ReadFile(configFilepath)
if err != nil {
return err
}
return SaveReverseProxyConfigToFile(recordToSave)
}
func RemoveReverseProxyConfigFile(rootname string) error {
filename := getFilenameFromRootName(rootname)
removePendingFile := strings.ReplaceAll(filepath.Join("./conf/proxy/", filename), "\\", "/")
SystemWideLogger.Println("Config Removed: ", removePendingFile)
if utils.FileExists(removePendingFile) {
err := os.Remove(removePendingFile)
if err != nil {
SystemWideLogger.PrintAndLog("Proxy", "Unabel to remove config file", err)
return err
}
//Parse it into dynamic proxy endpoint
thisConfigEndpoint := dynamicproxy.ProxyEndpoint{}
err = json.Unmarshal(endpointConfig, &thisConfigEndpoint)
if err != nil {
return err
}
//File already gone
//Matching domain not set. Assume root
if thisConfigEndpoint.RootOrMatchingDomain == "" {
thisConfigEndpoint.RootOrMatchingDomain = "/"
}
if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyType_Root {
//This is a root config file
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
if err != nil {
return err
}
dynamicProxyRouter.SetProxyRouteAsRoot(rootProxyEndpoint)
} else if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyType_Host {
//This is a host config file
readyProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
if err != nil {
return err
}
dynamicProxyRouter.AddProxyRouteToRuntime(readyProxyEndpoint)
} else {
return errors.New("not supported proxy type")
}
SystemWideLogger.PrintAndLog("Proxy", thisConfigEndpoint.RootOrMatchingDomain+" -> "+thisConfigEndpoint.Domain+" routing rule loaded", nil)
return nil
}
// Return ptype, rootname and proxyTarget, error if any
func LoadReverseProxyConfig(filename string) (*Record, error) {
thisRecord := Record{
ProxyType: "",
Rootname: "",
ProxyTarget: "",
UseTLS: false,
func filterProxyConfigFilename(filename string) string {
//Filter out wildcard characters
filename = strings.ReplaceAll(filename, "*", "(ST)")
filename = strings.ReplaceAll(filename, "?", "(QM)")
filename = strings.ReplaceAll(filename, "[", "(OB)")
filename = strings.ReplaceAll(filename, "]", "(CB)")
filename = strings.ReplaceAll(filename, "#", "(HT)")
return filepath.ToSlash(filename)
}
func SaveReverseProxyConfig(endpoint *dynamicproxy.ProxyEndpoint) error {
//Get filename for saving
filename := filepath.Join("./conf/proxy/", endpoint.RootOrMatchingDomain+".config")
if endpoint.ProxyType == dynamicproxy.ProxyType_Root {
filename = "./conf/proxy/root.config"
}
filename = filterProxyConfigFilename(filename)
//Save config to file
js, err := json.MarshalIndent(endpoint, "", " ")
if err != nil {
return err
}
return os.WriteFile(filename, js, 0775)
}
func RemoveReverseProxyConfig(endpoint string) error {
filename := filepath.Join("./conf/proxy/", endpoint+".config")
if endpoint == "/" {
filename = "./conf/proxy/root.config"
}
filename = filterProxyConfigFilename(filename)
if !utils.FileExists(filename) {
return errors.New("target endpoint not exists")
}
return os.Remove(filename)
}
// Get the default root config that point to the internal static web server
// this will be used if root config is not found (new deployment / missing root.config file)
func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
//Default settings
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{
ProxyType: dynamicproxy.ProxyType_Root,
RootOrMatchingDomain: "/",
Domain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
RequireTLS: false,
BypassGlobalTLS: false,
SkipTlsValidation: false,
SkipCertValidations: false,
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
RequireBasicAuth: false,
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
}
configContent, err := os.ReadFile(filename)
DefaultSiteOption: dynamicproxy.DefaultSite_InternalStaticWebServer,
DefaultSiteValue: "",
})
if err != nil {
return &thisRecord, err
return nil, err
}
//Unmarshal the content into config
err = json.Unmarshal(configContent, &thisRecord)
if err != nil {
return &thisRecord, err
}
//Return it
return &thisRecord, nil
}
// Convert a running proxy endpoint object into a save-able record struct
func ConvertProxyEndpointToRecord(targetProxyEndpoint *dynamicproxy.ProxyEndpoint) (*Record, error) {
thisProxyConfigRecord := Record{
ProxyType: targetProxyEndpoint.GetProxyTypeString(),
Rootname: targetProxyEndpoint.RootOrMatchingDomain,
ProxyTarget: targetProxyEndpoint.Domain,
UseTLS: targetProxyEndpoint.RequireTLS,
BypassGlobalTLS: targetProxyEndpoint.BypassGlobalTLS,
SkipTlsValidation: targetProxyEndpoint.SkipCertValidations,
RequireBasicAuth: targetProxyEndpoint.RequireBasicAuth,
BasicAuthCredentials: targetProxyEndpoint.BasicAuthCredentials,
BasicAuthExceptionRules: targetProxyEndpoint.BasicAuthExceptionRules,
}
return &thisProxyConfigRecord, nil
}
func getFilenameFromRootName(rootname string) string {
//Generate a filename for this rootname
filename := strings.ReplaceAll(rootname, ".", "_")
filename = strings.ReplaceAll(filename, "/", "-")
filename = filename + ".config"
return filename
return rootProxyEndpoint, nil
}
/*

View File

@ -1,18 +1,34 @@
module imuslab.com/zoraxy
go 1.16
go 1.21
toolchain go1.22.2
require (
github.com/boltdb/bolt v1.3.1
github.com/go-acme/lego/v4 v4.14.0
github.com/go-acme/lego/v4 v4.16.1
github.com/go-ping/ping v1.1.0
github.com/google/uuid v1.3.1
github.com/gorilla/sessions v1.2.1
github.com/gorilla/websocket v1.5.0
github.com/google/uuid v1.6.0
github.com/gorilla/sessions v1.2.2
github.com/gorilla/websocket v1.5.1
github.com/grandcat/zeroconf v1.0.0
github.com/likexian/whois v1.15.1
github.com/microcosm-cc/bluemonday v1.0.25
golang.org/x/net v0.14.0
golang.org/x/sys v0.11.0
golang.org/x/tools v0.12.0 // indirect
github.com/microcosm-cc/bluemonday v1.0.26
golang.org/x/net v0.23.0
golang.org/x/sys v0.18.0
golang.org/x/text v0.14.0
)
require (
github.com/aymerick/douceur v0.2.0 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/miekg/dns v1.1.58 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/tools v0.19.0 // indirect
)

1805
src/go.sum

File diff suppressed because it is too large Load Diff

View File

@ -13,11 +13,11 @@ import (
"github.com/google/uuid"
"imuslab.com/zoraxy/mod/acme"
"imuslab.com/zoraxy/mod/aroz"
"imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
"imuslab.com/zoraxy/mod/email"
"imuslab.com/zoraxy/mod/forwardproxy"
"imuslab.com/zoraxy/mod/ganserv"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
@ -35,6 +35,7 @@ import (
)
// General flags
var webUIPort = flag.String("port", ":8000", "Management web interface listening port")
var noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
var showver = flag.Bool("version", false, "Show version of this server")
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
@ -49,7 +50,7 @@ var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
var (
name = "Zoraxy"
version = "2.6.8"
version = "3.0.1"
nodeUUID = "generic"
development = false //Set this to false to use embedded web fs
bootTime = time.Now().Unix()
@ -63,7 +64,6 @@ var (
/*
Handler Modules
*/
handler *aroz.ArozHandler //Handle arozos managed permission system
sysdb *database.Database //System database
authAgent *auth.AuthAgent //Authentication agent
tlsCertManager *tlscert.Manager //TLS / SSL management
@ -80,6 +80,7 @@ var (
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
//Helper modules
EmailSender *email.Sender //Email sender that handle email sending
@ -128,20 +129,8 @@ func ShutdownSeq() {
}
func main() {
//Start the aoModule pipeline (which will parse the flags as well). Pass in the module launch information
handler = aroz.HandleFlagParse(aroz.ServiceInfo{
Name: name,
Desc: "Dynamic Reverse Proxy Server",
Group: "Network",
IconPath: "zoraxy/img/small_icon.png",
Version: version,
StartDir: "zoraxy/index.html",
SupportFW: true,
LaunchFWDir: "zoraxy/index.html",
SupportEmb: false,
InitFWSize: []int{1080, 580},
})
//Parse startup flags
flag.Parse()
if *showver {
fmt.Println(name + " - Version " + version)
os.Exit(0)
@ -166,7 +155,7 @@ func main() {
startupSequence()
//Initiate management interface APIs
requireAuth = !(*noauth || handler.IsUsingExternalPermissionManager())
requireAuth = !(*noauth)
initAPIs()
//Start the reverse proxy server in go routine
@ -179,8 +168,8 @@ func main() {
//Start the finalize sequences
finalSequence()
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + handler.Port)
err = http.ListenAndServe(handler.Port, nil)
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + *webUIPort)
err = http.ListenAndServe(*webUIPort, nil)
if err != nil {
log.Fatal(err)

View File

@ -163,7 +163,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
// Each certificate comes back with the cert bytes, the bytes of the client's
// private key, and a certificate URL.
err = os.WriteFile("./conf/certs/"+certificateName+".crt", certificates.Certificate, 0777)
err = os.WriteFile("./conf/certs/"+certificateName+".pem", certificates.Certificate, 0777)
if err != nil {
log.Println(err)
return false, err

View File

@ -1,9 +1,6 @@
package dynamicproxy
import (
_ "embed"
"errors"
"log"
"net/http"
"net/url"
"os"
@ -26,16 +23,10 @@ import (
- Vitrual Directory Routing
*/
var (
//go:embed tld.json
rawTldMap []byte
)
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
/*
Special Routing Rules, bypass most of the limitations
*/
//Check if there are external routing rule matches.
//If yes, route them via external rr
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
@ -53,10 +44,12 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
//Inject headers
w.Header().Set("x-proxy-by", "zoraxy/"+h.Parent.Option.HostVersion)
/*
General Access Check
*/
respWritten := h.handleAccessRouting(w, r)
if respWritten {
return
@ -80,38 +73,45 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
/*
Subdomain Routing
Host Routing
*/
if strings.Contains(r.Host, ".") {
//This might be a subdomain. See if there are any subdomain proxy router for this
sep := h.Parent.getSubdomainProxyEndpointFromHostname(domainOnly)
if sep != nil {
if sep.RequireBasicAuth {
err := h.handleBasicAuthRouting(w, r, sep)
if err != nil {
return
}
}
h.subdomainRequest(w, r, sep)
return
}
}
/*
Virtual Directory Routing
*/
//Clean up the request URI
proxyingPath := strings.TrimSpace(r.RequestURI)
targetProxyEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath)
if targetProxyEndpoint != nil {
if targetProxyEndpoint.RequireBasicAuth {
err := h.handleBasicAuthRouting(w, r, targetProxyEndpoint)
sep := h.Parent.getProxyEndpointFromHostname(domainOnly)
if sep != nil && !sep.Disabled {
if sep.RequireBasicAuth {
err := h.handleBasicAuthRouting(w, r, sep)
if err != nil {
return
}
}
h.proxyRequest(w, r, targetProxyEndpoint)
} else if !strings.HasSuffix(proxyingPath, "/") {
//Check if any virtual directory rules matches
proxyingPath := strings.TrimSpace(r.RequestURI)
targetProxyEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath)
if targetProxyEndpoint != nil && !targetProxyEndpoint.Disabled {
//Virtual directory routing rule found. Route via vdir mode
h.vdirRequest(w, r, targetProxyEndpoint)
return
} else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyType_Root {
potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled {
//Missing tailing slash. Redirect to target proxy endpoint
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
return
}
}
//Fallback to handle by the host proxy forwarder
h.hostRequest(w, r, sep)
return
}
/*
Root Router Handling
*/
//Clean up the request URI
proxyingPath := strings.TrimSpace(r.RequestURI)
if !strings.HasSuffix(proxyingPath, "/") {
potentialProxtEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath + "/")
if potentialProxtEndpoint != nil {
//Missing tailing slash. Redirect to target proxy endpoint
@ -136,52 +136,71 @@ Once entered this routing segment, the root routing options will take over
for the routing logic.
*/
func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) {
domainOnly := r.Host
if strings.Contains(r.Host, ":") {
hostPath := strings.Split(r.Host, ":")
domainOnly = hostPath[0]
}
if h.Parent.RootRoutingOptions.EnableRedirectForUnsetRules {
//Route to custom domain
if h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget == "" {
//Not set. Redirect to first level of domain redirectable
fld, err := h.getTopLevelRedirectableDomain(domainOnly)
if err != nil {
//Redirect to proxy root
h.proxyRequest(w, r, h.Parent.Root)
} else {
log.Println("[Router] Redirecting request from " + domainOnly + " to " + fld)
h.logRequest(r, false, 307, "root-redirect", domainOnly)
http.Redirect(w, r, fld, http.StatusTemporaryRedirect)
}
//Get the proxy root config
proot := h.Parent.Root
switch proot.DefaultSiteOption {
case DefaultSite_InternalStaticWebServer:
fallthrough
case DefaultSite_ReverseProxy:
//They both share the same behavior
//Check if any virtual directory rules matches
proxyingPath := strings.TrimSpace(r.RequestURI)
targetProxyEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath)
if targetProxyEndpoint != nil && !targetProxyEndpoint.Disabled {
//Virtual directory routing rule found. Route via vdir mode
h.vdirRequest(w, r, targetProxyEndpoint)
return
} else if h.isTopLevelRedirectableDomain(domainOnly) {
//This is requesting a top level private domain that should be serving root
h.proxyRequest(w, r, h.Parent.Root)
} else {
//Validate the redirection target URL
parsedURL, err := url.Parse(h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget)
if err != nil {
//Error when parsing target. Send to root
h.proxyRequest(w, r, h.Parent.Root)
} else if !strings.HasSuffix(proxyingPath, "/") && proot.ProxyType != ProxyType_Root {
potentialProxtEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled {
//Missing tailing slash. Redirect to target proxy endpoint
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
return
}
hostname := parsedURL.Hostname()
if domainOnly != hostname {
//Redirect to target
h.logRequest(r, false, 307, "root-redirect", domainOnly)
http.Redirect(w, r, h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget, http.StatusTemporaryRedirect)
return
} else {
//Loopback request due to bad settings (Shd leave it empty)
//Forward it to root proxy
h.proxyRequest(w, r, h.Parent.Root)
}
}
} else {
//Route to root
h.proxyRequest(w, r, h.Parent.Root)
//No vdir match. Route via root router
h.hostRequest(w, r, h.Parent.Root)
case DefaultSite_Redirect:
redirectTarget := strings.TrimSpace(proot.DefaultSiteValue)
if redirectTarget == "" {
redirectTarget = "about:blank"
}
//Check if it is an infinite loopback redirect
parsedURL, err := url.Parse(proot.DefaultSiteValue)
if err != nil {
//Error when parsing target. Send to root
h.hostRequest(w, r, h.Parent.Root)
return
}
hostname := parsedURL.Hostname()
if hostname == domainOnly {
h.logRequest(r, false, 500, "root-redirect", domainOnly)
http.Error(w, "Loopback redirects due to invalid settings", 500)
return
}
h.logRequest(r, false, 307, "root-redirect", domainOnly)
http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
case DefaultSite_NotFoundPage:
//Serve the not found page, use template if exists
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusNotFound)
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/notfound.html"))
if err != nil {
w.Write(page_hosterror)
} else {
w.Write(template)
}
}
}
@ -219,44 +238,3 @@ func (h *ProxyHandler) handleAccessRouting(w http.ResponseWriter, r *http.Reques
return false
}
// Return if the given host is already topped (e.g. example.com or example.co.uk) instead of
// a host with subdomain (e.g. test.example.com)
func (h *ProxyHandler) isTopLevelRedirectableDomain(requestHost string) bool {
parts := strings.Split(requestHost, ".")
if len(parts) > 2 {
//Cases where strange tld is used like .co.uk or .com.hk
_, ok := h.Parent.tldMap[strings.Join(parts[1:], ".")]
if ok {
//Already topped
return true
}
} else {
//Already topped
return true
}
return false
}
// GetTopLevelRedirectableDomain returns the toppest level of domain
// that is redirectable. E.g. a.b.c.example.co.uk will return example.co.uk
func (h *ProxyHandler) getTopLevelRedirectableDomain(unsetSubdomainHost string) (string, error) {
parts := strings.Split(unsetSubdomainHost, ".")
if h.isTopLevelRedirectableDomain(unsetSubdomainHost) {
//Already topped
return "", errors.New("already at top level domain")
}
for i := 0; i < len(parts); i++ {
possibleTld := parts[i:]
_, ok := h.Parent.tldMap[strings.Join(possibleTld, ".")]
if ok {
//This is tld length
tld := strings.Join(parts[i-1:], ".")
return "//" + tld, nil
}
}
return "", errors.New("unsupported top level domain given")
}

View File

@ -26,10 +26,6 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req
}
}
proxyType := "vdir-auth"
if pe.ProxyType == ProxyType_Subdomain {
proxyType = "subd-auth"
}
u, p, ok := r.BasicAuth()
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
@ -48,7 +44,7 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req
}
if !matchingFound {
h.logRequest(r, false, 401, proxyType, pe.Domain)
h.logRequest(r, false, 401, "host", pe.Domain)
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
w.WriteHeader(401)
return errors.New("unauthorized")

View File

@ -1,6 +1,7 @@
package dpcore
import (
"context"
"errors"
"io"
"log"
@ -8,12 +9,9 @@ import (
"net/http"
"net/url"
"strings"
"sync"
"time"
)
var onExitFlushLoop func()
// ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the
// client, support http, also support https tunnel using http.hijacker
@ -60,6 +58,7 @@ type ResponseRewriteRuleSet struct {
ProxyDomain string
OriginalHost string
UseTLS bool
NoCache bool
PathPrefix string //Vdir prefix for root, / will be rewrite to this
}
@ -67,7 +66,12 @@ type requestCanceler interface {
CancelRequest(req *http.Request)
}
func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerification bool) *ReverseProxy {
type DpcoreOptions struct {
IgnoreTLSVerification bool
FlushInterval time.Duration
}
func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOptions) *ReverseProxy {
targetQuery := target.RawQuery
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
@ -79,10 +83,6 @@ func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerificatio
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
if _, ok := req.Header["User-Agent"]; !ok {
req.Header.Set("User-Agent", "")
}
}
//Hack the default transporter to handle more connections
@ -94,16 +94,17 @@ func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerificatio
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
thisTransporter.(*http.Transport).DisableCompression = true
if ignoreTLSVerification {
if dpcOptions.IgnoreTLSVerification {
//Ignore TLS certificate validation error
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
}
return &ReverseProxy{
Director: director,
Prepender: prepender,
Verbal: false,
Transport: thisTransporter,
Director: director,
Prepender: prepender,
FlushInterval: dpcOptions.FlushInterval,
Verbal: false,
Transport: thisTransporter,
}
}
@ -177,64 +178,66 @@ var hopHeaders = []string{
//"Upgrade",
}
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
if p.FlushInterval != 0 {
if wf, ok := dst.(writeFlusher); ok {
mlw := &maxLatencyWriter{
dst: wf,
latency: p.FlushInterval,
done: make(chan bool),
}
go mlw.flushLoop()
defer mlw.stop()
dst = mlw
// Copy response from src to dst with given flush interval, reference from httputil.ReverseProxy
func (p *ReverseProxy) copyResponse(dst http.ResponseWriter, src io.Reader, flushInterval time.Duration) error {
var w io.Writer = dst
if flushInterval != 0 {
mlw := &maxLatencyWriter{
dst: dst,
flush: http.NewResponseController(dst).Flush,
latency: flushInterval,
}
defer mlw.stop()
// set up initial timer so headers get flushed even if body writes are delayed
mlw.flushPending = true
mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
w = mlw
}
io.Copy(dst, src)
var buf []byte
_, err := p.copyBuffer(w, src, buf)
return err
}
type writeFlusher interface {
io.Writer
http.Flusher
}
// Copy with given buffer size. Default to 64k
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
if len(buf) == 0 {
buf = make([]byte, 64*1024)
}
type maxLatencyWriter struct {
dst writeFlusher
latency time.Duration
mu sync.Mutex
done chan bool
}
func (m *maxLatencyWriter) Write(b []byte) (int, error) {
m.mu.Lock()
defer m.mu.Unlock()
return m.dst.Write(b)
}
func (m *maxLatencyWriter) flushLoop() {
t := time.NewTicker(m.latency)
defer t.Stop()
var written int64
for {
select {
case <-m.done:
if onExitFlushLoop != nil {
onExitFlushLoop()
nr, rerr := src.Read(buf)
if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
p.logf("dpcore read error during body copy: %v", rerr)
}
if nr > 0 {
nw, werr := dst.Write(buf[:nr])
if nw > 0 {
written += int64(nw)
}
return
case <-t.C:
m.mu.Lock()
m.dst.Flush()
m.mu.Unlock()
if werr != nil {
return written, werr
}
if nr != nw {
return written, io.ErrShortWrite
}
}
if rerr != nil {
if rerr == io.EOF {
rerr = nil
}
return written, rerr
}
}
}
func (m *maxLatencyWriter) stop() {
m.done <- true
}
func (p *ReverseProxy) logf(format string, args ...interface{}) {
if p.ErrorLog != nil {
p.ErrorLog.Printf(format, args...)
@ -243,7 +246,7 @@ func (p *ReverseProxy) logf(format string, args ...interface{}) {
}
}
func removeHeaders(header http.Header) {
func removeHeaders(header http.Header, noCache bool) {
// Remove hop-by-hop headers listed in the "Connection" header.
if c := header.Get("Connection"); c != "" {
for _, f := range strings.Split(c, ",") {
@ -260,10 +263,25 @@ func removeHeaders(header http.Header) {
}
}
if header.Get("A-Upgrade") != "" {
header.Set("Upgrade", header.Get("A-Upgrade"))
header.Del("A-Upgrade")
//Restore the Upgrade header if any
if header.Get("Zr-Origin-Upgrade") != "" {
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
header.Del("Zr-Origin-Upgrade")
}
//Disable cache if nocache is set
if noCache {
header.Del("Cache-Control")
header.Set("Cache-Control", "no-store")
}
//Hide Go-HTTP-Client UA if the client didnt sent us one
if _, ok := header["User-Agent"]; !ok {
// If the outbound request doesn't have a User-Agent header set,
// don't send the default Go HTTP client User-Agent.
header.Set("User-Agent", "")
}
}
func addXForwardedForHeader(req *http.Request) {
@ -281,6 +299,22 @@ func addXForwardedForHeader(req *http.Request) {
req.Header.Set("X-Forwarded-Proto", "http")
}
if req.Header.Get("X-Real-Ip") == "" {
//Check if CF-Connecting-IP header exists
CF_Connecting_IP := req.Header.Get("CF-Connecting-IP")
if CF_Connecting_IP != "" {
//Use CF Connecting IP
req.Header.Set("X-Real-Ip", CF_Connecting_IP)
} else {
// Not exists. Fill it in with first entry in X-Forwarded-For
ips := strings.Split(clientIP, ",")
if len(ips) > 0 {
req.Header.Set("X-Real-Ip", strings.TrimSpace(ips[0]))
}
}
}
}
}
@ -323,7 +357,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
copyHeader(outreq.Header, req.Header)
// Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
removeHeaders(outreq.Header)
removeHeaders(outreq.Header, rrr.NoCache)
// Add X-Forwarded-For Header.
addXForwardedForHeader(outreq)
@ -339,7 +373,13 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
}
// Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers.
removeHeaders(res.Header)
removeHeaders(res.Header, rrr.NoCache)
//Remove the User-Agent header if exists
if _, ok := res.Header["User-Agent"]; ok {
//Server to client request should not contains a User-Agent header
res.Header.Del("User-Agent")
}
if p.ModifyResponse != nil {
if err := p.ModifyResponse(res); err != nil {
@ -352,6 +392,12 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
}
}
//if res.StatusCode == 501 || res.StatusCode == 500 {
// fmt.Println(outreq.Proto, outreq.RemoteAddr, outreq.RequestURI)
// fmt.Println(">>>", outreq.Method, res.Header, res.ContentLength, res.StatusCode)
// fmt.Println(outreq.Header, req.Host)
//}
//Custom header rewriter functions
if res.Header.Get("Location") != "" {
locationRewrite := res.Header.Get("Location")
@ -400,7 +446,10 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
}
}
p.copyResponse(rw, res.Body)
//Get flush interval in real time and start copying the request
flushInterval := p.getFlushInterval(req, res)
p.copyResponse(rw, res.Body, flushInterval)
// close now, instead of defer, to populate res.Trailer
res.Body.Close()
copyHeader(rw.Header(), res.Trailer)

View File

@ -0,0 +1,38 @@
package dpcore
import (
"mime"
"net/http"
"time"
)
// Auto sniff of flush interval from header
func (p *ReverseProxy) getFlushInterval(req *http.Request, res *http.Response) time.Duration {
contentType := req.Header.Get("Content-Type")
if actualContentType, _, _ := mime.ParseMediaType(contentType); actualContentType == "text/event-stream" {
return -1
}
if req.ContentLength == -1 || p.isBidirectionalStream(req, res) {
return -1
}
//Cannot sniff anything. Use default value
return p.FlushInterval
}
// Check for bidirectional stream, copy from Caddy :D
func (p *ReverseProxy) isBidirectionalStream(req *http.Request, res *http.Response) bool {
// We have to check the encoding here; only flush headers with identity encoding.
// Non-identity encoding might combine with "encode" directive, and in that case,
// if body size larger than enc.MinLength, upper level encode handle might have
// Content-Encoding header to write.
// (see https://github.com/caddyserver/caddy/issues/3606 for use case)
ae := req.Header.Get("Accept-Encoding")
return req.ProtoMajor == 2 &&
res.ProtoMajor == 2 &&
res.ContentLength == -1 &&
(ae == "identity" || ae == "")
}

View File

@ -0,0 +1,73 @@
package dpcore
/*
Max Latency Writer
This script implements a io writer with periodic flushing base on a ticker
Mostly based on httputil.ReverseProxy
*/
import (
"io"
"sync"
"time"
)
type maxLatencyWriter struct {
dst io.Writer
flush func() error
latency time.Duration // non-zero; negative means to flush immediately
mu sync.Mutex // protects t, flushPending, and dst.Flush
t *time.Timer
flushPending bool
}
func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
m.mu.Lock()
defer m.mu.Unlock()
n, err = m.dst.Write(p)
if m.latency < 0 {
//Flush immediately
m.flush()
return
}
if m.flushPending {
//Flush in next tick cycle
return
}
if m.t == nil {
m.t = time.AfterFunc(m.latency, m.delayedFlush)
} else {
m.t.Reset(m.latency)
}
m.flushPending = true
return
}
func (m *maxLatencyWriter) delayedFlush() {
m.mu.Lock()
defer m.mu.Unlock()
if !m.flushPending {
// if stop was called but AfterFunc already started this goroutine
return
}
m.flush()
m.flushPending = false
}
func (m *maxLatencyWriter) stop() {
m.mu.Lock()
defer m.mu.Unlock()
m.flushPending = false
if m.t != nil {
m.t.Stop()
}
}

View File

@ -22,27 +22,19 @@ import (
func NewDynamicProxy(option RouterOption) (*Router, error) {
proxyMap := sync.Map{}
domainMap := sync.Map{}
thisRouter := Router{
Option: &option,
ProxyEndpoints: &proxyMap,
SubdomainEndpoint: &domainMap,
Running: false,
server: nil,
routingRules: []*RoutingRule{},
tldMap: map[string]int{},
Option: &option,
ProxyEndpoints: &proxyMap,
Running: false,
server: nil,
routingRules: []*RoutingRule{},
tldMap: map[string]int{},
}
thisRouter.mux = &ProxyHandler{
Parent: &thisRouter,
}
//Prase the tld map for tld redirection in main router
//See Server.go declarations
if len(rawTldMap) > 0 {
json.Unmarshal(rawTldMap, &thisRouter.tldMap)
}
return &thisRouter, nil
}
@ -76,21 +68,14 @@ func (router *Router) UpdateHttpToHttpsRedirectSetting(useRedirect bool) {
func (router *Router) StartProxyService() error {
//Create a new server object
if router.server != nil {
return errors.New("Reverse proxy server already running")
return errors.New("reverse proxy server already running")
}
//Check if root route is set
if router.Root == nil {
return errors.New("Reverse proxy router root not set")
return errors.New("reverse proxy router root not set")
}
//Load root options from file
loadedRootOption, err := loadRootRoutingOptionsFromFile()
if err != nil {
return err
}
router.RootRoutingOptions = loadedRootOption
minVersion := tls.VersionTLS10
if router.Option.ForceTLSLatest {
minVersion = tls.VersionTLS12
@ -101,16 +86,6 @@ func (router *Router) StartProxyService() error {
}
if router.Option.UseTls {
/*
//Serve with TLS mode
ln, err := tls.Listen("tcp", ":"+strconv.Itoa(router.Option.Port), config)
if err != nil {
log.Println(err)
router.Running = false
return err
}
router.tlsListener = ln
*/
router.server = &http.Server{
Addr: ":" + strconv.Itoa(router.Option.Port),
Handler: router.mux,
@ -129,7 +104,7 @@ func (router *Router) StartProxyService() error {
hostPath := strings.Split(r.Host, ":")
domainOnly = hostPath[0]
}
sep := router.getSubdomainProxyEndpointFromHostname(domainOnly)
sep := router.getProxyEndpointFromHostname(domainOnly)
if sep != nil && sep.BypassGlobalTLS {
//Allow routing via non-TLS handler
originalHostHeader := r.Host
@ -140,7 +115,7 @@ func (router *Router) StartProxyService() error {
r.URL, _ = url.Parse(originalHostHeader)
}
sep.Proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: sep.Domain,
OriginalHost: originalHostHeader,
UseTLS: sep.RequireTLS,
@ -225,7 +200,7 @@ func (router *Router) StartProxyService() error {
func (router *Router) StopProxyService() error {
if router.server == nil {
return errors.New("Reverse proxy server already stopped")
return errors.New("reverse proxy server already stopped")
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
@ -253,13 +228,13 @@ func (router *Router) StopProxyService() error {
// Restart the current router if it is running.
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
}
time.Sleep(300 * time.Millisecond)
// Start the server
err = router.StartProxyService()
if err != nil {
@ -267,7 +242,7 @@ func (router *Router) Restart() error {
}
}
return err
return nil
}
/*
@ -280,128 +255,17 @@ func (router *Router) IsProxiedSubdomain(r *http.Request) bool {
hostname = r.Host
}
hostname = strings.Split(hostname, ":")[0]
subdEndpoint := router.getSubdomainProxyEndpointFromHostname(hostname)
subdEndpoint := router.getProxyEndpointFromHostname(hostname)
return subdEndpoint != nil
}
/*
Add an URL into a custom proxy services
*/
func (router *Router) AddVirtualDirectoryProxyService(options *VdirOptions) error {
domain := options.Domain
if domain[len(domain)-1:] == "/" {
domain = domain[:len(domain)-1]
}
/*
if rootname[len(rootname)-1:] == "/" {
rootname = rootname[:len(rootname)-1]
}
*/
webProxyEndpoint := domain
if options.RequireTLS {
webProxyEndpoint = "https://" + webProxyEndpoint
} else {
webProxyEndpoint = "http://" + webProxyEndpoint
}
//Create a new proxy agent for this root
path, err := url.Parse(webProxyEndpoint)
if err != nil {
return err
}
proxy := dpcore.NewDynamicProxyCore(path, options.RootName, options.SkipCertValidations)
endpointObject := ProxyEndpoint{
ProxyType: ProxyType_Vdir,
RootOrMatchingDomain: options.RootName,
Domain: domain,
RequireTLS: options.RequireTLS,
SkipCertValidations: options.SkipCertValidations,
RequireBasicAuth: options.RequireBasicAuth,
BasicAuthCredentials: options.BasicAuthCredentials,
BasicAuthExceptionRules: options.BasicAuthExceptionRules,
Proxy: proxy,
}
router.ProxyEndpoints.Store(options.RootName, &endpointObject)
log.Println("Registered Proxy Rule: ", options.RootName+" to "+domain)
return nil
}
/*
Load routing from RP
*/
func (router *Router) LoadProxy(ptype string, key string) (*ProxyEndpoint, error) {
if ptype == "vdir" {
proxy, ok := router.ProxyEndpoints.Load(key)
if !ok {
return nil, errors.New("target proxy not found")
}
targetProxy := proxy.(*ProxyEndpoint)
targetProxy.parent = router
return targetProxy, nil
} else if ptype == "subd" {
proxy, ok := router.SubdomainEndpoint.Load(key)
if !ok {
return nil, errors.New("target proxy not found")
}
targetProxy := proxy.(*ProxyEndpoint)
targetProxy.parent = router
return targetProxy, nil
}
return nil, errors.New("unsupported ptype")
}
/*
Add an default router for the proxy server
*/
func (router *Router) SetRootProxy(options *RootOptions) error {
proxyLocation := options.ProxyLocation
if proxyLocation[len(proxyLocation)-1:] == "/" {
proxyLocation = proxyLocation[:len(proxyLocation)-1]
}
webProxyEndpoint := proxyLocation
if options.RequireTLS {
webProxyEndpoint = "https://" + webProxyEndpoint
} else {
webProxyEndpoint = "http://" + webProxyEndpoint
}
//Create a new proxy agent for this root
path, err := url.Parse(webProxyEndpoint)
if err != nil {
return err
}
proxy := dpcore.NewDynamicProxyCore(path, "", options.SkipCertValidations)
rootEndpoint := ProxyEndpoint{
ProxyType: ProxyType_Vdir,
RootOrMatchingDomain: "/",
Domain: proxyLocation,
RequireTLS: options.RequireTLS,
SkipCertValidations: options.SkipCertValidations,
RequireBasicAuth: options.RequireBasicAuth,
BasicAuthCredentials: options.BasicAuthCredentials,
BasicAuthExceptionRules: options.BasicAuthExceptionRules,
Proxy: proxy,
}
router.Root = &rootEndpoint
return nil
}
// Helpers to export the syncmap for easier processing
func (r *Router) GetSDProxyEndpointsAsMap() map[string]*ProxyEndpoint {
m := make(map[string]*ProxyEndpoint)
r.SubdomainEndpoint.Range(func(key, value interface{}) bool {
k, ok := key.(string)
func (router *Router) LoadProxy(matchingDomain string) (*ProxyEndpoint, error) {
var targetProxyEndpoint *ProxyEndpoint
router.ProxyEndpoints.Range(func(key, value interface{}) bool {
key, ok := key.(string)
if !ok {
return true
}
@ -409,13 +273,32 @@ func (r *Router) GetSDProxyEndpointsAsMap() map[string]*ProxyEndpoint {
if !ok {
return true
}
m[k] = v
if key == matchingDomain {
targetProxyEndpoint = v
}
return true
})
return m
if targetProxyEndpoint == nil {
return nil, errors.New("target routing rule not found")
}
return targetProxyEndpoint, nil
}
func (r *Router) GetVDProxyEndpointsAsMap() map[string]*ProxyEndpoint {
// Deep copy a proxy endpoint, excluding runtime paramters
func CopyEndpoint(endpoint *ProxyEndpoint) *ProxyEndpoint {
js, _ := json.Marshal(endpoint)
newProxyEndpoint := ProxyEndpoint{}
err := json.Unmarshal(js, &newProxyEndpoint)
if err != nil {
return nil
}
return &newProxyEndpoint
}
func (r *Router) GetProxyEndpointsAsMap() map[string]*ProxyEndpoint {
m := make(map[string]*ProxyEndpoint)
r.ProxyEndpoints.Range(func(key, value interface{}) bool {
k, ok := key.(string)

View File

@ -0,0 +1,158 @@
package dynamicproxy
import (
"encoding/json"
"errors"
"strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
/*
endpoint.go
author: tobychui
This script handle the proxy endpoint object actions
so proxyEndpoint can be handled like a proper oop object
Most of the functions are implemented in dynamicproxy.go
*/
/*
User Defined Header Functions
*/
// Check if a user define header exists in this endpoint, ignore case
func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
for _, header := range ep.UserDefinedHeaders {
if strings.EqualFold(header.Key, key) {
return true
}
}
return false
}
// Remvoe a user defined header from the list
func (ep *ProxyEndpoint) RemoveUserDefinedHeader(key string) error {
newHeaderList := []*UserDefinedHeader{}
for _, header := range ep.UserDefinedHeaders {
if !strings.EqualFold(header.Key, key) {
newHeaderList = append(newHeaderList, header)
}
}
ep.UserDefinedHeaders = newHeaderList
return nil
}
// Add a user defined header to the list, duplicates will be automatically removed
func (ep *ProxyEndpoint) AddUserDefinedHeader(key string, value string) error {
if ep.UserDefinedHeaderExists(key) {
ep.RemoveUserDefinedHeader(key)
}
ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, &UserDefinedHeader{
Key: cases.Title(language.Und, cases.NoLower).String(key), //e.g. x-proxy-by -> X-Proxy-By
Value: value,
})
return nil
}
/*
Virtual Directory Functions
*/
// Get virtual directory handler from given URI
func (ep *ProxyEndpoint) GetVirtualDirectoryHandlerFromRequestURI(requestURI string) *VirtualDirectoryEndpoint {
for _, vdir := range ep.VirtualDirectories {
if strings.HasPrefix(requestURI, vdir.MatchingPath) {
return vdir
}
}
return nil
}
// Get virtual directory handler by matching path (exact match required)
func (ep *ProxyEndpoint) GetVirtualDirectoryRuleByMatchingPath(matchingPath string) *VirtualDirectoryEndpoint {
for _, vdir := range ep.VirtualDirectories {
if vdir.MatchingPath == matchingPath {
return vdir
}
}
return nil
}
// Delete a vdir rule by its matching path
func (ep *ProxyEndpoint) RemoveVirtualDirectoryRuleByMatchingPath(matchingPath string) error {
entryFound := false
newVirtualDirectoryList := []*VirtualDirectoryEndpoint{}
for _, vdir := range ep.VirtualDirectories {
if vdir.MatchingPath == matchingPath {
entryFound = true
} else {
newVirtualDirectoryList = append(newVirtualDirectoryList, vdir)
}
}
if entryFound {
//Update the list of vdirs
ep.VirtualDirectories = newVirtualDirectoryList
return nil
}
return errors.New("target virtual directory routing rule not found")
}
// Delete a vdir rule by its matching path
func (ep *ProxyEndpoint) AddVirtualDirectoryRule(vdir *VirtualDirectoryEndpoint) (*ProxyEndpoint, error) {
//Check for matching path duplicate
if ep.GetVirtualDirectoryRuleByMatchingPath(vdir.MatchingPath) != nil {
return nil, errors.New("rule with same matching path already exists")
}
//Append it to the list of virtual directory
ep.VirtualDirectories = append(ep.VirtualDirectories, vdir)
//Prepare to replace the current routing rule
parentRouter := ep.parent
readyRoutingRule, err := parentRouter.PrepareProxyRoute(ep)
if err != nil {
return nil, err
}
if ep.ProxyType == ProxyType_Root {
parentRouter.Root = readyRoutingRule
} else if ep.ProxyType == ProxyType_Host {
ep.Remove()
parentRouter.AddProxyRouteToRuntime(readyRoutingRule)
} else {
return nil, errors.New("unsupported proxy type")
}
return readyRoutingRule, nil
}
// Create a deep clone object of the proxy endpoint
// Note the returned object is not activated. Call to prepare function before pushing into runtime
func (ep *ProxyEndpoint) Clone() *ProxyEndpoint {
clonedProxyEndpoint := ProxyEndpoint{}
js, _ := json.Marshal(ep)
json.Unmarshal(js, &clonedProxyEndpoint)
return &clonedProxyEndpoint
}
// Remove this proxy endpoint from running proxy endpoint list
func (ep *ProxyEndpoint) Remove() error {
ep.parent.ProxyEndpoints.Delete(ep.RootOrMatchingDomain)
return nil
}
// Write changes to runtime without respawning the proxy handler
// use prepare -> remove -> add if you change anything in the endpoint
// that effects the proxy routing src / dest
func (ep *ProxyEndpoint) UpdateToRuntime() {
ep.parent.ProxyEndpoints.Store(ep.RootOrMatchingDomain, ep)
}

View File

@ -1,68 +0,0 @@
package dynamicproxy
import "errors"
/*
ProxyEndpoint.go
author: tobychui
This script handle the proxy endpoint object actions
so proxyEndpoint can be handled like a proper oop object
Most of the functions are implemented in dynamicproxy.go
*/
//Get the string version of proxy type
func (ep *ProxyEndpoint) GetProxyTypeString() string {
if ep.ProxyType == ProxyType_Subdomain {
return "subd"
} else if ep.ProxyType == ProxyType_Vdir {
return "vdir"
}
return "unknown"
}
//Update change in the current running proxy endpoint config
func (ep *ProxyEndpoint) UpdateToRuntime() {
if ep.IsVdir() {
ep.parent.ProxyEndpoints.Store(ep.RootOrMatchingDomain, ep)
} else if ep.IsSubDomain() {
ep.parent.SubdomainEndpoint.Store(ep.RootOrMatchingDomain, ep)
}
}
//Return true if the endpoint type is virtual directory
func (ep *ProxyEndpoint) IsVdir() bool {
return ep.ProxyType == ProxyType_Vdir
}
//Return true if the endpoint type is subdomain
func (ep *ProxyEndpoint) IsSubDomain() bool {
return ep.ProxyType == ProxyType_Subdomain
}
//Remove this proxy endpoint from running proxy endpoint list
func (ep *ProxyEndpoint) Remove() error {
//fmt.Println(ptype, key)
if ep.IsVdir() {
ep.parent.ProxyEndpoints.Delete(ep.RootOrMatchingDomain)
return nil
} else if ep.IsSubDomain() {
ep.parent.SubdomainEndpoint.Delete(ep.RootOrMatchingDomain)
return nil
}
return errors.New("invalid or unsupported type")
}
//ProxyEndpoint remove provide global access by key
func (router *Router) RemoveProxyEndpointByRootname(proxyType string, rootnameOrMatchingDomain string) error {
targetEpt, err := router.LoadProxy(proxyType, rootnameOrMatchingDomain)
if err != nil {
return err
}
return targetEpt.Remove()
}

View File

@ -6,6 +6,8 @@ import (
"net"
"net/http"
"net/url"
"path/filepath"
"sort"
"strings"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
@ -28,13 +30,41 @@ func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *P
return targetProxyEndpoint
}
func (router *Router) getSubdomainProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
var targetSubdomainEndpoint *ProxyEndpoint = nil
ep, ok := router.SubdomainEndpoint.Load(hostname)
ep, ok := router.ProxyEndpoints.Load(hostname)
if ok {
targetSubdomainEndpoint = ep.(*ProxyEndpoint)
}
//No hit. Try with wildcard
matchProxyEndpoints := []*ProxyEndpoint{}
router.ProxyEndpoints.Range(func(k, v interface{}) bool {
ep := v.(*ProxyEndpoint)
match, err := filepath.Match(ep.RootOrMatchingDomain, hostname)
if err != nil {
//Continue
return true
}
if match {
//targetSubdomainEndpoint = ep
matchProxyEndpoints = append(matchProxyEndpoints, ep)
return true
}
return true
})
if len(matchProxyEndpoints) == 1 {
//Only 1 match
return matchProxyEndpoints[0]
} else if len(matchProxyEndpoints) > 1 {
//More than one match. Get the best match one
sort.Slice(matchProxyEndpoints, func(i, j int) bool {
return matchProxyEndpoints[i].RootOrMatchingDomain < matchProxyEndpoints[j].RootOrMatchingDomain
})
return matchProxyEndpoints[0]
}
return targetSubdomainEndpoint
}
@ -54,14 +84,22 @@ func (router *Router) rewriteURL(rooturl string, requestURL string) string {
return rewrittenURL
}
// Handle subdomain request
func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
// Handle host request
func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
r.Header.Set("X-Forwarded-Host", r.Host)
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
//Inject custom headers
if len(target.UserDefinedHeaders) > 0 {
for _, customHeader := range target.UserDefinedHeaders {
r.Header.Set(customHeader.Key, customHeader.Value)
}
}
requestURL := r.URL.String()
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
r.Header.Set("A-Upgrade", "websocket")
r.Header.Set("Zr-Origin-Upgrade", "websocket")
wsRedirectionEndpoint := target.Domain
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
//Append / to the end of the redirection endpoint if not exists
@ -76,7 +114,10 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
}
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: target.SkipCertValidations,
SkipOriginCheck: target.SkipWebSocketOriginCheck,
})
wspHandler.ServeHTTP(w, r)
return
}
@ -89,10 +130,11 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
r.URL, _ = url.Parse(originalHostHeader)
}
err := target.Proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: target.Domain,
OriginalHost: originalHostHeader,
UseTLS: target.RequireTLS,
NoCache: h.Parent.Option.NoCache,
PathPrefix: "",
})
@ -113,15 +155,23 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
}
// Handle vdir type request
func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
rewriteURL := h.Parent.rewriteURL(target.RootOrMatchingDomain, r.RequestURI)
func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, target *VirtualDirectoryEndpoint) {
rewriteURL := h.Parent.rewriteURL(target.MatchingPath, r.RequestURI)
r.URL, _ = url.Parse(rewriteURL)
r.Header.Set("X-Forwarded-Host", r.Host)
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
//Inject custom headers
if len(target.parent.UserDefinedHeaders) > 0 {
for _, customHeader := range target.parent.UserDefinedHeaders {
r.Header.Set(customHeader.Key, customHeader.Value)
}
}
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
r.Header.Set("A-Upgrade", "websocket")
r.Header.Set("Zr-Origin-Upgrade", "websocket")
wsRedirectionEndpoint := target.Domain
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
@ -131,7 +181,10 @@ func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, targ
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
}
h.logRequest(r, true, 101, "vdir-websocket", target.Domain)
wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: target.SkipCertValidations,
SkipOriginCheck: target.parent.SkipWebSocketOriginCheck,
})
wspHandler.ServeHTTP(w, r)
return
}
@ -144,11 +197,11 @@ func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, targ
r.URL, _ = url.Parse(originalHostHeader)
}
err := target.Proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: target.Domain,
OriginalHost: originalHostHeader,
UseTLS: target.RequireTLS,
PathPrefix: target.RootOrMatchingDomain,
PathPrefix: target.MatchingPath,
})
var dnsError *net.DNSError

View File

@ -2,19 +2,25 @@ package redirection
import (
"encoding/json"
"fmt"
"log"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"sync"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils"
)
type RuleTable struct {
AllowRegex bool //Allow regular expression to be used in rule matching. Require up to O(n^m) time complexity
Logger *logger.Logger
configPath string //The location where the redirection rules is stored
rules sync.Map //Store the redirection rules for this reverse proxy instance
}
type RedirectRules struct {
@ -24,10 +30,11 @@ type RedirectRules struct {
StatusCode int //Status Code for redirection
}
func NewRuleTable(configPath string) (*RuleTable, error) {
func NewRuleTable(configPath string, allowRegex bool) (*RuleTable, error) {
thisRuleTable := RuleTable{
rules: sync.Map{},
configPath: configPath,
AllowRegex: allowRegex,
}
//Load all the rules from the config path
if !utils.FileExists(configPath) {
@ -77,7 +84,7 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
}
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
filename := strings.ReplaceAll(strings.ReplaceAll(redirectURL, "/", "-"), ".", "_") + ".json"
filename := utils.ReplaceSpecialCharacters(redirectURL) + ".json"
// Create the full file path by joining the t.configPath with the filename
filepath := path.Join(t.configPath, filename)
@ -105,11 +112,12 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
func (t *RuleTable) DeleteRedirectRule(redirectURL string) error {
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
filename := strings.ReplaceAll(strings.ReplaceAll(redirectURL, "/", "-"), ".", "_") + ".json"
filename := utils.ReplaceSpecialCharacters(redirectURL) + ".json"
// Create the full file path by joining the t.configPath with the filename
filepath := path.Join(t.configPath, filename)
fmt.Println(redirectURL, filename, filepath)
// Check if the file exists
if _, err := os.Stat(filepath); os.IsNotExist(err) {
return nil // File doesn't exist, nothing to delete
@ -145,18 +153,47 @@ func (t *RuleTable) MatchRedirectRule(requestedURL string) *RedirectRules {
// Iterate through all the keys in the rules map
var targetRedirectionRule *RedirectRules = nil
var maxMatch int = 0
t.rules.Range(func(key interface{}, value interface{}) bool {
// Check if the requested URL starts with the key as a prefix
if strings.HasPrefix(requestedURL, key.(string)) {
// This request URL matched the domain
if len(key.(string)) > maxMatch {
if t.AllowRegex {
//Regexp matching rule
matched, err := regexp.MatchString(key.(string), requestedURL)
if err != nil {
//Something wrong with the regex?
t.log("Unable to match regex", err)
return true
}
if matched {
maxMatch = len(key.(string))
targetRedirectionRule = value.(*RedirectRules)
}
} else {
//Default: prefix matching redirect
if strings.HasPrefix(requestedURL, key.(string)) {
// This request URL matched the domain
if len(key.(string)) > maxMatch {
maxMatch = len(key.(string))
targetRedirectionRule = value.(*RedirectRules)
}
}
}
return true
})
return targetRedirectionRule
}
// Log the message to log file, use STDOUT if logger not set
func (t *RuleTable) log(message string, err error) {
if t.Logger == nil {
if err == nil {
log.Println("[Redirect] " + message)
} else {
log.Println("[Redirect] " + message + ": " + err.Error())
}
} else {
t.Logger.PrintAndLog("Redirect", message, err)
}
}

View File

@ -1,51 +0,0 @@
package dynamicproxy
import (
"encoding/json"
"errors"
"log"
"os"
"imuslab.com/zoraxy/mod/utils"
)
/*
rootRoute.go
This script handle special case in routing where the root proxy
entity is involved. This also include its setting object
RootRoutingOptions
*/
var rootConfigFilepath string = "conf/root_config.json"
func loadRootRoutingOptionsFromFile() (*RootRoutingOptions, error) {
if !utils.FileExists(rootConfigFilepath) {
//Not found. Create a root option
js, _ := json.MarshalIndent(RootRoutingOptions{}, "", " ")
err := os.WriteFile(rootConfigFilepath, js, 0775)
if err != nil {
return nil, errors.New("Unable to write root config to file: " + err.Error())
}
}
newRootOption := RootRoutingOptions{}
rootOptionsBytes, err := os.ReadFile(rootConfigFilepath)
if err != nil {
log.Println("[Error] Unable to read root config file at " + rootConfigFilepath + ": " + err.Error())
return nil, err
}
err = json.Unmarshal(rootOptionsBytes, &newRootOption)
if err != nil {
log.Println("[Error] Unable to parse root config file: " + err.Error())
return nil, err
}
return &newRootOption, nil
}
// Save the new config to file. Note that this will not overwrite the runtime one
func (opt *RootRoutingOptions) SaveToFile() error {
js, _ := json.MarshalIndent(opt, "", " ")
err := os.WriteFile(rootConfigFilepath, js, 0775)
return err
}

View File

@ -0,0 +1,114 @@
package dynamicproxy
import (
"errors"
"net/url"
"strings"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
)
/*
Dynamic Proxy Router Functions
This script handle the proxy rules router spawning
and preparation
*/
// Prepare proxy route generate a proxy handler service object for your endpoint
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
//Filter the tailing slash if any
domain := endpoint.Domain
if domain[len(domain)-1:] == "/" {
domain = domain[:len(domain)-1]
}
endpoint.Domain = domain
//Parse the web proxy endpoint
webProxyEndpoint := domain
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
//TLS is not hardcoded in proxy target domain
if endpoint.RequireTLS {
webProxyEndpoint = "https://" + webProxyEndpoint
} else {
webProxyEndpoint = "http://" + webProxyEndpoint
}
}
//Create a new proxy agent for this root
path, err := url.Parse(webProxyEndpoint)
if err != nil {
return nil, err
}
//Create the proxy routing handler
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
IgnoreTLSVerification: endpoint.SkipCertValidations,
})
endpoint.proxy = proxy
endpoint.parent = router
//Prepare proxy routing hjandler for each of the virtual directories
for _, vdir := range endpoint.VirtualDirectories {
domain := vdir.Domain
if domain[len(domain)-1:] == "/" {
domain = domain[:len(domain)-1]
}
//Parse the web proxy endpoint
webProxyEndpoint = domain
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
//TLS is not hardcoded in proxy target domain
if vdir.RequireTLS {
webProxyEndpoint = "https://" + webProxyEndpoint
} else {
webProxyEndpoint = "http://" + webProxyEndpoint
}
}
path, err := url.Parse(webProxyEndpoint)
if err != nil {
return nil, err
}
proxy := dpcore.NewDynamicProxyCore(path, vdir.MatchingPath, &dpcore.DpcoreOptions{
IgnoreTLSVerification: vdir.SkipCertValidations,
})
vdir.proxy = proxy
vdir.parent = endpoint
}
return endpoint, nil
}
// Add Proxy Route to current runtime. Call to PrepareProxyRoute before adding to runtime
func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
if endpoint.proxy == nil {
//This endpoint is not prepared
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
}
// Push record into running subdomain endpoints
router.ProxyEndpoints.Store(endpoint.RootOrMatchingDomain, endpoint)
return nil
}
// Set given Proxy Route as Root. Call to PrepareProxyRoute before adding to runtime
func (router *Router) SetProxyRouteAsRoot(endpoint *ProxyEndpoint) error {
if endpoint.proxy == nil {
//This endpoint is not prepared
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
}
// Push record into running root endpoints
router.Root = endpoint
return nil
}
// ProxyEndpoint remove provide global access by key
func (router *Router) RemoveProxyEndpointByRootname(rootnameOrMatchingDomain string) error {
targetEpt, err := router.LoadProxy(rootnameOrMatchingDomain)
if err != nil {
return err
}
return targetEpt.Remove()
}

View File

@ -1,50 +0,0 @@
package dynamicproxy
import (
"log"
"net/url"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
)
/*
Add an URL intoa custom subdomain service
*/
func (router *Router) AddSubdomainRoutingService(options *SubdOptions) error {
domain := options.Domain
if domain[len(domain)-1:] == "/" {
domain = domain[:len(domain)-1]
}
webProxyEndpoint := domain
if options.RequireTLS {
webProxyEndpoint = "https://" + webProxyEndpoint
} else {
webProxyEndpoint = "http://" + webProxyEndpoint
}
//Create a new proxy agent for this root
path, err := url.Parse(webProxyEndpoint)
if err != nil {
return err
}
proxy := dpcore.NewDynamicProxyCore(path, "", options.SkipCertValidations)
router.SubdomainEndpoint.Store(options.MatchingDomain, &ProxyEndpoint{
RootOrMatchingDomain: options.MatchingDomain,
Domain: domain,
RequireTLS: options.RequireTLS,
Proxy: proxy,
BypassGlobalTLS: options.BypassGlobalTLS,
SkipCertValidations: options.SkipCertValidations,
RequireBasicAuth: options.RequireBasicAuth,
BasicAuthCredentials: options.BasicAuthCredentials,
BasicAuthExceptionRules: options.BasicAuthExceptionRules,
})
log.Println("Adding Subdomain Rule: ", options.MatchingDomain+" to "+domain)
return nil
}

View File

@ -0,0 +1,157 @@
<!DOCTYPE html>
<html>
<head>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
<meta charset="UTF-8">
<meta name="theme-color" content="#4b75ff">
<link rel="icon" type="image/png" href="img/small_icon.png"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js"></script>
<title>404 - Host Not Found</title>
<style>
h1, h2, h3, h4, h5, p, a, span{
font-family: 'Noto Sans TC', sans-serif;
font-weight: 300;
color: rgb(88, 88, 88)
}
.diagram{
background-color: #ebebeb;
box-shadow:
inset 0px 11px 8px -10px #CCC,
inset 0px -11px 8px -10px #CCC;
padding-bottom: 2em;
}
.diagramHeader{
margin-top: 0.2em;
}
@media (max-width:512px) {
.widescreenOnly{
display: none !important;
}
.four.wide.column:not(.widescreenOnly){
width: 50% !important;
}
.ui.grid{
justify-content: center !important;
}
}
</style>
</head>
<body>
<div>
<br><br>
<div class="ui container">
<h1 style="font-size: 4rem;">Error 404</h1>
<p style="font-size: 2rem; margin-bottom: 0.4em;">Target Host Not Found</p>
<small id="timestamp"></small>
</div>
<br><br>
</div>
<div class="diagram">
<div class="ui text container">
<div class="ui grid">
<div class="four wide column widescreenOnly" align="center">
<svg version="1.1" id="client_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="#C9CACA" d="M184.795,143.037c0,9.941-8.059,18-18,18H33.494c-9.941,0-18-8.059-18-18V44.952c0-9.941,8.059-18,18-18
h133.301c9.941,0,18,8.059,18,18V143.037z"/>
<circle fill="#FFFFFF" cx="37.39" cy="50.88" r="6.998"/>
<circle fill="#FFFFFF" cx="54.115" cy="50.88" r="6.998"/>
<path fill="#FFFFFF" d="M167.188,50.88c0,3.865-3.133,6.998-6.998,6.998H72.379c-3.865,0-6.998-3.133-6.998-6.998l0,0
c0-3.865,3.133-6.998,6.998-6.998h87.811C164.055,43.882,167.188,47.015,167.188,50.88L167.188,50.88z"/>
<rect x="31.296" y="66.907" fill="#FFFFFF" width="132.279" height="77.878"/>
<circle fill="#9BCA3E" cx="96.754" cy="144.785" r="37.574"/>
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="108.497,133.047 93.373,153.814
82.989,143.204 "/>
</svg>
<small>You</small>
<h2 class="diagramHeader">Browser</h2>
<p style="font-weight: 500; color: #9bca3e;">Working</p>
</div>
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
</div>
<div class="four wide column widescreenOnly" align="center">
<svg version="1.1" id="cloud_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<ellipse fill="#9FA0A0" cx="46.979" cy="108.234" rx="25.399" ry="25.139"/>
<circle fill="#9FA0A0" cx="109.407" cy="100.066" r="50.314"/>
<circle fill="#9FA0A0" cx="22.733" cy="129.949" r="19.798"/>
<circle fill="#9FA0A0" cx="172.635" cy="125.337" r="24.785"/>
<path fill="#9FA0A0" d="M193.514,133.318c0,9.28-7.522,16.803-16.803,16.803H28.223c-9.281,0-16.803-7.522-16.803-16.803l0,0
c0-9.28,7.522-16.804,16.803-16.804h148.488C185.991,116.515,193.514,124.038,193.514,133.318L193.514,133.318z"/>
<circle fill="#9BCA3D" cx="100" cy="149.572" r="38.267"/>
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="113.408,136.402 95.954,160.369
83.971,148.123 "/>
</svg>
<small>Gateway Node</small>
<h2 class="diagramHeader">Reverse Proxy</h2>
<p style="font-weight: 500; color: #9bca3e;">Working</p>
</div>
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
</div>
<div class="four wide column" align="center">
<svg version="1.1" id="host_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100%" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="#999999" d="M168.484,113.413c0,9.941,3.317,46.324-6.624,46.324H35.359c-9.941,0-5.873-39.118-5.715-46.324
l17.053-50.909c1.928-9.879,8.059-18,18-18h69.419c9.941,0,15.464,7.746,18,18L168.484,113.413z"/>
<rect x="38.068" y="118.152" fill="#FFFFFF" width="122.573" height="34.312"/>
<circle fill="#BD2426" cx="141.566" cy="135.873" r="8.014"/>
<circle fill="#BD2426" cx="99.354" cy="152.464" r="36.343"/>
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="144.125" x2="107.594" y2="161.946"/>
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="161.946" x2="107.594" y2="144.79"/>
</svg>
<small id="host"></small>
<h2 class="diagramHeader">Host</h2>
<p style="font-weight: 500; color: #bd2426;">Not Found</p>
</div>
</div>
</div>
</div>
<div>
<br>
<div class="ui container">
<div class="ui stackable grid">
<div class="eight wide column">
<h1>What happend?</h1>
<p>The reverse proxy target domain is not found.<br>For more information, see the error message on the reverse proxy terminal.</p>
</div>
<div class="eight wide column">
<h1>What can I do?</h1>
<h5 style="font-weight: 500;">If you are a visitor of this website: </h5>
<p>Please try again in a few minutes</p>
<h5 style="font-weight: 500;">If you are the owner of this website:</h5>
<div class="ui bulleted list">
<div class="item">Check if the proxy rules that match this hostname exists</div>
<div class="item">Visit the Reverse Proxy management interface to correct any setting errors</div>
</div>
</div>
</div>
</div>
<br>
</div>
<div class="ui divider"></div>
<div class="ui container" style="color: grey; font-size: 90%">
<p>Powered by Zoraxy</p>
</div>
<br><br>
<script>
$("#timestamp").text(new Date());
$("#host").text(location.href);
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -14,8 +14,9 @@ import (
)
const (
ProxyType_Subdomain = 0
ProxyType_Vdir = 1
ProxyType_Root = 0
ProxyType_Host = 1
ProxyType_Vdir = 2
)
type ProxyHandler struct {
@ -24,9 +25,11 @@ type ProxyHandler struct {
type RouterOption struct {
HostUUID string //The UUID of Zoraxy, use for heading mod
HostVersion string //The version 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
NoCache bool //Force set Cache-Control: no-store
ListenOnPort80 bool //Enable port 80 http listener
ForceHttpsRedirect bool //Force redirection of http to https endpoint
TlsManager *tlscert.Manager
@ -37,16 +40,14 @@ type RouterOption struct {
}
type Router struct {
Option *RouterOption
ProxyEndpoints *sync.Map
SubdomainEndpoint *sync.Map
Running bool
Root *ProxyEndpoint
RootRoutingOptions *RootRoutingOptions
mux http.Handler
server *http.Server
tlsListener net.Listener
routingRules []*RoutingRule
Option *RouterOption
ProxyEndpoints *sync.Map
Running bool
Root *ProxyEndpoint
mux http.Handler
server *http.Server
tlsListener net.Listener
routingRules []*RoutingRule
tlsRedirectStop chan bool //Stop channel for tls redirection server
tldMap map[string]int //Top level domain map, see tld.json
@ -69,63 +70,72 @@ type BasicAuthExceptionRule struct {
PathPrefix string
}
// A proxy endpoint record
type ProxyEndpoint struct {
ProxyType int //The type of this proxy, see const def
RootOrMatchingDomain string //Root for vdir or Matching domain for subd, also act as key
Domain string //Domain or IP to proxy to
RequireTLS bool //Target domain require TLS
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
SkipCertValidations bool //Set to true to accept self signed certs
RequireBasicAuth bool //Set to true to request basic auth before proxy
BasicAuthCredentials []*BasicAuthCredentials `json:"-"` //Basic auth credentials
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
Proxy *dpcore.ReverseProxy `json:"-"`
parent *Router
// User defined headers to add into a proxy endpoint
type UserDefinedHeader struct {
Key string
Value string
}
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
// program structure than directly using ProxyEndpoint
type VirtualDirectoryEndpoint struct {
MatchingPath string //Matching prefix of the request path, also act as key
Domain string //Domain or IP to proxy to
RequireTLS bool //Target domain require TLS
SkipCertValidations bool //Set to true to accept self signed certs
Disabled bool //If the rule is enabled
proxy *dpcore.ReverseProxy `json:"-"`
parent *ProxyEndpoint `json:"-"`
}
// A proxy endpoint record, a general interface for handling inbound routing
type ProxyEndpoint struct {
ProxyType int //The type of this proxy, see const def
RootOrMatchingDomain string //Matching domain for host, also act as key
Domain string //Domain or IP to proxy to
//TLS/SSL Related
RequireTLS bool //Target domain require TLS
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
SkipCertValidations bool //Set to true to accept self signed certs
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
//Virtual Directories
VirtualDirectories []*VirtualDirectoryEndpoint
//Custom Headers
UserDefinedHeaders []*UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
//Authentication
RequireBasicAuth bool //Set to true to request basic auth before proxy
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
//Fallback routing logic
DefaultSiteOption int //Fallback routing logic options
DefaultSiteValue string //Fallback routing target, optional
Disabled bool //If the rule is disabled
//Internal Logic Elements
parent *Router
proxy *dpcore.ReverseProxy `json:"-"`
}
/*
Routing type specific interface
These are options that only avaible for a specific interface
when running, these are converted into "ProxyEndpoint" objects
for more generic routing logic
*/
// Root options are those that are required for reverse proxy handler to work
type RootOptions struct {
ProxyLocation string //Proxy Root target, all unset traffic will be forward to here
RequireTLS bool //Proxy root target require TLS connection (not recommended)
BypassGlobalTLS bool //Bypass global TLS setting and make root http only (not recommended)
SkipCertValidations bool //Skip cert validation, suitable for self-signed certs, CURRENTLY NOT USED
//Basic Auth Related
RequireBasicAuth bool //Require basic auth, CURRENTLY NOT USED
BasicAuthCredentials []*BasicAuthCredentials
BasicAuthExceptionRules []*BasicAuthExceptionRule
}
// Additional options are here for letting router knows how to route exception cases for root
type RootRoutingOptions struct {
//Root only configs
EnableRedirectForUnsetRules bool //Force unset rules to redirect to custom domain
UnsetRuleRedirectTarget string //Custom domain to redirect to for unset rules
}
type VdirOptions struct {
RootName string
Domain string
RequireTLS bool
BypassGlobalTLS bool
SkipCertValidations bool
RequireBasicAuth bool
BasicAuthCredentials []*BasicAuthCredentials
BasicAuthExceptionRules []*BasicAuthExceptionRule
}
type SubdOptions struct {
MatchingDomain string
Domain string
RequireTLS bool
BypassGlobalTLS bool
SkipCertValidations bool
RequireBasicAuth bool
BasicAuthCredentials []*BasicAuthCredentials
BasicAuthExceptionRules []*BasicAuthExceptionRule
}
const (
DefaultSite_InternalStaticWebServer = 0
DefaultSite_ReverseProxy = 1
DefaultSite_Redirect = 2
DefaultSite_NotFoundPage = 3
)
/*
Web Templates
@ -133,4 +143,6 @@ Web Templates
var (
//go:embed templates/forbidden.html
page_forbidden []byte
//go:embed templates/hosterror.html
page_hosterror []byte
)

View File

@ -0,0 +1,25 @@
MIT License
Copyright (c) 2022 Smarty
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
NOTE: Various optional and subordinate components carry their own licensing
requirements and restrictions. Use of those components is subject to the terms
and conditions outlined the respective license of each component.

View File

@ -0,0 +1,109 @@
package cproxy
import (
"net/http"
"time"
)
func New(options ...option) http.Handler {
var this configuration
Options.apply(options...)(&this)
return newHandler(this.Filter, this.ClientConnector, this.ServerConnector, this.Monitor)
}
var Options singleton
type singleton struct{}
type option func(*configuration)
type configuration struct {
DialTimeout time.Duration
Filter Filter
DialAddress string
Dialer Dialer
LogConnections bool
ProxyProtocol bool
Initializer initializer
ClientConnector clientConnector
ServerConnector serverConnector
Monitor monitor
Logger logger
}
func (singleton) DialTimeout(value time.Duration) option {
return func(this *configuration) { this.DialTimeout = value }
}
func (singleton) Filter(value Filter) option {
return func(this *configuration) { this.Filter = value }
}
func (singleton) ClientConnector(value clientConnector) option {
return func(this *configuration) { this.ClientConnector = value }
}
func (singleton) DialAddress(value string) option {
return func(this *configuration) { this.DialAddress = value }
}
func (singleton) Dialer(value Dialer) option {
return func(this *configuration) { this.Dialer = value }
}
func (singleton) LogConnections(value bool) option {
return func(this *configuration) { this.LogConnections = value }
}
func (singleton) ProxyProtocol(value bool) option {
return func(this *configuration) { this.ProxyProtocol = value }
}
func (singleton) Initializer(value initializer) option {
return func(this *configuration) { this.Initializer = value }
}
func (singleton) ServerConnector(value serverConnector) option {
return func(this *configuration) { this.ServerConnector = value }
}
func (singleton) Monitor(value monitor) option {
return func(this *configuration) { this.Monitor = value }
}
func (singleton) Logger(value logger) option {
return func(this *configuration) { this.Logger = value }
}
func (singleton) apply(options ...option) option {
return func(this *configuration) {
for _, item := range Options.defaults(options...) {
item(this)
}
if this.Dialer == nil {
this.Dialer = newDialer(this)
}
this.Dialer = newRoutingDialer(this)
if this.ProxyProtocol {
this.Initializer = newProxyProtocolInitializer()
}
if this.Initializer == nil {
this.Initializer = nop{}
}
this.Initializer = newLoggingInitializer(this)
if this.ServerConnector == nil {
this.ServerConnector = newServerConnector(this.Dialer, this.Initializer)
}
}
}
func (singleton) defaults(options ...option) []option {
return append([]option{
Options.DialTimeout(time.Second * 10),
Options.Filter(newFilter()),
Options.ClientConnector(newClientConnector()),
Options.Initializer(nop{}),
Options.Monitor(nop{}),
Options.Logger(nop{}),
}, options...)
}
type nop struct{}
func (nop) Measure(int) {}
func (nop) Printf(string, ...interface{}) {}
func (nop) Initialize(Socket, Socket) bool { return true }

View File

@ -0,0 +1,19 @@
package cproxy
import "net/http"
type defaultClientConnector struct{}
func newClientConnector() *defaultClientConnector {
return &defaultClientConnector{}
}
func (this *defaultClientConnector) Connect(response http.ResponseWriter) Socket {
if hijacker, ok := response.(http.Hijacker); !ok {
return nil
} else if socket, _, _ := hijacker.Hijack(); socket == nil {
return nil // this 'else if' exists to avoid the pointer nil != interface nil issue
} else {
return socket
}
}

View File

@ -0,0 +1,25 @@
package cproxy
import (
"net"
"time"
)
type defaultDialer struct {
timeout time.Duration
logger logger
}
func newDialer(config *configuration) *defaultDialer {
return &defaultDialer{timeout: config.DialTimeout, logger: config.Logger}
}
func (this *defaultDialer) Dial(address string) Socket {
if socket, err := net.DialTimeout("tcp", address, this.timeout); err == nil {
return socket
} else {
this.logger.Printf("[INFO] Unable to establish connection to [%s]: %s", address, err)
}
return nil
}

View File

@ -0,0 +1,9 @@
package cproxy
import "net/http"
type defaultFilter struct{}
func newFilter() *defaultFilter { return &defaultFilter{} }
func (this *defaultFilter) IsAuthorized(http.ResponseWriter, *http.Request) bool { return true }

View File

@ -0,0 +1,56 @@
package cproxy
import "net/http"
type defaultHandler struct {
filter Filter
clientConnector clientConnector
serverConnector serverConnector
meter monitor
}
func newHandler(filter Filter, clientConnector clientConnector, serverConnector serverConnector, meter monitor) *defaultHandler {
return &defaultHandler{
filter: filter,
clientConnector: clientConnector,
serverConnector: serverConnector,
meter: meter,
}
}
func (this *defaultHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
this.meter.Measure(MeasurementHTTPRequest)
if request.Method != "CONNECT" {
this.meter.Measure(MeasurementBadMethod)
writeResponseStatus(response, http.StatusMethodNotAllowed)
} else if !this.filter.IsAuthorized(response, request) {
this.meter.Measure(MeasurementUnauthorizedRequest)
//writeResponseStatus(response, http.StatusUnauthorized)
} else if client := this.clientConnector.Connect(response); client == nil {
this.meter.Measure(MeasurementClientConnectionFailed)
writeResponseStatus(response, http.StatusNotImplemented)
} else if connection := this.serverConnector.Connect(client, request.URL.Host); connection == nil {
this.meter.Measure(MeasurementServerConnectionFailed)
_, _ = client.Write(statusBadGateway)
_ = client.Close()
} else {
this.meter.Measure(MeasurementProxyReady)
_, _ = client.Write(statusReady)
connection.Proxy()
this.meter.Measure(MeasurementProxyComplete)
}
}
func writeResponseStatus(response http.ResponseWriter, statusCode int) {
http.Error(response, http.StatusText(statusCode), statusCode)
}
var (
statusBadGateway = []byte("HTTP/1.1 502 Bad Gateway\r\n\r\n")
statusReady = []byte("HTTP/1.1 200 OK\r\n\r\n")
)

View File

@ -0,0 +1,54 @@
package cproxy
import (
"io"
"sync"
)
type defaultProxy struct {
client Socket
server Socket
waiter *sync.WaitGroup
}
func newProxy(client, server Socket) *defaultProxy {
waiter := &sync.WaitGroup{}
waiter.Add(2) // wait on both client->server and server->client streams
return &defaultProxy{
waiter: waiter,
client: client,
server: server,
}
}
func (this *defaultProxy) Proxy() {
go this.streamAndClose(this.client, this.server)
go this.streamAndClose(this.server, this.client)
this.closeSockets()
}
func (this *defaultProxy) streamAndClose(reader, writer Socket) {
_, _ = io.Copy(writer, reader)
tryCloseRead(reader)
tryCloseWrite(writer)
this.waiter.Done()
}
func tryCloseRead(socket Socket) {
if tcp, ok := socket.(tcpSocket); ok {
_ = tcp.CloseRead()
}
}
func tryCloseWrite(socket Socket) {
if tcp, ok := socket.(tcpSocket); ok {
_ = tcp.CloseWrite()
}
}
func (this *defaultProxy) closeSockets() {
this.waiter.Wait()
_ = this.client.Close()
_ = this.server.Close()
}

View File

@ -0,0 +1,24 @@
package cproxy
type defaultServerConnector struct {
dialer Dialer
initializer initializer
}
func newServerConnector(dialer Dialer, initializer initializer) *defaultServerConnector {
return &defaultServerConnector{dialer: dialer, initializer: initializer}
}
func (this *defaultServerConnector) Connect(client Socket, serverAddress string) proxy {
server := this.dialer.Dial(serverAddress)
if server == nil {
return nil
}
if !this.initializer.Initialize(client, server) {
_ = server.Close()
return nil
}
return newProxy(client, server)
}

View File

@ -0,0 +1,32 @@
package cproxy
import "net/http"
type hostnameFilter struct {
authorized []string
}
func NewHostnameFilter(authorized []string) Filter {
return &hostnameFilter{authorized: authorized}
}
func (this hostnameFilter) IsAuthorized(_ http.ResponseWriter, request *http.Request) bool {
if len(this.authorized) == 0 {
return true
}
host := request.URL.Host
hostLength := len(host)
for _, authorized := range this.authorized {
if authorized[:2] == "*." {
have, want := hostLength, len(authorized)-1
if have > want && authorized[1:] == host[hostLength-want:] {
return true
}
} else if authorized == host {
return true
}
}
return false
}

View File

@ -0,0 +1,26 @@
package cproxy
import (
"net/http"
"strings"
)
type hostnameSuffixFilter struct {
authorized []string
}
func NewHostnameSuffixFilter(authorized []string) Filter {
return &hostnameSuffixFilter{authorized: authorized}
}
func (this hostnameSuffixFilter) IsAuthorized(_ http.ResponseWriter, request *http.Request) bool {
host := request.URL.Host
for _, authorized := range this.authorized {
if strings.HasSuffix(host, authorized) {
return true
}
}
return false
}

View File

@ -0,0 +1,67 @@
package cproxy
import (
"io"
"net"
"net/http"
)
type (
Filter interface {
IsAuthorized(http.ResponseWriter, *http.Request) bool
}
clientConnector interface {
Connect(http.ResponseWriter) Socket
}
)
type (
Dialer interface {
Dial(string) Socket
}
serverConnector interface {
Connect(Socket, string) proxy
}
initializer interface {
Initialize(Socket, Socket) bool
}
proxy interface {
Proxy()
}
)
type (
Socket interface {
io.ReadWriteCloser
RemoteAddr() net.Addr
}
tcpSocket interface {
Socket
CloseRead() error
CloseWrite() error
}
)
type (
monitor interface {
Measure(int)
}
logger interface {
Printf(string, ...interface{})
}
)
const (
MeasurementHTTPRequest int = iota
MeasurementBadMethod
MeasurementUnauthorizedRequest
MeasurementClientConnectionFailed
MeasurementServerConnectionFailed
MeasurementProxyReady
MeasurementProxyComplete
)

View File

@ -0,0 +1,24 @@
package cproxy
type loggingInitializer struct {
logger logger
inner initializer
}
func newLoggingInitializer(config *configuration) initializer {
if !config.LogConnections {
return config.Initializer
}
return &loggingInitializer{inner: config.Initializer, logger: config.Logger}
}
func (this *loggingInitializer) Initialize(client, server Socket) bool {
result := this.inner.Initialize(client, server)
if !result {
this.logger.Printf("[INFO] Connection failed [%s] -> [%s]", client.RemoteAddr(), server.RemoteAddr())
}
return result
}

View File

@ -0,0 +1,36 @@
package cproxy
import (
"fmt"
"io"
"net"
"strings"
)
type proxyProtocolInitializer struct{}
func newProxyProtocolInitializer() *proxyProtocolInitializer {
return &proxyProtocolInitializer{}
}
func (this *proxyProtocolInitializer) Initialize(client, server Socket) bool {
header := formatHeader(client.RemoteAddr(), server.RemoteAddr())
_, err := io.WriteString(server, header)
return err == nil
}
func formatHeader(client, server net.Addr) string {
clientAddress, clientPort := parseAddress(client.String())
serverAddress, serverPort := parseAddress(server.String())
if strings.Contains(clientAddress, ":") {
return fmt.Sprintf(proxyProtocolIPv6Preamble, clientAddress, serverAddress, clientPort, serverPort)
}
return fmt.Sprintf(proxyProtocolIPv4Preamble, clientAddress, serverAddress, clientPort, serverPort)
}
func parseAddress(address string) (string, string) {
address, port, _ := net.SplitHostPort(address)
return address, port
}
const proxyProtocolIPv4Preamble = "PROXY TCP4 %s %s %s %s\r\n"
const proxyProtocolIPv6Preamble = "PROXY TCP6 %s %s %s %s\r\n"

View File

@ -0,0 +1,18 @@
package cproxy
type routingDialer struct {
inner Dialer
targetAddress string
}
func newRoutingDialer(config *configuration) Dialer {
if len(config.DialAddress) == 0 {
return config.Dialer
}
return &routingDialer{inner: config.Dialer, targetAddress: config.DialAddress}
}
func (this *routingDialer) Dial(string) Socket {
return this.inner.Dial(this.targetAddress)
}

View File

@ -0,0 +1,137 @@
package forwardproxy
import (
"context"
"encoding/json"
"errors"
"log"
"net/http"
"strconv"
"time"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/forwardproxy/cproxy"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils"
)
type ZrFilter struct {
//To be implemented
}
type Handler struct {
server *http.Server
handler *http.Handler
running bool
db *database.Database
logger *logger.Logger
Port int
}
func NewForwardProxy(sysdb *database.Database, port int, logger *logger.Logger) *Handler {
thisFilter := ZrFilter{}
handler := cproxy.New(cproxy.Options.Filter(thisFilter))
return &Handler{
db: sysdb,
server: nil,
handler: &handler,
running: false,
logger: logger,
Port: port,
}
}
// Start the forward proxy
func (h *Handler) Start() error {
if h.running {
return errors.New("forward proxy already running")
}
server := &http.Server{Addr: ":" + strconv.Itoa(h.Port), Handler: *h.handler}
h.server = server
go func() {
if err := server.ListenAndServe(); err != nil {
if err != nil {
log.Println(err.Error())
}
}
}()
h.running = true
return nil
}
// Stop the forward proxy
func (h *Handler) Stop() error {
if h.running && h.server != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := h.server.Shutdown(ctx); err != nil {
return err
}
h.running = false
h.server = nil
}
return nil
}
// Update the port number of the forward proxy
func (h *Handler) UpdatePort(newPort int) error {
h.Stop()
h.Port = newPort
return h.Start()
}
func (it ZrFilter) IsAuthorized(w http.ResponseWriter, r *http.Request) bool {
return true
}
// Handle port change of the forward proxy
func (h *Handler) HandlePort(w http.ResponseWriter, r *http.Request) {
port, err := utils.PostInt(r, "port")
if err != nil {
js, _ := json.Marshal(h.Port)
utils.SendJSONResponse(w, string(js))
} else {
//Update the port
err = h.UpdatePort(port)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
h.logger.PrintAndLog("Forward Proxy", "HTTP Forward Proxy port updated to :"+strconv.Itoa(h.Port), nil)
h.db.Write("fwdproxy", "port", port)
utils.SendOK(w)
}
}
// Handle power toggle of the forward proxys
func (h *Handler) HandleToogle(w http.ResponseWriter, r *http.Request) {
enabled, err := utils.PostBool(r, "enable")
if err != nil {
//Get the current state of the forward proxy
js, _ := json.Marshal(h.running)
utils.SendJSONResponse(w, string(js))
} else {
if enabled {
err = h.Start()
if err != nil {
h.logger.PrintAndLog("Forward Proxy", "Unable to start forward proxy server", err)
utils.SendErrorResponse(w, err.Error())
return
}
h.logger.PrintAndLog("Forward Proxy", "HTTP Forward Proxy Started, listening on :"+strconv.Itoa(h.Port), nil)
} else {
err = h.Stop()
if err != nil {
h.logger.PrintAndLog("Forward Proxy", "Unable to stop forward proxy server", err)
utils.SendErrorResponse(w, err.Error())
return
}
h.logger.PrintAndLog("Forward Proxy", "HTTP Forward Proxy Stopped", nil)
}
h.db.Write("fwdproxy", "enabled", enabled)
utils.SendOK(w)
}
}

View File

@ -207,7 +207,7 @@ func (m *NetworkManager) HandleSetRanges(w http.ResponseWriter, r *http.Request)
utils.SendOK(w)
}
//Handle listing of network members. Set details=true for listing all details
// Handle listing of network members. Set details=true for listing all details
func (m *NetworkManager) HandleMemberList(w http.ResponseWriter, r *http.Request) {
netid, err := utils.GetPara(r, "netid")
if err != nil {
@ -241,7 +241,7 @@ func (m *NetworkManager) HandleMemberList(w http.ResponseWriter, r *http.Request
}
}
//Handle Authorization of members
// Handle Authorization of members
func (m *NetworkManager) HandleMemberAuthorization(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
@ -281,7 +281,7 @@ func (m *NetworkManager) HandleMemberAuthorization(w http.ResponseWriter, r *htt
}
}
//Handle Delete or Add IP for a member in a network
// Handle Delete or Add IP for a member in a network
func (m *NetworkManager) HandleMemberIP(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
@ -356,7 +356,7 @@ func (m *NetworkManager) HandleMemberIP(w http.ResponseWriter, r *http.Request)
}
}
//Handle naming for members
// Handle naming for members
func (m *NetworkManager) HandleMemberNaming(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
@ -391,7 +391,7 @@ func (m *NetworkManager) HandleMemberNaming(w http.ResponseWriter, r *http.Reque
}
}
//Handle delete of a given memver
// Handle delete of a given memver
func (m *NetworkManager) HandleMemberDelete(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
@ -426,3 +426,79 @@ func (m *NetworkManager) HandleMemberDelete(w http.ResponseWriter, r *http.Reque
utils.SendOK(w)
}
// Check if a given network id is a network hosted on this zoraxy node
func (m *NetworkManager) IsLocalGAN(networkId string) bool {
networks, err := m.listNetworkIds()
if err != nil {
return false
}
for _, network := range networks {
if network == networkId {
return true
}
}
return false
}
// Handle server instant joining a given network
func (m *NetworkManager) HandleServerJoinNetwork(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
utils.SendErrorResponse(w, "net id not set")
return
}
//Check if the target network is a network hosted on this server
if !m.IsLocalGAN(netid) {
utils.SendErrorResponse(w, "given network is not a GAN hosted on this node")
return
}
if m.memberExistsInNetwork(netid, m.ControllerID) {
utils.SendErrorResponse(w, "controller already inside network")
return
}
//Join the network
err = m.joinNetwork(netid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
// Handle server instant leaving a given network
func (m *NetworkManager) HandleServerLeaveNetwork(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
utils.SendErrorResponse(w, "net id not set")
return
}
//Check if the target network is a network hosted on this server
if !m.IsLocalGAN(netid) {
utils.SendErrorResponse(w, "given network is not a GAN hosted on this node")
return
}
//Leave the network
err = m.leaveNetwork(netid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Remove it from target network if it is authorized
err = m.deleteMember(netid, m.ControllerID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}

View File

@ -117,7 +117,7 @@ type MemberInfo struct {
VRev int `json:"vRev"`
}
//Get the zerotier node info from local service
// Get the zerotier node info from local service
func getControllerInfo(token string, apiPort int) (*NodeInfo, error) {
url := "http://localhost:" + strconv.Itoa(apiPort) + "/status"
@ -187,7 +187,7 @@ func (m *NetworkManager) createNetwork() (*NetworkInfo, error) {
return &networkInfo, nil
}
//List network details
// List network details
func (m *NetworkManager) getNetworkInfoById(networkId string) (*NetworkInfo, error) {
req, err := http.NewRequest("GET", os.ExpandEnv("http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/"+networkId+"/"), nil)
if err != nil {
@ -249,7 +249,7 @@ func (m *NetworkManager) setNetworkInfoByID(networkId string, newNetworkInfo *Ne
return nil
}
//List network IDs
// List network IDs
func (m *NetworkManager) listNetworkIds() ([]string, error) {
req, err := http.NewRequest("GET", "http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/", nil)
if err != nil {
@ -281,7 +281,7 @@ func (m *NetworkManager) listNetworkIds() ([]string, error) {
return networkIds, nil
}
//wrapper for checking if a network id exists
// wrapper for checking if a network id exists
func (m *NetworkManager) networkExists(networkId string) bool {
networkIds, err := m.listNetworkIds()
if err != nil {
@ -297,7 +297,7 @@ func (m *NetworkManager) networkExists(networkId string) bool {
return false
}
//delete a network
// delete a network
func (m *NetworkManager) deleteNetwork(networkID string) error {
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkID + "/"
client := &http.Client{}
@ -330,8 +330,8 @@ func (m *NetworkManager) deleteNetwork(networkID string) error {
return nil
}
//Configure network
//Example: configureNetwork(netid, "192.168.192.1", "192.168.192.254", "192.168.192.0/24")
// Configure network
// Example: configureNetwork(netid, "192.168.192.1", "192.168.192.254", "192.168.192.0/24")
func (m *NetworkManager) configureNetwork(networkID string, ipRangeStart string, ipRangeEnd string, routeTarget string) error {
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkID + "/"
data := map[string]interface{}{
@ -545,7 +545,7 @@ func (m *NetworkManager) memberExistsInNetwork(netid string, memid string) bool
return false
}
//Get a network memeber info by netid and memberid
// Get a network memeber info by netid and memberid
func (m *NetworkManager) getNetworkMemberInfo(netid string, memberid string) (*MemberInfo, error) {
req, err := http.NewRequest("GET", "http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/"+netid+"/member/"+memberid, nil)
if err != nil {
@ -573,7 +573,7 @@ func (m *NetworkManager) getNetworkMemberInfo(netid string, memberid string) (*M
return thisMemeberInfo, nil
}
//Set the authorization state of a member
// Set the authorization state of a member
func (m *NetworkManager) AuthorizeMember(netid string, memberid string, setAuthorized bool) error {
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + netid + "/member/" + memberid
payload := []byte(`{"authorized": true}`)
@ -600,7 +600,7 @@ func (m *NetworkManager) AuthorizeMember(netid string, memberid string, setAutho
return nil
}
//Delete a member from the network
// Delete a member from the network
func (m *NetworkManager) deleteMember(netid string, memid string) error {
req, err := http.NewRequest("DELETE", "http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/"+netid+"/member/"+memid, nil)
if err != nil {
@ -620,3 +620,45 @@ func (m *NetworkManager) deleteMember(netid string, memid string) error {
return nil
}
// Make the host to join a given network
func (m *NetworkManager) joinNetwork(netid string) error {
req, err := http.NewRequest("POST", "http://localhost:"+strconv.Itoa(m.apiPort)+"/network/"+netid, nil)
if err != nil {
return err
}
req.Header.Set("X-Zt1-Auth", os.ExpandEnv(m.authToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
}
return nil
}
// Make the host to leave a given network
func (m *NetworkManager) leaveNetwork(netid string) error {
req, err := http.NewRequest("DELETE", "http://localhost:"+strconv.Itoa(m.apiPort)+"/network/"+netid, nil)
if err != nil {
return err
}
req.Header.Set("X-Zt1-Auth", os.ExpandEnv(m.authToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
}
return nil
}

View File

@ -1,6 +1,9 @@
package geodb
import "strings"
import (
"encoding/json"
"strings"
)
/*
Whitelist.go
@ -8,11 +11,29 @@ import "strings"
This script handles whitelist related functions
*/
const (
EntryType_CountryCode int = 0
EntryType_IP int = 1
)
type WhitelistEntry struct {
EntryType int //Entry type of whitelist, Country Code or IP
CC string //ISO Country Code
IP string //IP address or range
Comment string //Comment for this entry
}
//Geo Whitelist
func (s *Store) AddCountryCodeToWhitelist(countryCode string) {
func (s *Store) AddCountryCodeToWhitelist(countryCode string, comment string) {
countryCode = strings.ToLower(countryCode)
s.sysdb.Write("whitelist-cn", countryCode, true)
entry := WhitelistEntry{
EntryType: EntryType_CountryCode,
CC: countryCode,
Comment: comment,
}
s.sysdb.Write("whitelist-cn", countryCode, entry)
}
func (s *Store) RemoveCountryCodeFromWhitelist(countryCode string) {
@ -22,20 +43,19 @@ func (s *Store) RemoveCountryCodeFromWhitelist(countryCode string) {
func (s *Store) IsCountryCodeWhitelisted(countryCode string) bool {
countryCode = strings.ToLower(countryCode)
var isWhitelisted bool = false
s.sysdb.Read("whitelist-cn", countryCode, &isWhitelisted)
return isWhitelisted
return s.sysdb.KeyExists("whitelist-cn", countryCode)
}
func (s *Store) GetAllWhitelistedCountryCode() []string {
whitelistedCountryCode := []string{}
func (s *Store) GetAllWhitelistedCountryCode() []*WhitelistEntry {
whitelistedCountryCode := []*WhitelistEntry{}
entries, err := s.sysdb.ListTable("whitelist-cn")
if err != nil {
return whitelistedCountryCode
}
for _, keypairs := range entries {
ip := string(keypairs[0])
whitelistedCountryCode = append(whitelistedCountryCode, ip)
thisWhitelistEntry := WhitelistEntry{}
json.Unmarshal(keypairs[1], &thisWhitelistEntry)
whitelistedCountryCode = append(whitelistedCountryCode, &thisWhitelistEntry)
}
return whitelistedCountryCode
@ -43,8 +63,14 @@ func (s *Store) GetAllWhitelistedCountryCode() []string {
//IP Whitelist
func (s *Store) AddIPToWhiteList(ipAddr string) {
s.sysdb.Write("whitelist-ip", ipAddr, true)
func (s *Store) AddIPToWhiteList(ipAddr string, comment string) {
thisIpEntry := WhitelistEntry{
EntryType: EntryType_IP,
IP: ipAddr,
Comment: comment,
}
s.sysdb.Write("whitelist-ip", ipAddr, thisIpEntry)
}
func (s *Store) RemoveIPFromWhiteList(ipAddr string) {
@ -52,14 +78,14 @@ func (s *Store) RemoveIPFromWhiteList(ipAddr string) {
}
func (s *Store) IsIPWhitelisted(ipAddr string) bool {
var isWhitelisted bool = false
s.sysdb.Read("whitelist-ip", ipAddr, &isWhitelisted)
isWhitelisted := s.sysdb.KeyExists("whitelist-ip", ipAddr)
if isWhitelisted {
//single IP whitelist entry
return true
}
//Check for IP wildcard and CIRD rules
AllWhitelistedIps := s.GetAllWhitelistedIp()
AllWhitelistedIps := s.GetAllWhitelistedIpAsStringSlice()
for _, whitelistRules := range AllWhitelistedIps {
wildcardMatch := MatchIpWildcard(ipAddr, whitelistRules)
if wildcardMatch {
@ -75,17 +101,29 @@ func (s *Store) IsIPWhitelisted(ipAddr string) bool {
return false
}
func (s *Store) GetAllWhitelistedIp() []string {
whitelistedIp := []string{}
func (s *Store) GetAllWhitelistedIp() []*WhitelistEntry {
whitelistedIp := []*WhitelistEntry{}
entries, err := s.sysdb.ListTable("whitelist-ip")
if err != nil {
return whitelistedIp
}
for _, keypairs := range entries {
ip := string(keypairs[0])
whitelistedIp = append(whitelistedIp, ip)
//ip := string(keypairs[0])
thisEntry := WhitelistEntry{}
json.Unmarshal(keypairs[1], &thisEntry)
whitelistedIp = append(whitelistedIp, &thisEntry)
}
return whitelistedIp
}
func (s *Store) GetAllWhitelistedIpAsStringSlice() []string {
allWhitelistedIPs := []string{}
entries := s.GetAllWhitelistedIp()
for _, entry := range entries {
allWhitelistedIPs = append(allWhitelistedIPs, entry.IP)
}
return allWhitelistedIPs
}

View File

@ -85,10 +85,6 @@ func NewReverseProxy(target *url.URL) *ReverseProxy {
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
if _, ok := req.Header["User-Agent"]; !ok {
req.Header.Set("User-Agent", "")
}
}
return &ReverseProxy{Director: director, Verbal: false}
@ -211,9 +207,9 @@ func removeHeaders(header http.Header) {
}
}
if header.Get("A-Upgrade") != "" {
header.Set("Upgrade", header.Get("A-Upgrade"))
header.Del("A-Upgrade")
if header.Get("Zr-Origin-Upgrade") != "" {
header.Set("Upgrade", header.Get("Zr-Origin-Upgrade"))
header.Del("Zr-Origin-Upgrade")
}
}

View File

@ -82,10 +82,13 @@ func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWrite
requestURL := r.URL.String()
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
r.Header.Set("A-Upgrade", "websocket")
r.Header.Set("Zr-Origin-Upgrade", "websocket")
requestURL = strings.TrimPrefix(requestURL, "/")
u, _ := url.Parse("ws://127.0.0.1:" + strconv.Itoa(targetInstance.AssignedPort) + "/" + requestURL)
wspHandler := websocketproxy.NewProxy(u, false)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: false,
SkipOriginCheck: false,
})
wspHandler.ServeHTTP(w, r)
return
}

View File

@ -5,22 +5,22 @@ import (
"strings"
)
//This remove the certificates in the list where either the
//public key or the private key is missing
// This remove the certificates in the list where either the
// public key or the private key is missing
func getCertPairs(certFiles []string) []string {
crtMap := make(map[string]bool)
pemMap := make(map[string]bool)
keyMap := make(map[string]bool)
for _, filename := range certFiles {
if filepath.Ext(filename) == ".crt" {
crtMap[strings.TrimSuffix(filename, ".crt")] = true
if filepath.Ext(filename) == ".pem" {
pemMap[strings.TrimSuffix(filename, ".pem")] = true
} else if filepath.Ext(filename) == ".key" {
keyMap[strings.TrimSuffix(filename, ".key")] = true
}
}
var result []string
for domain := range crtMap {
for domain := range pemMap {
if keyMap[domain] {
result = append(result, domain)
}
@ -29,7 +29,7 @@ func getCertPairs(certFiles []string) []string {
return result
}
//Get the cloest subdomain certificate from a list of domains
// Get the cloest subdomain certificate from a list of domains
func matchClosestDomainCertificate(subdomain string, domains []string) string {
var matchingDomain string = ""
maxLength := 0
@ -43,18 +43,3 @@ func matchClosestDomainCertificate(subdomain string, domains []string) string {
return matchingDomain
}
//Check if a requesting domain is a subdomain of a given domain
func isSubdomain(subdomain, domain string) bool {
subdomainParts := strings.Split(subdomain, ".")
domainParts := strings.Split(domain, ".")
if len(subdomainParts) < len(domainParts) {
return false
}
for i := range domainParts {
if subdomainParts[len(subdomainParts)-1-i] != domainParts[len(domainParts)-1-i] {
return false
}
}
return true
}

Some files were not shown because too many files have changed in this diff Show More