diff --git a/documentation/docs/components/operators/snippets/quic-bridge-deployment-script-setup.mdx b/documentation/docs/components/operators/snippets/quic-bridge-deployment-script-setup.mdx
index 592269d7283..0fd62d033f5 100644
--- a/documentation/docs/components/operators/snippets/quic-bridge-deployment-script-setup.mdx
+++ b/documentation/docs/components/operators/snippets/quic-bridge-deployment-script-setup.mdx
@@ -32,6 +32,10 @@ chmod +x quic_bridge_deployment.sh
```sh
./quic_bridge_deployment.sh full_bridge_setup
```
+- If you prefer a non-interactive mode, run the command with this variable (and skip next step):
+```sh
+NONINTERACTIVE=1 quick_bridge_deployment.sh full_bridge_setup
+```
###### 3. Follow the interactive prompts
- Make sure you don't just press enter to insert default values if your setup is different, for example in case of path to the config file
diff --git a/documentation/docs/components/operators/snippets/routing-conf.mdx b/documentation/docs/components/operators/snippets/routing-conf.mdx
new file mode 100644
index 00000000000..31c71484541
--- /dev/null
+++ b/documentation/docs/components/operators/snippets/routing-conf.mdx
@@ -0,0 +1,276 @@
+import { Callout } from 'nextra/components';
+import { Tabs } from 'nextra/components';
+import { Steps } from 'nextra/components';
+import { AccordionTemplate } from 'components/accordion-template.tsx';
+
+export const ManagerIPOutput = () => (
+
+ Correct ./network-tunnel-manager.sh fetch_and_display_ipv6 output
+
+);
+
+export const ManagerTablesOutput = () => (
+
+ Correct ./network-tunnel-manager.sh check_nymtun_iptables output
+
+);
+
+export const ShowTun = () => (
+
+ Correct ip addr show nymtun0 output
+
+);
+
+
+
+We recommend operators to configure their `nym-node` with the full routing configuration.
+
+However, most of the time the packets sent through the Mixnet are IPv4 based. The IPv6 packets are still pretty rare and therefore it's not mandatory from operational point of view to have this configuration implemented if you running only `mixnode` mode.
+
+If you preparing to run a `nym-node` with all modes enabled in the future, this setup is required.
+
+
+
+Networking configuration across different ISPs and various operation systems does not have a generic solution. If the provided configuration setup doesn't solve your problem check out [IPv6 troubleshooting](/operators/troubleshooting/vps-isp.mdx#ipv6-troubleshooting) page. Be aware that you may have to do more research, customised adjustments or contact your ISP to change settings for your VPS.
+ Callout>
+
+**Network Tunnel Manager ([`network-tunnel-manager.sh`](https://github.com/nymtech/nym/blob/develop/scripts/network_tunnel_manager.sh), NTM) is currently the one tool hadling the configuration of `nym-node` hosting server, according to the required design (node's [functionality](/operators/nodes/nym-node/setup#functionality-mode), WireGuard setup etc).**
+
+**NTM cand administrate these areas:**
+
+* IPv4 and IPv6 routing to the internet
+
+* The `nymtun0` interface (Mixnet / 5-hop): dynamically managed by the `exit-gateway` service. When the service is stopped, `nymtun0` disappears, and when started, `nymtun0` is recreated.
+
+* The `nymwg` interface (WG / 2-hop): used for creating a secure wireguard tunnel as part of the Nym Network configuration.
+
+* `iptables` rules specific to `nymwg` to ensure proper routing and forwarding through the wireguard tunnel. The `nymwg` interface needs to be correctly configured and active for the related commands to function properly. This includes applying or removing iptables rules and running connectivity tests through the `nymwg` tunnel.
+
+* WireGuard exit policy: Mixnet uses a common [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt), to apply the same for WG, the operators need to set that one up on their server using `iptables` rules.
+
+* Testing and validating all above
+
+**Before starting with the following configuration, make sure you have the [latest `nym-node` binary](https://github.com/nymtech/nym/releases) installed and your [VPS setup](../preliminary-steps/vps-setup.mdx) finished properly!**
+
+
+**Run the following steps as root!**
+ Callout>
+
+**Choose configuration command according your setup**
+
+
+
New nym-node code> full configuration,
+ Existing nym-node code> full configuration,
+ Step-by-step or Partial configuration
+ ]} defaultIndex={0}>
+
+This design is meant for operators setting up a new node on a fresh machine and it will result with a complete server readiness for routing as Entry Gateway and Exit Gateway in both Mixnet and WireGuard mode.
+
+
+###### 1. Download `network-tunnel-manager.sh`, make executable and run with `--help` command:
+
+```sh
+curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
+chmod +x network-tunnel-manager.sh && \
+./network-tunnel-manager.sh --help
+```
+
+###### 2. Make sure your `nym-node` service is up and running and bonded
+
+- **If you setting up a new node and not upgrading an existing one, keep it running and [bond](/operators/nodes/nym-node/bonding.mdx) your node now! Then come back here and follow the rest of the configuration.**
+
+###### 3. Run command for configuration:
+- Nodes with **WireGuard enabled**: Configures interfaces (`nymtun0` and `nymwg`), IPv4 and IPv6 routing, WireGuard exit policy and does validation tests
+```sh
+./network-tunnel-manager.sh complete_networking_configuration
+```
+- Nodes with **WireGuard disabled**: Does everything like the command above *without WireGuard exit policy*
+```sh
+./network-tunnel-manager.sh nym_tunnel_setup
+```
+ Steps>
+
+
+This is meant for operators configuring an existing and bonded node and it will result with a complete server readiness for routing as Entry Gateway and Exit Gateway in both Mixnet and WireGuard mode.
+
+
+###### 1. Download `network-tunnel-manager.sh`, make executable and run with `--help` command:
+
+```sh
+curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
+chmod +x network-tunnel-manager.sh && \
+./network-tunnel-manager.sh --help
+```
+
+###### 2. Run command for configuration:
+- Nodes with **WireGuard enabled**: Configures interfaces (`nymtun0` and `nymwg`), IPv4 and IPv6 routing, WireGuard exit policy and does validation tests
+```sh
+./network-tunnel-manager.sh complete_networking_configuration
+```
+- Nodes with **WireGuard disabled**: Does everything like the command above *without WireGuard exit policy*
+```sh
+./network-tunnel-manager.sh nym_tunnel_setup
+```
+ Steps>
+
+
+
+This design is meant for operators who want to do their server configuration step by step or choose only some parts of the setup.
+
+###### 1. Download `network-tunnel-manager.sh`, make executable and run with `--help` command:
+
+```sh
+curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
+chmod +x network-tunnel-manager.sh && \
+./network-tunnel-manager.sh --help
+```
+
+###### 2. Make sure your `nym-node` service is up and running and bonded
+
+- **If you setting up a new node and not upgrading an existing one, keep it running and [bond](/operators/nodes/nym-node/bonding.mdx) your node now! Then come back here and follow the rest of the configuration.**
+
+###### 3. Choose steps according your need
+
+> You should be certain in your selection when configuring only various parts of the server.
+
+###### Setup IP tables rules
+
+- Delete IP tables rules for IPv4 and IPv6 and apply new ones:
+```sh
+./network-tunnel-manager.sh remove_duplicate_rules nymtun0
+
+./network-tunnel-manager.sh apply_iptables_rules
+```
+
+- The process may prompt you if you want to save current IPv4 and IPv6 rules, choose yes.
+
+
+
+- At this point you should see a `global ipv6` address.
+```sh
+./network-tunnel-manager.sh fetch_and_display_ipv6
+```
+
+}>
+```sh
+iptables-persistent is already installed.
+Using IPv6 address: 2001:db8:a160::1/112 #the address will be different for you
+operation fetch_ipv6_address_nym_tun completed successfully.
+```
+
+
+###### Check Nymtun IP tables:
+
+```sh
+./network-tunnel-manager.sh check_nymtun_iptables
+```
+
+- If there's no process running it wouldn't return anything.
+- In case you see `nymtun0` but not active, this is probably because you are setting up a new (never bonded) node and not upgrading an existing one.
+
+
+}>
+```sh
+iptables-persistent is already installed.
+network Device: eth0
+---------------------------------------
+
+inspecting IPv4 firewall rules...
+Chain FORWARD (policy DROP 0 packets, 0 bytes)
+ 0 0 ufw-reject-forward all -- * * 0.0.0.0/0 0.0.0.0/0
+ 0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
+ 0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
+ 0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
+ 0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
+ 0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
+ 0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
+---------------------------------------
+
+inspecting IPv6 firewall rules...
+Chain FORWARD (policy DROP 0 packets, 0 bytes)
+ 0 0 ufw6-reject-forward all * * ::/0 ::/0
+ 0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
+ 0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
+ 0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
+ 0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
+ 0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
+ 0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
+operation check_nymtun_iptables completed successfully.
+```
+
+
+###### Remove old and apply new rules for wireguad routing
+
+```sh
+../network-tunnel-manager.sh remove_duplicate_rules nymwg
+
+./network-tunnel-manager.sh apply_iptables_rules_wg
+```
+
+###### Apply rules to configure DNS routing and allow ICMP piung test for node probing (network testing)
+
+```sh
+./network-tunnel-manager.sh configure_dns_and_icmp_wg
+```
+###### Adjust and validate IP forwarding
+
+```sh
+./network-tunnel-manager.sh adjust_ip_forwarding
+
+./network-tunnel-manager.sh check_ipv6_ipv4_forwarding
+```
+
+###### Check `nymtun0` interface and test routing configuration
+
+```sh
+ip addr show nymtun0
+```
+
+
+}>
+```sh
+# your addresses will be different
+8: nymtun0: mtu 1420 qdisc fq_codel state UNKNOWN group default qlen 500
+ link/none
+ inet 10.0.0.1/16 scope global nymtun0
+ valid_lft forever preferred_lft forever
+ inet6 fc00::1/112 scope global
+ valid_lft forever preferred_lft forever
+ inet6 fe80::ad08:d167:5700:8c7c/64 scope link stable-privacy
+ valid_lft forever preferred_lft forever`
+```
+
+
+- Validate your IPv6 and IPv4 networking by running a joke test via Mixnet:
+```sh
+./network-tunnel-manager.sh joke_through_the_mixnet
+```
+
+- Validate your tunneling by running a joke test via WG:
+```sh
+../network-tunnel-manager.sh joke_through_wg_tunnel
+```
+
+###### Enable wireguard
+
+Now you can run your node with the `--wireguard-enabled true` flag or add it to your [systemd service config](#systemd). Restart your `nym-node` or [systemd](#2-following-steps-for-nym-nodes-running-as-systemd-service) service (recommended):
+
+```sh
+systemctl daemon-reload && service nym-node restart
+```
+- Optionally, you can check if the node is running correctly by monitoring the service logs:
+
+```sh
+journalctl -u nym-node.service -f -n 100
+```
+ Steps>
+
+
+
+
+
+Note that the functionality the node runs in is decided by [arguments on the node itself / in node's `config.toml`](/operators/nodes/nym-node/setup#functionality-mode), this tool only prepares the server.
+ Callout>
+
+Make sure that you get the validation of all connectivity. If there are still any problems, please refer to [troubleshooting section](/operators/troubleshooting/vps-isp.mdx#incorrect-gateway-network-check).
diff --git a/documentation/docs/components/operators/snippets/wg-exit-policy-conf.mdx b/documentation/docs/components/operators/snippets/wg-exit-policy-conf.mdx
new file mode 100644
index 00000000000..7d5ebf3de70
--- /dev/null
+++ b/documentation/docs/components/operators/snippets/wg-exit-policy-conf.mdx
@@ -0,0 +1,68 @@
+import { Callout } from 'nextra/components';
+import { Tabs } from 'nextra/components';
+import { Steps } from 'nextra/components';
+import { AccordionTemplate } from 'components/accordion-template.tsx';
+import ExitPolicyInstallOutput from 'components/operators/snippets/wg-exit-policy-install-output.mdx';
+import ExitPolicyStatusOutput from 'components/operators/snippets/wg-exit-policy-status-output.mdx';
+
+
+**In case you had used `network-tunnel-manager.sh` with the command `complete_networking_setup`, your WireGuard exit policy is already setup. You can test it in the next chapter.**
+ Callout>
+
+Nym Node running as Exit Gateway has contains multiple modules, one of them is Nym Network Requester(NR), routing TCP traffic to the internet. To make sure that the node is not just an open proxy, NR checks agains [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) following these conditions (in this exact order):
+
+1. Do we explicitly block those IP addresses regardless of ports?
+2. Do we allow those specific ports regardless of IPs?
+3. Do block EVERYTHING else!
+
+The exit policy is same for all NRs, the content is shaped by an offchain governance of Nym Node operators on our [forum](https://forum.nym.com/t/poll-a-new-nym-exit-policy-for-exit-gateways-and-the-nym-mixnet-is-inbound/464).
+
+There is a caveat though. NR is only routing TCP streams and therefore any other type of routing than Mixnet is *not* filtered thorugh the exit policy. To ensure that Nym Nodes follow the same exit policy when routing IP packets through WireGuard and don't act as open proxies, the operators have to set up these rules via IP tables rules.
+
+**For all routing configuration we provide one tool [`network-tunnel-manager.sh` (NTM)](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh). This tool manages WireGuard exit policy as well.**
+
+In case you haven't run `network-tunnel-manager.sh` with the command `complete_networking_setup` you need to use NTM for WireGuard exit policy configuration.
+
+**Folow these steps**
+
+
+**Run the following steps as root!**
+ Callout>
+
+
+
+###### 1. Download `network-tunnel-manager.sh`, make executable and run with `--help` command:
+
+```sh
+curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
+chmod +x network-tunnel-manager.sh && \
+./network-tunnel-manager.sh --help
+```
+
+###### 2. Install exit policy
+
+- Clear old rules and configure new ones:
+```sh
+./network-tunnel-manager.sh exit_policy_clear
+./network-tunnel-manager.sh exit_policy_install
+```
+- The output should look like this:
+
+
+ AccordionTemplate>
+
+
+###### 3. Check status of your configuration
+```sh
+./network-tunnel-manager.sh exit_policy_status
+```
+
+- The output should look like this:
+
+
+ AccordionTemplate>
+ Steps>
+
+Now your WireGuard routing (2-hop) should have same rotuing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet) mode of NymVPN.
+
+
diff --git a/documentation/docs/components/operators/snippets/wg-exit-policy-test.mdx b/documentation/docs/components/operators/snippets/wg-exit-policy-test.mdx
new file mode 100644
index 00000000000..f8a25006f31
--- /dev/null
+++ b/documentation/docs/components/operators/snippets/wg-exit-policy-test.mdx
@@ -0,0 +1,39 @@
+import { Tabs } from 'nextra/components';
+import { Steps } from 'nextra/components';
+
+import ExitPolicyTestServer from 'components/operators/snippets/wg-exit-policy-testing-from-server.mdx';
+import ExitPolicyTestOutside from 'components/operators/snippets/wg-exit-policy-testing-from-outside.mdx';
+
+**For all routing configuration we provide one tool [`network-tunnel-manager.sh` (NTM)](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh). This tool manages WireGuard tests all configurations, including WireGuard exit policy as well.**
+
+
+You can use NTM to validate the application of the IP tables routes on your `nym-node` by checking it from the server side as well as from the outside.
+
+
+ From the server,
+ From the outside - using NymVPN
+ ]} defaultIndex={0}>
+
+
+
+
+
+
+If all works , your node has successfully implemented WireGuard exit policy with the same routing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet) for TCP routing.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-outside.mdx b/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-outside.mdx
index ebc11c315cc..349a7cd2def 100644
--- a/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-outside.mdx
+++ b/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-outside.mdx
@@ -1,9 +1,29 @@
+import { Steps } from 'nextra/components';
+
+Here are a few ways you can ensre your WireGuard exit policy is working correctly from the outside.
+
+
+
+###### 1. Using NymVPN
+
+- Connect to NymVPN and use your node as an Exit Gateway in dVPN (2-hop) mode
+
+- While connected to NymVPN, navigate to [`portquiz.net:12345`](http://portquiz.net:12345) and see if you can load the page
+
+- It shouldn't load, but if you navigate to some of the accepted ports, like[`portquiz.net:443`](http://portquiz.net:443) it should all work
+
+###### 2. Testing from your local terminal
+
- Install these dependencies on your local machine:
```shell
sudo apt install tcpdump
sudo tcpdump -i nymwg -n
```
-- Connect to [NymVPN](https://nym.com) and select your node as an Exit Gateway (after running the exit policy [manager script](#wireguard-exit-policy-configuration))
+- Connect to [NymVPN](https://nym.com) and select your node as an Exit Gateway
+
- Run the `tcpdump` command before registering
-- Have the output of the `echo $BLOCKED_IP` from your node and try to go into your browser or a registered client and try to connect - It will not resolve.
+
+- Have the output of the `echo $BLOCKED_IP` from your node and try to go into your browser or a registered client and try to connect - It should not resolve
+
+ Steps>
\ No newline at end of file
diff --git a/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-server.mdx b/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-server.mdx
index 02fe54adfab..501dcc38769 100644
--- a/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-server.mdx
+++ b/documentation/docs/components/operators/snippets/wg-exit-policy-testing-from-server.mdx
@@ -1,11 +1,39 @@
-Run this command to define variable `BLOCKED_IP` and try to `ping` it:
+import { Steps } from 'nextra/components';
+import { AccordionTemplate } from 'components/accordion-template.tsx';
+import ExitPolicyTestOutput from 'components/operators/snippets/wg-exit-policy-test-output.mdx';
+
+
+
+
+###### 1. Make sure to have the latest NTM:
+```sh
+curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
+chmod +x network-tunnel-manager.sh && \
+./network-tunnel-manager.sh --help
+```
+
+###### 2. Run tests with NTM
+```sh
+./network-tunnel-manager.sh exit_policy_test_connectivity
+./network-tunnel-manager.sh exit_policy_tests
+```
+
+- The output should look like this:
+
+
+ AccordionTemplate>
+
+###### 3. Optionally you can do some manual sanity checks
+- Run this command to define variable `BLOCKED_IP` and try to `ping` it:
```shell
BLOCKED_IP=$(grep "ExitPolicy reject" /etc/nym/exit-policy.txt | head -1 | sed -E 's/ExitPolicy reject ([^:]+):.*/\1/' | sed 's/\/.*$//')
ping -c 3 $BLOCKED_IP
```
-You should see `100% packet loss` as an outcome.
+- You should see `100% packet loss` as an outcome.
```shell
telnet $BLOCKED_IP 80
```
-You should see `telnet: Unable to connect to remote host: Connection timed out`.
+- You should see `telnet: Unable to connect to remote host: Connection timed out`.
+
+ Steps>
\ No newline at end of file
diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md
index 069e6eb1411..858227b92a0 100644
--- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md
+++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md
@@ -1 +1 @@
-0.77%
+0.79%
diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md
index 486924e8327..6c69256b943 100644
--- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md
+++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md
@@ -1 +1 @@
-37.305
+36.446
diff --git a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md
index bd9b415e4a5..bfd87316555 100644
--- a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md
+++ b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md
@@ -1 +1 @@
-Thursday, November 13th 2025, 11:23:33 UTC
+Thursday, November 20th 2025, 12:33:19 UTC
diff --git a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx
index 69afc1e41d5..d791e7f946c 100644
--- a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx
+++ b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx
@@ -3,34 +3,11 @@ import { Tabs } from 'nextra/components';
import { VarInfo } from 'components/variable-info.tsx';
import { Steps } from 'nextra/components';
import { AccordionTemplate } from 'components/accordion-template.tsx';
-import ExitPolicyInstallOutput from 'components/operators/snippets/wg-exit-policy-install-output.mdx';
-import ExitPolicyStatusOutput from 'components/operators/snippets/wg-exit-policy-status-output.mdx';
-import ExitPolicyTestOutput from 'components/operators/snippets/wg-exit-policy-test-output.mdx';
-import ExitPolicyTestServer from 'components/operators/snippets/wg-exit-policy-testing-from-server.mdx';
-import ExitPolicyTestOutside from 'components/operators/snippets/wg-exit-policy-testing-from-outside.mdx';
+import WGExitPolicyConf from 'components/operators/snippets/wg-exit-policy-conf.mdx';
+import WGExitPolicyTest from 'components/operators/snippets/wg-exit-policy-test.mdx';
+import RoutingConf from 'components/operators/snippets/routing-conf.mdx';
import QuicDeploymentSteps from 'components/operators/snippets/quic-bridge-deployment-script-setup.mdx';
-
-export const ManagerIPOutput = () => (
-
- Correct ./network_tunnel_manager.sh fetch_and_display_ipv6 output
-
-);
-
-export const ManagerTablesOutput = () => (
-
- Correct ./network_tunnel_manager.sh check_nymtun_iptables output
-
-);
-
-export const ShowTun = () => (
-
- Correct ip addr show nymtun0 output
-
-);
-
-
-
# Nym Node Configuration
@@ -222,9 +199,11 @@ This lets your operating system know it's ok to reload the service configuration
-## Connectivity Test and Configuration
+## Routing Configuration
+
+
-During our ongoing testing events we found out, that after introducing IP Packet Router (IPR) and [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) on embedded Network Requester (NR) by default, only a fragment of Gateways routes correctly through IPv4 and IPv6. We built a useful monitor to check out your Gateway (`nym-node --mode exit-gateway`) at [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net/).
+### Quick IPv6 Check
IPv6 routing is not only a case for gateways. Imagine a rare occasion when you run a `mixnode` without IPv6 enabled and a client will sent IPv6 packets through the Mixnet through such route:
```ascii
@@ -232,19 +211,6 @@ IPv6 routing is not only a case for gateways. Imagine a rare occasion when you r
```
In this (unusual) case your `mixnode` will not be able to route the packets. The node will drop the packets and its performance would go down. For that reason it's beneficial to have IPv6 enabled when running a `mixnode` functionality.
-
-We recommend operators to configure their `nym-node` with the full routing configuration.
-
-However, most of the time the packets sent through the Mixnet are IPv4 based. The IPv6 packets are still pretty rare and therefore it's not mandatory from operational point of view to have this configuration implemented if you running only `mixnode` mode.
-
-If you preparing to run a `nym-node` with all modes enabled in the future, this setup is required.
-
-
-
-For everyone participating in Delegation Program or Service Grant program, this setup is a requirement!
-
-
-### Quick IPv6 Check
You can always check IPv6 address and connectivity by using some of these methods:
@@ -273,267 +239,13 @@ telnet -6 ipv6.telnetmyip.com
Make sure to keep your IPv4 address enabled while setting up IPv6, as the majority of routing goes through that one!
-### Routing Configuration
-
-While we're working on Rust implementation to have these settings as a part of the binary build, to solve these connectivity requirements in the meantime we wrote a script [`network_tunnel_manager.sh`](https://github.com/nymtech/nym/blob/develop/scripts/network_tunnel_manager.sh) to support operators to configure their servers and address all the connectivity requirements.
-
-Networking configuration across different ISPs and various operation systems does not have a generic solution. If the provided configuration setup doesn't solve your problem check out [IPv6 troubleshooting](../../troubleshooting/vps-isp.mdx#ipv6-troubleshooting) page. Be aware that you may have to do more research, customised adjustments or contact your ISP to change settings for your VPS.
-
-The `nymtun0` interface is dynamically managed by the `exit-gateway` service. When the service is stopped, `nymtun0` disappears, and when started, `nymtun0` is recreated.
-
-The `nymwg` interface is used for creating a secure wireguard tunnel as part of the Nym Network configuration. Similar to `nymtun0`, the script manages iptables rules specific to `nymwg` to ensure proper routing and forwarding through the wireguard tunnel. The `nymwg` interface needs to be correctly configured and active for the related commands to function properly. This includes applying or removing iptables rules and running connectivity tests through the `nymwg` tunnel.
-
-The script should be used in a context where `nym-node` is running to fully utilise its capabilities, particularly for fetching IPv6 addresses or applying network rules that depend on the `nymtun0` and `nymwg` interfaces and to establish a WireGuard tunnel.
-
-**Before starting with the following configuration, make sure you have the [latest `nym-node` binary](https://github.com/nymtech/nym/releases) installed and your [VPS setup](../preliminary-steps/vps-setup.mdx) finished properly!**
-
-
-
-###### 1. Download `network_tunnel_manager.sh`, make executable and run:
-
-```sh
-curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/network_tunnel_manager.sh -o network_tunnel_manager.sh && \
-chmod +x network_tunnel_manager.sh && \
-./network_tunnel_manager.sh
-```
-
-###### 2. Make sure your `nym-node` service is up and running and bond
-
-- **If you setting up a new node and not upgrading an existing one, keep it running and [bond](bonding.mdx) your node now**. Then come back here and follow the rest of the configuration.
-
-
-**Run the following steps as root or with `sudo` prefix!**
-
-
-
-###### 3. Setup IP tables rules
-
-- Delete IP tables rules for IPv4 and IPv6 and apply new ones:
-```sh
-./network_tunnel_manager.sh remove_duplicate_rules nymtun0
-
-./network_tunnel_manager.sh apply_iptables_rules
-```
-
-- The process may prompt you if you want to save current IPv4 and IPv6 rules, choose yes.
-
-
-
-- At this point you should see a `global ipv6` address.
-```sh
-./network_tunnel_manager.sh fetch_and_display_ipv6
-```
-
-}>
-```sh
-iptables-persistent is already installed.
-Using IPv6 address: 2001:db8:a160::1/112 #the address will be different for you
-operation fetch_ipv6_address_nym_tun completed successfully.
-```
-
-
-###### 4. Check Nymtun IP tables:
-
-```sh
-./network_tunnel_manager.sh check_nymtun_iptables
-```
-
-- If there's no process running it wouldn't return anything.
-- In case you see `nymtun0` but not active, this is probably because you are setting up a new (never bonded) node and not upgrading an existing one.
-
-
-}>
-```sh
-iptables-persistent is already installed.
-network Device: eth0
----------------------------------------
-
-inspecting IPv4 firewall rules...
-Chain FORWARD (policy DROP 0 packets, 0 bytes)
- 0 0 ufw-reject-forward all -- * * 0.0.0.0/0 0.0.0.0/0
- 0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
- 0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
- 0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
- 0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
- 0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
- 0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
----------------------------------------
-
-inspecting IPv6 firewall rules...
-Chain FORWARD (policy DROP 0 packets, 0 bytes)
- 0 0 ufw6-reject-forward all * * ::/0 ::/0
- 0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
- 0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
- 0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
- 0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
- 0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
- 0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
-operation check_nymtun_iptables completed successfully.
-```
-
-
-###### 5. Remove old and apply new rules for wireguad routing
-
-```sh
-/network_tunnel_manager.sh remove_duplicate_rules nymwg
-
-./network_tunnel_manager.sh apply_iptables_rules_wg
-```
-
-###### 6. Apply rules to configure DNS routing and allow ICMP piung test for node probing (network testing)
-
-```sh
-./network_tunnel_manager.sh configure_dns_and_icmp_wg
-```
-###### 7. Adjust and validate IP forwarding
-
-```sh
-./network_tunnel_manager.sh adjust_ip_forwarding
-
-./network_tunnel_manager.sh check_ipv6_ipv4_forwarding
-```
-
-###### 8. Check `nymtun0` interface and test routing configuration
-
-```sh
-ip addr show nymtun0
-```
-
-
-}>
-```sh
-# your addresses will be different
-8: nymtun0: mtu 1420 qdisc fq_codel state UNKNOWN group default qlen 500
- link/none
- inet 10.0.0.1/16 scope global nymtun0
- valid_lft forever preferred_lft forever
- inet6 fc00::1/112 scope global
- valid_lft forever preferred_lft forever
- inet6 fe80::ad08:d167:5700:8c7c/64 scope link stable-privacy
- valid_lft forever preferred_lft forever`
-```
-
-
-- Validate your IPv6 and IPv4 networking by running a joke test via Mixnet:
-```sh
-./network_tunnel_manager.sh joke_through_the_mixnet
-```
-
-- Validate your tunneling by running a joke test via WG:
-```sh
-./network_tunnel_manager.sh joke_through_wg_tunnel
-```
-
-- **Note:** WireGuard will return only IPv4 joke, not IPv6. WG IPv6 is under development. Running IPR joke through the mixnet with `./network_tunnel_manager.sh joke_through_the_mixnet` should work with both IPv4 and IPv6!
-
-
-###### 9. Enable wireguard
-
-Now you can run your node with the `--wireguard-enabled true` flag or add it to your [systemd service config](#systemd). Restart your `nym-node` or [systemd](#2-following-steps-for-nym-nodes-running-as-systemd-service) service (recommended):
-
-```sh
-systemctl daemon-reload && service nym-node restart
-```
-- Optionally, you can check if the node is running correctly by monitoring the service logs:
-
-```sh
-journalctl -u nym-node.service -f -n 100
-```
-
-
-Make sure that you get the validation of all connectivity. If there are still any problems, please refer to [troubleshooting section](../../troubleshooting/vps-isp.mdx#incorrect-gateway-network-check).
-
## Wireguard Exit Policy Configuration
-Nym Node running as Exit Gateway has contains multiple modules, one of them is Nym Network Requester(NR), routing TCP traffic to the internet. To make sure that the node is not just an open proxy, NR checks agains [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) following these conditions (in this exact order):
-
-1. Do we explicitly block those IP addresses regardless of ports?
-2. Do we allow those specific ports regardless of IPs?
-3. Do block EVERYTHING else!
-
-The exit policy is same for all NRs, the content is shaped by an offchain governance of Nym Node operators on our [forum](https://forum.nym.com/t/poll-a-new-nym-exit-policy-for-exit-gateways-and-the-nym-mixnet-is-inbound/464).
-
-There is a caveat though. NR is only routing TCP streams and therefore any other type of routing is *not* filtered thorugh the exit policy. To ensure that Nym Nodes follow the same exit policy when routing IP packets through wireguard and don't act as open proxies, the operators have to set up these rules via IP tables rules.
-
-**Follow these steps, using a [setup script](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh) and [testing scripts](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/exit-policy-tests.sh) written by Nym quality assurance team, to setup exit policy for wireguard:**
-
-
-
-###### 1. Download the scripts and make executable
-
-- SSH to your node
-- Create a folder `~/nym-binaries` and navigate there
-```sh
-mkdir $HOME/nym-binaries
-cd $HOME/nym-binaries
-```
-- Download the scripts
-```sh
-wget https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh
-
-wget https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/exit-policy-tests.sh
-```
-- Make executable
-```sh
-chmod +x wireguard-exit-policy-manager.sh exit-policy-tests.sh
-```
-
-###### 2. Install `wireguard-exit-policy-manager.sh`
-```sh
-./wireguard-exit-policy-manager.sh install
-```
-- The output should look like this:
-
-
- AccordionTemplate>
-
-
-###### 3. Run `wireguard-exit-policy-manager.sh`
-```sh
-./wireguard-exit-policy-manager.sh status
-```
-
-- The output should look like this:
-
-
- AccordionTemplate>
-
-###### 4. Test with `exit-policy-tests.sh`
-
-```sh
-./exit-policy-tests.sh
-```
-
-- The output should look like this:
-
-
- AccordionTemplate>
-
-###### 5. In case of problems, you can clear the exit policy rule
-```sh
-./wireguard-exit-policy-manager.sh clear
-
-./wireguard-exit-policy-manager.sh status
-```
- Steps>
-
-Now your wireguart routing should have same rotuing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet) mode of NymVPN.
+
### Testing Wireguard Exit Policy
-You can validate the application of the IP tables routes on your `nym-node` by checking it from the server side as well as from the outside.
-
-
- From the server,
- From the outside - using NymVPN
- ]} defaultIndex={0}>
-
-
-
-
-
-Your node has successfully implemented wireguard exit policy with the same routing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet).
+
## QUIC Transport Bridge Deployment
diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh
deleted file mode 100644
index 58eff34565d..00000000000
--- a/scripts/network_tunnel_manager.sh
+++ /dev/null
@@ -1,290 +0,0 @@
-#!/bin/bash
-
-network_device=$(ip route show default | awk '/default/ {print $5}')
-tunnel_interface="nymtun0"
-wg_tunnel_interface="nymwg"
-
-if ! dpkg -s iptables-persistent >/dev/null 2>&1; then
- sudo apt-get update
- sudo apt-get install -y iptables-persistent
-else
- echo "iptables-persistent is already installed."
-fi
-
-fetch_ipv6_address() {
- local interface=$1
- ipv6_global_address=$(ip -6 addr show "$interface" scope global | grep inet6 | awk '{print $2}' | head -n 1)
-
- if [[ -z "$ipv6_global_address" ]]; then
- echo "no globally routable IPv6 address found on $interface. Please configure IPv6 or check your network settings."
- exit 1
- else
- echo "using IPv6 address: $ipv6_global_address"
- fi
-}
-
-fetch_and_display_ipv6() {
- ipv6_address=$(ip -6 addr show "$network_device" scope global | grep inet6 | awk '{print $2}')
- if [[ -z "$ipv6_address" ]]; then
- echo "no global IPv6 address found on $network_device."
- else
- echo "IPv6 address on $network_device: $ipv6_address"
- fi
-}
-
-remove_duplicate_rules() {
- local interface=$1
- local script_name=$(basename "$0")
-
- if [[ -z "$interface" ]]; then
- echo "error: no interface specified. please enter the interface (nymwg or nymtun0):"
- read -r interface
- fi
-
- if [[ "$interface" != "nymwg" && "$interface" != "nymtun0" ]]; then
- echo "error: invalid interface '$interface'. allowed values are 'nymwg' or 'nymtun0'." >&2
- exit 1
- fi
-
- echo "removing duplicate rules for $interface..."
-
- iptables-save | grep "$interface" | while read -r line; do
- sudo iptables -D ${line#-A } || echo "Failed to delete rule: $line"
- done
-
- ip6tables-save | grep "$interface" | while read -r line; do
- sudo ip6tables -D ${line#-A } || echo "Failed to delete rule: $line"
- done
-
- echo "duplicates removed for $interface."
- echo "!!-important-!! you need to now reapply the iptables rules for $interface."
- if [ "$interface" == "nymwg" ]; then
- echo "run: ./$script_name apply_iptables_rules_wg"
- else
- echo "run: ./$script_name apply_iptables_rules"
- fi
-}
-
-adjust_ip_forwarding() {
- ipv6_forwarding_setting="net.ipv6.conf.all.forwarding=1"
- ipv4_forwarding_setting="net.ipv4.ip_forward=1"
-
- # remove duplicate entries for these settings from the file
- sudo sed -i "/^net.ipv6.conf.all.forwarding=/d" /etc/sysctl.conf
- sudo sed -i "/^net.ipv4.ip_forward=/d" /etc/sysctl.conf
-
- echo "$ipv6_forwarding_setting" | sudo tee -a /etc/sysctl.conf
- echo "$ipv4_forwarding_setting" | sudo tee -a /etc/sysctl.conf
-
- sudo sysctl -p /etc/sysctl.conf
-
-}
-
-apply_iptables_rules() {
- local interface=$1
- echo "applying IPtables rules for $interface..."
- sleep 2
-
- sudo iptables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE
- sudo iptables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT
- sudo iptables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT
-
- sudo ip6tables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE
- sudo ip6tables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT
- sudo ip6tables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT
-
- sudo iptables-save | sudo tee /etc/iptables/rules.v4
- sudo ip6tables-save | sudo tee /etc/iptables/rules.v6
-}
-
-check_tunnel_iptables() {
- local interface=$1
- echo "inspecting IPtables rules for $interface..."
- echo "---------------------------------------"
- echo "IPv4 rules:"
- iptables -L FORWARD -v -n | awk -v dev="$interface" '/^Chain FORWARD/ || $0 ~ dev || $0 ~ "ufw-reject-forward"'
- echo "---------------------------------------"
- echo "IPv6 rules:"
- ip6tables -L FORWARD -v -n | awk -v dev="$interface" '/^Chain FORWARD/ || $0 ~ dev || $0 ~ "ufw6-reject-forward"'
-}
-
-check_ipv6_ipv4_forwarding() {
- result_ipv4=$(cat /proc/sys/net/ipv4/ip_forward)
- result_ipv6=$(cat /proc/sys/net/ipv6/conf/all/forwarding)
- echo "IPv4 forwarding is $([ "$result_ipv4" == "1" ] && echo "enabled" || echo "not enabled")."
- echo "IPv6 forwarding is $([ "$result_ipv6" == "1" ] && echo "enabled" || echo "not enabled")."
-}
-
-check_ip_routing() {
- echo "IPv4 routing table:"
- ip route
- echo "---------------------------------------"
- echo "IPv6 routing table:"
- ip -6 route
-}
-
-perform_pings() {
- echo "performing IPv4 ping to google.com..."
- ping -c 4 google.com
- echo "---------------------------------------"
- echo "performing IPv6 ping to google.com..."
- ping6 -c 4 google.com
-}
-
-joke_through_tunnel() {
- local interface=$1
- local green="\033[0;32m"
- local reset="\033[0m"
- local red="\033[0;31m"
- local yellow="\033[0;33m"
-
- sleep 1
- echo
- echo -e "${yellow}checking tunnel connectivity and fetching a joke for $interface...${reset}"
- echo -e "${yellow}if these test succeeds, it confirms your machine can reach the outside world via IPv4 and IPv6.${reset}"
- echo -e "${yellow}however, probes and external clients may experience different connectivity to your nym-node.${reset}"
-
- ipv4_address=$(ip addr show "$interface" | awk '/inet / {print $2}' | cut -d'/' -f1)
- ipv6_address=$(ip addr show "$interface" | awk '/inet6 / && $2 !~ /^fe80/ {print $2}' | cut -d'/' -f1)
-
- if [[ -z "$ipv4_address" && -z "$ipv6_address" ]]; then
- echo -e "${red}no IP address found on $interface. unable to fetch a joke.${reset}"
- echo -e "${red}please verify your tunnel configuration and ensure the interface is up.${reset}"
- return 1
- fi
-
- if [[ -n "$ipv4_address" ]]; then
- echo
- echo -e "------------------------------------"
- echo -e "detected IPv4 address: $ipv4_address"
- echo -e "testing IPv4 connectivity..."
- echo
-
- if ping -c 1 -I "$ipv4_address" google.com >/dev/null 2>&1; then
- echo -e "${green}IPv4 connectivity is working. fetching a joke...${reset}"
- joke=$(curl -s -H "Accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -r .joke)
- [[ -n "$joke" && "$joke" != "null" ]] && echo -e "${green}IPv4 joke: $joke${reset}" || echo -e "failed to fetch a joke via IPv4."
- else
- echo -e "${red}IPv4 connectivity is not working for $interface. verify your routing and NAT settings.${reset}"
- fi
- else
- echo -e "${red}no IPv4 address found on $interface. unable to fetch a joke via IPv4.${reset}"
- fi
-
- if [[ -n "$ipv6_address" ]]; then
- echo
- echo -e "------------------------------------"
- echo -e "detected IPv6 address: $ipv6_address"
- echo -e "testing IPv6 connectivity..."
- echo
-
- if ping6 -c 1 -I "$ipv6_address" google.com >/dev/null 2>&1; then
- echo -e "${green}IPv6 connectivity is working. fetching a joke...${reset}"
- joke=$(curl -s -H "Accept: application/json" --interface "$ipv6_address" https://icanhazdadjoke.com/ | jq -r .joke)
- [[ -n "$joke" && "$joke" != "null" ]] && echo -e "${green}IPv6 joke: $joke${reset}" || echo -e "${red}failed to fetch a joke via IPv6.${reset}"
- else
- echo -e "${red}IPv6 connectivity is not working for $interface. verify your routing and NAT settings.${reset}"
- fi
- else
- echo -e "${red}no IPv6 address found on $interface. unable to fetch a joke via IPv6.${reset}"
- fi
-
- echo -e "${green}joke fetching processes completed for $interface.${reset}"
- echo -e "------------------------------------"
-
- sleep 3
- echo
- echo
- echo -e "${yellow}### connectivity testing recommendations ###${reset}"
- echo -e "${yellow}- use the following command to test WebSocket connectivity from an external client:${reset}"
- echo -e "${yellow} wscat -c wss://:9001 ${reset}"
- echo -e "${yellow}- test UDP connectivity on port 51822 (commonly used for nym wireguard) ${reset}"
- echo -e "${yellow} from another machine, use tools like nc or socat to send UDP packets ${reset}"
- echo -e "${yellow} echo 'test message' | nc -u 51822 ${reset}"
- echo -e "${yellow}if connectivity issues persist, ensure port forwarding and firewall rules are correctly configured ${reset}"
- echo
-}
-
-
-configure_dns_and_icmp_wg() {
- echo "allowing icmp (ping)..."
- sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
- sudo iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
-
- echo "allowing dns over udp (port 53)..."
- sudo iptables -A INPUT -p udp --dport 53 -j ACCEPT
-
- echo "allowing dns over tcp (port 53)..."
- sudo iptables -A INPUT -p tcp --dport 53 -j ACCEPT
-
- echo "saving iptables rules..."
- sudo iptables-save >/etc/iptables/rules.v4
-
- echo "dns and icmp configuration completed."
-}
-
-case "$1" in
-fetch_ipv6_address_nym_tun)
- fetch_ipv6_address "$tunnel_interface"
- ;;
-fetch_and_display_ipv6)
- fetch_and_display_ipv6
- ;;
-apply_iptables_rules)
- apply_iptables_rules "$tunnel_interface"
- ;;
-apply_iptables_rules_wg)
- apply_iptables_rules "$wg_tunnel_interface"
- ;;
-check_nymtun_iptables)
- check_tunnel_iptables "$tunnel_interface"
- ;;
-check_nym_wg_tun)
- check_tunnel_iptables "$wg_tunnel_interface"
- ;;
-check_ipv6_ipv4_forwarding)
- check_ipv6_ipv4_forwarding
- ;;
-check_ip_routing)
- check_ip_routing
- ;;
-perform_pings)
- perform_pings
- ;;
-joke_through_the_mixnet)
- joke_through_tunnel "$tunnel_interface"
- ;;
-joke_through_wg_tunnel)
- joke_through_tunnel "$wg_tunnel_interface"
- ;;
-configure_dns_and_icmp_wg)
- configure_dns_and_icmp_wg
- ;;
-adjust_ip_forwarding)
- adjust_ip_forwarding
- ;;
-remove_duplicate_rules)
- remove_duplicate_rules "$2"
- ;;
-*)
- echo "Usage: $0 [command]"
- echo "Commands:"
- echo " fetch_ipv6_address_nym_tun - Fetch IPv6 for nymtun0."
- echo " fetch_and_display_ipv6 - Show IPv6 on default device."
- echo " apply_iptables_rules - Apply IPtables rules for nymtun0."
- echo " apply_iptables_rules_wg - Apply IPtables rules for nymwg."
- echo " check_nymtun_iptables - Check IPtables for nymtun0."
- echo " check_nym_wg_tun - Check IPtables for nymwg."
- echo " check_ipv6_ipv4_forwarding - Check IPv4 and IPv6 forwarding."
- echo " check_ip_routing - Display IP routing tables."
- echo " perform_pings - Test IPv4 and IPv6 connectivity."
- echo " joke_through_the_mixnet - Fetch a joke via nymtun0."
- echo " joke_through_wg_tunnel - Fetch a joke via nymwg."
- echo " configure_dns_and_icmp_wg - Allows icmp ping tests for probes alongside configuring dns"
- echo " adjust_ip_forwarding - Enable IPV6 and IPV4 forwarding"
- echo " remove_duplicate_rules - Remove duplicate iptables rules. Valid interfaces: nymwg, nymtun0"
- exit 1
- ;;
-esac
-
-echo "operation $1 completed successfully."
diff --git a/scripts/nym-node-setup/network-tunnel-manager.sh b/scripts/nym-node-setup/network-tunnel-manager.sh
new file mode 100644
index 00000000000..ab941e89b56
--- /dev/null
+++ b/scripts/nym-node-setup/network-tunnel-manager.sh
@@ -0,0 +1,1352 @@
+#!/bin/bash
+# nym tunnel and wireguard exit policy manager
+# run this script as root
+
+set -euo pipefail
+set +o errtrace
+
+
+###############################################################################
+# colors (no emojis)
+###############################################################################
+GREEN='\033[0;32m'
+RED='\033[0;31m'
+YELLOW='\033[0;33m'
+NC='\033[0m'
+CYAN='\033[0;36m'
+RESET='\033[0m'
+
+info() {
+ printf "%b\n" "${YELLOW}[INFO] $*${NC}"
+}
+
+ok() {
+ printf "%b\n" "${GREEN}[OK] $*${NC}"
+}
+
+error() {
+ printf "%b\n" "${RED}[ERROR] $*${NC}"
+}
+
+###############################################################################
+# safety: must run as root, jq
+###############################################################################
+if [ "$(id -u)" -ne 0 ]; then
+ error "This script must be run as root"
+ exit 1
+fi
+
+###############################################################################
+# Logging
+###############################################################################
+LOG_FILE="/var/log/nym/network_tunnel_manager.log"
+mkdir -p "$(dirname "$LOG_FILE")"
+touch "$LOG_FILE"
+chmod 640 "$LOG_FILE"
+
+# rotate log if >10MB
+if [[ -f "$LOG_FILE" && $(stat -c%s "$LOG_FILE") -gt 10485760 ]]; then
+ mv "$LOG_FILE" "${LOG_FILE}.1"
+ touch "$LOG_FILE"
+ chmod 640 "$LOG_FILE"
+fi
+
+echo "----- $(date '+%Y-%m-%d %H:%M:%S') START network-tunnel-manager -----" | tee -a "$LOG_FILE"
+echo -e "${CYAN}Logs are being saved locally to:${RESET} $LOG_FILE"
+echo -e "${CYAN}These logs never leave your machine.${RESET}"
+echo "" | tee -a "$LOG_FILE"
+
+# safe logger
+log() {
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
+}
+
+# global redirection, strip ANSI before writing to log
+add_log_redirection() {
+ exec > >(tee >(sed -u 's/\x1b\[[0-9;]*m//g' >> "$LOG_FILE"))
+ exec 2> >(tee >(sed -u 's/\x1b\[[0-9;]*m//g' >> "$LOG_FILE") >&2)
+}
+add_log_redirection
+
+
+trap 'log "ERROR: exit=$? line=$LINENO cmd=$(printf "%q" "$BASH_COMMAND")"' ERR
+
+
+
+
+START_TIME=$(date +%s)
+
+###############################################################################
+# basic config
+###############################################################################
+
+NYM_CHAIN="NYM-EXIT"
+POLICY_FILE="/etc/nym/exit-policy.txt"
+EXIT_POLICY_LOCATION="https://nymtech.net/.wellknown/network-requester/exit-policy.txt"
+
+TUNNEL_INTERFACE="${TUNNEL_INTERFACE:-nymtun0}"
+WG_INTERFACE="${WG_INTERFACE:-nymwg}"
+
+# Function to detect and validate uplink interface
+detect_uplink_interface() {
+ local cmd="$1"
+ local dev
+
+ dev="$(eval "$cmd" 2>/dev/null | awk '{print $5}' | head -n1 || true)"
+
+ if [[ -n "$dev" && "$dev" =~ ^[a-zA-Z0-9._-]+$ ]]; then
+ echo "$dev"
+ else
+ echo ""
+ fi
+}
+
+# uplink device detection, can be overridden
+NETWORK_DEVICE="${NETWORK_DEVICE:-}"
+if [[ -z "$NETWORK_DEVICE" ]]; then
+ NETWORK_DEVICE="$(detect_uplink_interface "ip -o route show default")"
+fi
+if [[ -z "$NETWORK_DEVICE" ]]; then
+ NETWORK_DEVICE="$(detect_uplink_interface "ip -o route show default table all")"
+fi
+if [[ -z "$NETWORK_DEVICE" ]]; then
+ error "cannot determine uplink interface. set NETWORK_DEVICE or UPLINK_DEV"
+ exit 1
+fi
+
+###############################################################################
+# shared helpers
+###############################################################################
+
+ensure_jq() {
+ info "checking for jq..."
+
+ if command -v jq >/dev/null 2>&1; then
+ ok "jq is already installed"
+ else
+ info "jq not found, installing..."
+ apt-get update -y
+ DEBIAN_FRONTEND=noninteractive apt-get install -y jq
+
+ if command -v jq >/dev/null 2>&1; then
+ ok "jq installed successfully"
+ else
+ error "failed to install jq"
+ exit 1
+ fi
+ fi
+}
+
+install_iptables_persistent() {
+ if ! dpkg -s iptables-persistent >/dev/null 2>&1; then
+ info "installing iptables-persistent"
+ apt-get update -y
+ DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent
+ else
+ ok "iptables-persistent is already installed"
+ fi
+}
+
+adjust_ip_forwarding() {
+ info "configuring ip forwarding via /etc/sysctl.d/99-nym-forwarding.conf"
+ install -m 0644 /dev/null /etc/sysctl.d/99-nym-forwarding.conf
+ cat > /etc/sysctl.d/99-nym-forwarding.conf </dev/null || echo 0)
+ v6=$(cat /proc/sys/net/ipv6/conf/all/forwarding 2>/dev/null || echo 0)
+
+ if [[ "$v4" == "1" && "$v6" == "1" ]]; then
+ ok "ipv4 and ipv6 forwarding enabled"
+ else
+ error "warning: ip forwarding not fully enabled (ipv4=$v4 ipv6=$v6)"
+ fi
+}
+
+save_iptables_rules() {
+ info "saving iptables rules to /etc/iptables$"
+ mkdir -p /etc/iptables
+ iptables-save > /etc/iptables/rules.v4
+ ip6tables-save > /etc/iptables/rules.v6
+ ok "iptables rules saved"
+}
+
+###############################################################################
+# part 1: network tunnel manager (nymtun0 + nymwg base nat/forwarding)
+###############################################################################
+
+fetch_ipv6_address() {
+ local interface=$1
+ local ipv6_global_address
+ ipv6_global_address=$(ip -6 addr show "$interface" scope global | awk '/inet6/ {print $2}' | head -n 1)
+
+ if [[ -z "$ipv6_global_address" ]]; then
+ error "no globally routable ipv6 address found on $interface. please configure ipv6 or check your network settings"
+ exit 1
+ else
+ info "using ipv6 address: $ipv6_global_address"
+ fi
+}
+
+fetch_and_display_ipv6() {
+ local ipv6_address
+ ipv6_address=$(ip -6 addr show "$NETWORK_DEVICE" scope global | awk '/inet6/ {print $2}')
+ if [[ -z "$ipv6_address" ]]; then
+ error "no global ipv6 address found on $NETWORK_DEVICE"
+ else
+ ok "ipv6 address on $NETWORK_DEVICE: $ipv6_address"
+ fi
+}
+
+# dedupe / clean-up rules for an interface in FORWARD and NYM-EXIT
+# keeps a single copy of each rule
+
+remove_duplicate_rules() {
+ local interface="$1"
+
+ if [[ -z "$interface" ]]; then
+ error "Error: No interface specified. Usage: $0 remove_duplicate_rules "
+ exit 1
+ fi
+
+ info "detecting and removing duplicate rules for $interface in FORWARD and ${NYM_CHAIN}"
+
+ #
+ # ipv4
+ #
+ local rules_v4
+ rules_v4=$(iptables-save | grep -E "(-A FORWARD|-A $NYM_CHAIN)" | grep -F -- "$interface" || true)
+
+ if [[ -n "$rules_v4" ]]; then
+ info "processing ipv4 rules"
+
+ local tmp4
+ tmp4=$(mktemp)
+ printf "%s\n" "$rules_v4" | sort | uniq > "$tmp4"
+
+ local rule count cleaned chain rest match index
+ while IFS= read -r rule; do
+ [[ -z "$rule" ]] && continue
+
+ # FIX: protect grep from rule content becoming flags
+ count=$(printf "%s\n" "$rules_v4" | grep -F -- "$rule" | wc -l)
+
+ if [[ "$count" -gt 1 ]]; then
+ info "removing $((count - 1)) duplicate(s) of ipv4 rule: $rule"
+
+ for ((i=1; i/dev/null; then
+ iptables -t filter -D "$chain" "${RULE_ARR[@]}" && continue
+ fi
+
+ match=$(iptables -S | grep -F -- "$cleaned" | head -n1 || true)
+
+ if [[ -n "$match" ]]; then
+ chain=$(echo "$match" | awk '{print $2}')
+ index=$(iptables -L "$chain" --line-numbers | grep -F "$interface" | awk 'NR==1{print $1}')
+
+ if [[ -n "$index" ]]; then
+ iptables -D "$chain" "$index" 2>/dev/null || \
+ error "warning: failed deleting ipv4 duplicate via index ($chain $index)"
+ else
+ error "warning: unable to locate ipv4 duplicate index for: $rule"
+ fi
+ else
+ error "warning: could not reliably match ipv4 duplicate rule: $rule"
+ fi
+ done
+ fi
+
+ done < "$tmp4"
+
+ rm -f "$tmp4"
+
+ else
+ ok "no ipv4 rules found for $interface to deduplicate"
+ fi
+
+
+
+ #
+ # ipv6
+ #
+ local rules_v6
+ rules_v6=$(ip6tables-save | grep -E "(-A FORWARD|-A $NYM_CHAIN)" | grep -F -- "$interface" || true)
+
+ if [[ -n "$rules_v6" ]]; then
+ info "processing ipv6 rules"
+
+ local tmp6
+ tmp6=$(mktemp)
+ printf "%s\n" "$rules_v6" | sort | uniq > "$tmp6"
+
+ local rule count cleaned chain rule_spec match index
+ while IFS= read -r rule; do
+ [[ -z "$rule" ]] && continue
+
+ # FIX: protect grep from interpreting rule as flags
+ count=$(printf "%s\n" "$rules_v6" | grep -F -- "$rule" | wc -l)
+
+ if [[ "$count" -gt 1 ]]; then
+ info "removing $((count - 1)) duplicate(s) of ipv6 rule: $rule"
+
+ for ((i=1; i/dev/null; then
+ ip6tables -t filter -D "$chain" "${RULE6_ARR[@]}" && continue
+ fi
+
+ match=$(ip6tables -S | grep -F -- "$cleaned" | head -n1 || true)
+
+ if [[ -n "$match" ]]; then
+ chain=$(echo "$match" | awk '{print $2}')
+
+ index=$(ip6tables -L "$chain" --line-numbers | grep -F "$interface" | awk 'NR==1{print $1}')
+
+ if [[ -n "$index" ]]; then
+ ip6tables -D "$chain" "$index" 2>/dev/null || \
+ error "warning: failed deleting ipv6 duplicate via index ($chain $index)"
+ else
+ error "warning: unable to locate ipv6 duplicate index for: $rule"
+ fi
+ else
+ error "warning: could not match ipv6 duplicate rule reliably: $rule"
+ fi
+ done
+ fi
+
+ done < "$tmp6"
+
+ rm -f "$tmp6"
+
+ else
+ ok "no ipv6 rules found for $interface to deduplicate"
+ fi
+
+ ok "duplicate rule scan completed for $interface"
+}
+
+apply_iptables_rules() {
+ local interface=$1
+ info "applying iptables rules for $interface using uplink $NETWORK_DEVICE"
+ sleep 1
+
+ # ipv4 nat and forwarding
+ iptables -t nat -C POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE 2>/dev/null || \
+ iptables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE
+
+ iptables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || \
+ iptables -I FORWARD 1 -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT
+
+ iptables -C FORWARD -i "$NETWORK_DEVICE" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \
+ iptables -I FORWARD 2 -i "$NETWORK_DEVICE" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT
+
+ # ipv6 nat and forwarding
+ ip6tables -t nat -C POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE 2>/dev/null || \
+ ip6tables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE
+
+ ip6tables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || \
+ ip6tables -I FORWARD 1 -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT
+
+ ip6tables -C FORWARD -i "$NETWORK_DEVICE" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \
+ ip6tables -I FORWARD 2 -i "$NETWORK_DEVICE" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT
+
+ save_iptables_rules
+}
+
+check_tunnel_iptables() {
+ local interface=$1
+ info "inspecting iptables rules for $interface"
+ info "ipv4 forward chain:"
+ iptables -L FORWARD -v -n | awk -v dev="$interface" '/^Chain FORWARD/ || $0 ~ dev || $0 ~ "ufw-reject-forward"'
+ echo
+ info "ipv6 forward chain:"
+ ip6tables -L FORWARD -v -n | awk -v dev="$interface" '/^Chain FORWARD/ || $0 ~ dev || $0 ~ "ufw6-reject-forward"'
+}
+
+check_ipv6_ipv4_forwarding() {
+ local result_ipv4 result_ipv6
+ result_ipv4=$(cat /proc/sys/net/ipv4/ip_forward)
+ result_ipv6=$(cat /proc/sys/net/ipv6/conf/all/forwarding)
+ ok "ipv4 forwarding is $([ "$result_ipv4" == "1" ] && ok enabled || error not enabled)"
+ ok "ipv6 forwarding is $([ "$result_ipv6" == "1" ] && ok enabled || error not enabled)"
+}
+
+check_ip_routing() {
+ info "ipv4 routing table:"
+ ip route
+ info "---------------------------"
+ info "ipv6 routing table:"
+ ip -6 route
+}
+
+perform_pings() {
+ info "performing ipv4 ping to google.com"
+ ping -4 -c 4 google.com || error "ipv4 ping failed"
+ echo "---------------------------"
+ info "performing ipv6 ping to google.com"
+ ping6 -6 -c 4 google.com || error "ipv6 ping failed"
+}
+
+joke_through_tunnel() {
+ ensure_jq
+ local interface=$1
+
+ sleep 1
+ echo
+ info "checking tunnel connectivity and fetching a joke for $interface"
+ info "if this test succeeds, it confirms your machine can reach the outside world via ipv4 and ipv6"
+ info "probes and external clients may still see different connectivity to your nym node"
+
+ local ipv4_address ipv6_address joke
+ ipv4_address=$(ip addr show "$interface" | awk '/inet / {print $2}' | cut -d'/' -f1)
+ ipv6_address=$(ip addr show "$interface" | awk '/inet6 / && $2 !~ /^fe80/ {print $2}' | cut -d'/' -f1)
+
+ if [[ -z "$ipv4_address" && -z "$ipv6_address" ]]; then
+ error "no ip address found on $interface. unable to fetch a joke"
+ error "please verify your tunnel configuration and ensure the interface is up"
+ return 1
+ fi
+
+ if [[ -n "$ipv4_address" ]]; then
+ echo
+ echo "------------------------------------"
+ info "detected ipv4 address: $ipv4_address"
+ info "testing ipv4 connectivity"
+ echo
+
+ if ping -c 1 -I "$ipv4_address" google.com >/dev/null 2>&1; then
+ ok "ipv4 connectivity is working. fetching a joke"
+ joke=$(curl -s -H "Accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -r .joke)
+ [[ -n "$joke" && "$joke" != "null" ]] && ok "ipv4 joke: $joke" || echo "failed to fetch a joke via ipv4"
+ else
+ error "ipv4 connectivity is not working for $interface. verify your routing and nat settings"
+ fi
+ else
+ error "no ipv4 address found on $interface. unable to fetch a joke via ipv4"
+ fi
+
+ if [[ -n "$ipv6_address" ]]; then
+ echo
+ echo "------------------------------------"
+ info "detected ipv6 address: $ipv6_address"
+ info "testing ipv6 connectivity"
+ echo
+
+ if ping6 -c 1 -I "$ipv6_address" google.com >/dev/null 2>&1; then
+ ok "ipv6 connectivity is working. fetching a joke"
+ joke=$(curl -s -H "Accept: application/json" --interface "$ipv6_address" https://icanhazdadjoke.com/ | jq -r .joke)
+ [[ -n "$joke" && "$joke" != "null" ]] && ok "ipv6 joke: $joke" || error "failed to fetch a joke via ipv6"
+ else
+ error "ipv6 connectivity is not working for $interface. verify your routing and nat settings"
+ fi
+ else
+ error "no ipv6 address found on $interface. unable to fetch a joke via ipv6"
+ fi
+
+ ok "joke fetching processes completed for $interface"
+ echo "------------------------------------"
+
+ sleep 3
+ echo
+ echo
+ info "connectivity testing recommendations"
+ info "- from another machine use wscat to test websocket connectivity on 9001"
+ info "- test udp connectivity on port 51822 (wireguard)"
+ info "- example: echo 'test' | nc -u 51822"
+}
+
+configure_dns_and_icmp_wg() {
+ info "allowing ping (icmp) and dns on this host"
+ iptables -C INPUT -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null || \
+ iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
+ iptables -C OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT 2>/dev/null || \
+ iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
+
+ iptables -C INPUT -p udp --dport 53 -j ACCEPT 2>/dev/null || \
+ iptables -A INPUT -p udp --dport 53 -j ACCEPT
+ iptables -C INPUT -p tcp --dport 53 -j ACCEPT 2>/dev/null || \
+ iptables -A INPUT -p tcp --dport 53 -j ACCEPT
+
+ save_iptables_rules
+ ok "dns and icmp configuration completed"
+}
+
+###############################################################################
+# part 2: wireguard exit policy manager
+###############################################################################
+
+add_port_rules() {
+ local cmd="$1" # iptables or ip6tables
+ local port="$2"
+ local protocol="${3:-tcp}"
+
+ if [[ "$port" == *"-"* ]]; then
+ local start_port end_port
+ start_port=$(echo "$port" | cut -d'-' -f1)
+ end_port=$(echo "$port" | cut -d'-' -f2)
+
+ if ! $cmd -C "$NYM_CHAIN" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT 2>/dev/null; then
+ $cmd -A "$NYM_CHAIN" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT
+ ok "added $cmd $NYM_CHAIN $protocol port range $start_port:$end_port"
+ fi
+ else
+ if ! $cmd -C "$NYM_CHAIN" -p "$protocol" --dport "$port" -j ACCEPT 2>/dev/null; then
+ $cmd -A "$NYM_CHAIN" -p "$protocol" --dport "$port" -j ACCEPT
+ ok "added $cmd $NYM_CHAIN $protocol port $port"
+ fi
+ fi
+}
+
+exit_policy_install_deps() {
+ install_iptables_persistent
+
+ for item in iptables ip6tables ip grep sed awk wget curl; do
+ if ! command -v "$item" >/dev/null 2>&1; then
+ info "installing dependency: $item"
+ apt-get install -y "$item"
+ fi
+ done
+}
+
+create_nym_chain() {
+ info "creating nym exit policy chain $NYM_CHAIN"
+
+ if iptables -S "$NYM_CHAIN" >/dev/null 2>&1; then
+ iptables -F "$NYM_CHAIN"
+ else
+ iptables -N "$NYM_CHAIN"
+ fi
+
+ if ip6tables -S "$NYM_CHAIN" >/dev/null 2>&1; then
+ ip6tables -F "$NYM_CHAIN"
+ else
+ ip6tables -N "$NYM_CHAIN"
+ fi
+
+ if ! iptables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then
+ iptables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN"
+ fi
+
+ if ! ip6tables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then
+ ip6tables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN"
+ fi
+}
+
+setup_nat_rules() {
+ info "setting up nat and forwarding rules for $WG_INTERFACE via $NETWORK_DEVICE"
+
+ if ! iptables -t nat -C POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE 2>/dev/null; then
+ iptables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE
+ fi
+ if ! ip6tables -t nat -C POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE 2>/dev/null; then
+ ip6tables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE
+ fi
+
+ if ! iptables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null; then
+ iptables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT
+ fi
+ if ! iptables -C FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null; then
+ iptables -I FORWARD 2 -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT
+ fi
+
+ if ! ip6tables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null; then
+ ip6tables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT
+ fi
+ if ! ip6tables -C FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null; then
+ ip6tables -I FORWARD 2 -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT
+ fi
+}
+
+configure_exit_dns_and_icmp() {
+ info "ensuring dns and icmp are allowed inside nym exit chain"
+
+ # Remove any existing DNS/ICMP rules first to avoid duplicates
+ iptables -D "$NYM_CHAIN" -p udp --dport 53 -j ACCEPT 2>/dev/null || true
+ iptables -D "$NYM_CHAIN" -p tcp --dport 53 -j ACCEPT 2>/dev/null || true
+ iptables -D "$NYM_CHAIN" -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null || true
+ iptables -D "$NYM_CHAIN" -p icmp --icmp-type echo-reply -j ACCEPT 2>/dev/null || true
+ ip6tables -D "$NYM_CHAIN" -p udp --dport 53 -j ACCEPT 2>/dev/null || true
+ ip6tables -D "$NYM_CHAIN" -p tcp --dport 53 -j ACCEPT 2>/dev/null || true
+ ip6tables -D "$NYM_CHAIN" -p ipv6-icmp -j ACCEPT 2>/dev/null || true
+
+ # Insert rules at the beginning in correct order: DNS first, then ICMP
+ iptables -I "$NYM_CHAIN" 1 -p udp --dport 53 -j ACCEPT
+ iptables -I "$NYM_CHAIN" 2 -p tcp --dport 53 -j ACCEPT
+ iptables -I "$NYM_CHAIN" 3 -p icmp --icmp-type echo-request -j ACCEPT
+ iptables -I "$NYM_CHAIN" 4 -p icmp --icmp-type echo-reply -j ACCEPT
+ ip6tables -I "$NYM_CHAIN" 1 -p udp --dport 53 -j ACCEPT
+ ip6tables -I "$NYM_CHAIN" 2 -p tcp --dport 53 -j ACCEPT
+ ip6tables -I "$NYM_CHAIN" 3 -p ipv6-icmp -j ACCEPT
+}
+
+apply_port_allowlist() {
+ echo "applying allowed port list into ${NYM_CHAIN}"
+
+ configure_exit_dns_and_icmp
+
+ declare -A PORT_MAPPINGS=(
+ ["FTP"]="20-21"
+ ["SSH"]="22"
+ ["WHOIS"]="43"
+ ["DNS"]="53"
+ ["Finger"]="79"
+ ["HTTP"]="80-81"
+ ["Kerberos"]="88"
+ ["POP3"]="110"
+ ["NTP"]="123"
+ ["IMAP"]="143"
+ ["IMAP3"]="220"
+ ["LDAP"]="389"
+ ["HTTPS"]="443"
+ ["SMBWindowsFileShare"]="445"
+ ["Kpasswd"]="464"
+ ["RTSP"]="554"
+ ["LDAPS"]="636"
+ ["SILC"]="706"
+ ["KerberosAdmin"]="749"
+ ["DNSOverTLS"]="853"
+ ["Rsync"]="873"
+ ["VMware"]="902-904"
+ ["RemoteHTTPS"]="981"
+ ["FTPOverTLS"]="989-990"
+ ["NetnewsAdmin"]="991"
+ ["TelnetOverTLS"]="992"
+ ["IMAPOverTLS"]="993"
+ ["POP3OverTLS"]="995"
+ ["OpenVPN"]="1194"
+ ["WireGuardPeer"]="51820-51822"
+ ["QTServerAdmin"]="1220"
+ ["PKTKRB"]="1293"
+ ["MSSQL"]="1433"
+ ["VLSILicenseManager"]="1500"
+ ["OracleDB"]="1521"
+ ["Sametime"]="1533"
+ ["GroupWise"]="1677"
+ ["PPTP"]="1723"
+ ["RTSPAlt"]="1755"
+ ["MSNP"]="1863"
+ ["NFS"]="2049"
+ ["CPanel"]="2082-2083"
+ ["GNUnet"]="2086-2087"
+ ["NBX"]="2095-2096"
+ ["Zephyr"]="2102-2104"
+ ["XboxLive"]="3074"
+ ["MySQL"]="3306"
+ ["SVN"]="3690"
+ ["RWHOIS"]="4321"
+ ["Virtuozzo"]="4643"
+ ["RTPVOIP"]="5000-5005"
+ ["MMCC"]="5050"
+ ["ICQ"]="5190"
+ ["XMPP"]="5222-5223"
+ ["AndroidMarket"]="5228"
+ ["PostgreSQL"]="5432"
+ ["MongoDBDefault"]="27017"
+ ["Electrum"]="8082"
+ ["SimplifyMedia"]="8087-8088"
+ ["Zcash"]="8232-8233"
+ ["Bitcoin"]="8332-8333"
+ ["HTTPSALT"]="8443"
+ ["TeamSpeak"]="8767"
+ ["MQTTS"]="8883"
+ ["HTTPProxy"]="8888"
+ ["TorORPort"]="9001"
+ ["TorDirPort"]="9030"
+ ["Tari"]="9053"
+ ["Gaming"]="9339"
+ ["Git"]="9418"
+ ["HTTPSALT2"]="9443"
+ ["Lightning"]="9735"
+ ["NDMP"]="10000"
+ ["OpenPGP"]="11371"
+ ["Monero"]="18080-18081"
+ ["MoneroRPC"]="18089"
+ ["GoogleVoice"]="19294"
+ ["EnsimControlPanel"]="19638"
+ ["DarkFiTor"]="25551"
+ ["Minecraft"]="25565"
+ ["DarkFi"]="26661"
+ ["Steam"]="27000-27050"
+ ["ElectrumSSL"]="50002"
+ ["MOSH"]="60000-61000"
+ ["Mumble"]="64738"
+ ["Metadata"]="51830"
+ )
+
+ local port
+ for service in "${!PORT_MAPPINGS[@]}"; do
+ port="${PORT_MAPPINGS[$service]}"
+ echo "adding rules for $service (ports $port)"
+ add_port_rules iptables "$port" "tcp"
+ add_port_rules ip6tables "$port" "tcp"
+ add_port_rules iptables "$port" "udp"
+ add_port_rules ip6tables "$port" "udp"
+ done
+}
+
+apply_spamhaus_blocklist() {
+ info "applying spamhaus-like blocklist from $EXIT_POLICY_LOCATION"
+
+ mkdir -p "$(dirname "$POLICY_FILE")"
+
+ if ! wget -q "$EXIT_POLICY_LOCATION" -O "$POLICY_FILE" 2>/dev/null; then
+ error "failed to download exit policy, using minimal blocklist"
+ cat >"$POLICY_FILE" < "$tmpfile"
+
+ local total_rules
+ total_rules=$(wc -l < "$tmpfile")
+ info "processing $total_rules blocklist rules"
+ local line ip_range
+ while IFS= read -r line; do
+ [[ -z "$line" ]] && continue
+
+ ip_range=$(echo "$line" | sed -E 's/ExitPolicy reject ([^:]+):.*/\1/')
+
+ if [[ -n "$ip_range" ]]; then
+
+ # ipv4 reject
+ if ! iptables -C "$NYM_CHAIN" -d "$ip_range" -j REJECT 2>/dev/null; then
+ iptables -A "$NYM_CHAIN" -d "$ip_range" -j REJECT --reject-with icmp-port-unreachable \
+ || error "warning: failed adding ipv4 reject for $ip_range"
+ fi
+
+ # ipv6 reject
+ if [[ "$ip_range" == *":"* ]]; then
+ if ! ip6tables -C "$NYM_CHAIN" -d "$ip_range" -j REJECT 2>/dev/null; then
+ ip6tables -A "$NYM_CHAIN" -d "$ip_range" -j REJECT \
+ || error "warning: failed adding ipv6 reject for $ip_range"
+ fi
+ fi
+
+ fi
+ done < "$tmpfile"
+
+ rm -f "$tmpfile"
+}
+
+
+
+add_default_reject_rule() {
+ info "ensuring default reject rule at end of ${NYM_CHAIN}"
+
+ iptables -D "$NYM_CHAIN" -j REJECT 2>/dev/null || true
+ iptables -D "$NYM_CHAIN" -j REJECT --reject-with icmp-port-unreachable 2>/dev/null || true
+ ip6tables -D "$NYM_CHAIN" -j REJECT 2>/dev/null || true
+ ip6tables -D "$NYM_CHAIN" -j REJECT --reject-with icmp6-port-unreachable 2>/dev/null || true
+
+ iptables -A "$NYM_CHAIN" -j REJECT --reject-with icmp-port-unreachable
+ ip6tables -A "$NYM_CHAIN" -j REJECT --reject-with icmp6-port-unreachable
+}
+
+clear_exit_policy_rules() {
+ info "clearing nym exit policy rules ..."
+
+ iptables -F "$NYM_CHAIN" 2>/dev/null || true
+ ip6tables -F "$NYM_CHAIN" 2>/dev/null || true
+
+ iptables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true
+ ip6tables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true
+
+ iptables -X "$NYM_CHAIN" 2>/dev/null || true
+ ip6tables -X "$NYM_CHAIN" 2>/dev/null || true
+}
+
+show_exit_policy_status() {
+ info "nym exit policy status"
+ info "network device: $NETWORK_DEVICE"
+ info "wireguard interface: $WG_INTERFACE"
+ echo
+
+ if ! ip link show "$WG_INTERFACE" >/dev/null 2>&1; then
+ error "warning: wireguard interface $WG_INTERFACE not found"
+ else
+ info "interface details:"
+ ip link show "$WG_INTERFACE"
+ echo
+ info "ipv4 addresses:"
+ ip -4 addr show dev "$WG_INTERFACE"
+ echo
+ info "ipv6 addresses:"
+ ip -6 addr show dev "$WG_INTERFACE"
+ fi
+
+ echo
+ info "iptables chains for ${NYM_CHAIN}:"
+ iptables -L "$NYM_CHAIN" -n -v 2>/dev/null || echo "ipv4 chain not found"
+ echo
+ ip6tables -L "$NYM_CHAIN" -n -v 2>/dev/null || echo "ipv6 chain not found"
+ echo
+ info "ip forwarding:"
+ echo "ipv4: $(cat /proc/sys/net/ipv4/ip_forward 2>/dev/null || echo 0)"
+ echo "ipv6: $(cat /proc/sys/net/ipv6/conf/all/forwarding 2>/dev/null || echo 0)"
+}
+
+test_exit_policy_connectivity() {
+ info "testing connectivity through $WG_INTERFACE"
+
+ local iface_info
+ iface_info=$(ip link show "$WG_INTERFACE" 2>/dev/null || true)
+ if [[ -z "$iface_info" ]]; then
+ error "interface $WG_INTERFACE not found"
+ return 1
+ fi
+
+ ok "interface:"
+ ok "$iface_info"
+
+ local ipv4_address ipv6_address
+ ipv4_address=$(ip -4 addr show dev "$WG_INTERFACE" | awk '/inet / {print $2}' | cut -d'/' -f1 | head -n1)
+ ipv6_address=$(ip -6 addr show dev "$WG_INTERFACE" scope global | awk '/inet6/ {print $2}' | cut -d'/' -f1 | head -n1)
+
+ ok "ipv4 address: ${ipv4_address:-none}"
+ ok "ipv6 address: ${ipv6_address:-none}"
+
+ if [[ -n "$ipv4_address" ]]; then
+ echo -e "${NC}testing ipv4 ping to 8.8.8.8 ..."
+ timeout 5 ping -c 3 -I "$ipv4_address" 8.8.8.8 >/dev/null 2>&1 && \
+ ok "ipv4 ping ok" || error "ipv4 ping failed"
+
+ echo -e "${NC}testing ipv4 dns resolution ..."
+ timeout 5 ping -c 3 -I "$ipv4_address" google.com >/dev/null 2>&1 && \
+ ok "ipv4 dns ok" || error "ipv4 dns failed"
+ fi
+
+ if [[ -n "$ipv6_address" ]]; then
+ echo -e "${NC}testing ipv6 ping to google dns ..."
+ timeout 5 ping6 -c 3 -I "$ipv6_address" 2001:4860:4860::8888 >/dev/null 2>&1 && \
+ ok "ipv6 ping ok" || error "ipv6 ping failed"
+
+ echo -e "${NC}testing ipv6 dns resolution ..."
+ timeout 5 ping6 -c 3 -I "$ipv6_address" google.com >/dev/null 2>&1 && \
+ ok "ipv6 dns ok" || error "ipv6 dns failed"
+ fi
+
+ ok "connectivity tests finished"
+}
+
+
+###############################################################################
+# part 3: check the firewall setup
+###############################################################################
+
+firewall_rule_line() {
+ local chain=$1
+ local rule_idx=$2
+ # this is because thefirst rule appears on line 3
+ iptables -L "$chain" -n --line-numbers | sed -n "$((rule_idx + 2))p"
+}
+
+check_forward_chain() {
+ local output
+ output=$(iptables -L FORWARD -n --line-numbers)
+
+ if ! echo "$output" | grep -q "^1[[:space:]]\+$NYM_CHAIN"; then
+ error "FORWARD rule 1 is not ${NYM_CHAIN}; re-run network-tunnel-manager.sh exit_policy_install"
+ return 1
+ fi
+
+ if ! echo "$output" | grep -q "ACCEPT.*state RELATED,ESTABLISHED"; then
+ error "FORWARD chain missing RELATED,ESTABLISHED accepts; re-run network-tunnel-manager.sh apply_iptables_rules_wg"
+ return 1
+ fi
+
+ ok "FORWARD chain ordering looks good"
+ return 0
+}
+
+check_nym_exit_chain() {
+ local errors=0
+ local patterns=("udp.*dpt:53" "tcp.*dpt:53" "icmp.*type 8" "icmp.*type 0")
+
+ for idx in "${!patterns[@]}"; do
+ local line
+ line=$(firewall_rule_line "$NYM_CHAIN" $((idx + 1)))
+ if [[ "$line" =~ ${patterns[$idx]} ]]; then
+ ok "${NYM_CHAIN} rule $((idx + 1)) ok (${patterns[$idx]})"
+ else
+ error "${NYM_CHAIN} rule $((idx + 1)) is not ${patterns[$idx]}; re-run network-tunnel-manager.sh exit_policy_install"
+ errors=1
+ fi
+ done
+
+ local last_rule
+ last_rule=$(iptables -L "$NYM_CHAIN" -n --line-numbers | awk 'NR>2 {line=$0} END {print line}')
+ if [[ -z "${last_rule:-}" ]]; then
+ error "${NYM_CHAIN} chain is empty; re-run network-tunnel-manager.sh exit_policy_install"
+ errors=1
+ elif [[ "$last_rule" =~ REJECT ]] && [[ "$last_rule" =~ 0\.0\.0\.0/0 ]]; then
+ ok "${NYM_CHAIN} ends with the catch-all REJECT"
+ else
+ error "${NYM_CHAIN} final rule is not the catch-all REJECT (got: $last_rule)"
+ errors=1
+ fi
+
+ return $errors
+}
+
+check_iptables_default_policies() {
+ info "checking base iptables default policies (INPUT/FORWARD)"
+
+ local issues=0
+ local input_policy forward_policy output_policy
+
+ input_policy=$(iptables -S INPUT 2>/dev/null | awk 'NR==1 && $1=="-P" {print $3}')
+ forward_policy=$(iptables -S FORWARD 2>/dev/null | awk 'NR==1 && $1=="-P" {print $3}')
+ output_policy=$(iptables -S OUTPUT 2>/dev/null | awk 'NR==1 && $1=="-P" {print $3}')
+
+ if [[ -z "${input_policy:-}" ]]; then
+ error "unable to read INPUT default policy (iptables -S INPUT failed?)"
+ issues=1
+ elif [[ "${input_policy^^}" != "DROP" ]]; then
+ error "INPUT default policy is ${input_policy^^}; expected DROP so traffic is only allowed by explicit rules."
+ issues=1
+ else
+ ok "INPUT default policy is DROP"
+ fi
+
+ if [[ -z "${forward_policy:-}" ]]; then
+ error "unable to read FORWARD default policy (iptables -S FORWARD failed?)"
+ issues=1
+ elif [[ "${forward_policy^^}" != "DROP" ]]; then
+ error "FORWARD default policy is ${forward_policy^^}; expected DROP to ensure traffic only flows via NYM-EXIT rules."
+ issues=1
+ else
+ ok "FORWARD default policy is DROP"
+ fi
+
+ if [[ -z "${output_policy:-}" ]]; then
+ error "unable to read OUTPUT default policy (iptables -S OUTPUT failed?)"
+ issues=1
+ elif [[ "${output_policy^^}" != "ACCEPT" ]]; then
+ error "OUTPUT default policy is ${output_policy^^}; expected ACCEPT"
+ issues=1
+ else
+ ok "OUTPUT default policy is ACCEPT"
+ fi
+
+ return $issues
+}
+
+check_firewall_setup() {
+ info "checking ipv4 firewall ordering…"
+ local errors=0
+
+ check_iptables_default_policies || errors=1
+ check_forward_chain || errors=1
+ check_nym_exit_chain || errors=1
+
+ if command -v ip6tables >/dev/null 2>&1; then
+ info "checking ipv6 firewall ordering…"
+ if ip6tables -L "$NYM_CHAIN" -n --line-numbers >/dev/null 2>&1; then
+ if ! ip6tables -L "$NYM_CHAIN" -n --line-numbers | sed -n '3p' | grep -q "udp.*dpt:53"; then
+ error "ip6tables ${NYM_CHAIN} rule 1 is not UDP 53"
+ errors=1
+ fi
+ fi
+ fi
+
+ if [[ $errors -ne 0 ]]; then
+ error "There may be some ordering issues, it is recommended to re-run network-tunnel-manager.sh exit_policy_install after configuring UFW."
+ return 1
+ fi
+
+ ok "It's looking good!"
+ return 0
+}
+
+
+###############################################################################
+# part 4: full exit policy verification tests
+###############################################################################
+
+test_port_range_rules() {
+ info "testing port range rules in ${NYM_CHAIN}"
+
+ local port_ranges=(
+ "20-21:tcp:ftp"
+ "80-81:tcp:http"
+ "2082-2083:tcp:cpanel"
+ "5222-5223:tcp:xmpp"
+ "27000-27050:tcp:steam-sample"
+ "989-990:tcp:ftp-tls"
+ "5000-5005:tcp:rtp-voip"
+ "8087-8088:tcp:simplify-media"
+ "8232-8233:tcp:zcash"
+ "8332-8333:tcp:bitcoin"
+ "18080-18081:tcp:monero"
+ )
+
+ local failures=0
+ local start end
+ for entry in "${port_ranges[@]}"; do
+ IFS=':' read -r range proto name <<<"$entry"
+ start=$(echo "$range" | cut -d'-' -f1)
+ end=$(echo "$range" | cut -d'-' -f2)
+
+ if iptables -t filter -C "$NYM_CHAIN" -p "$proto" --dport "$start:$end" -j ACCEPT 2>/dev/null; then
+ ok "rule ok: $name $proto $range"
+ else
+ error "missing rule: $name $proto $range"
+ ((failures++))
+ fi
+ done
+
+ return "$failures"
+}
+
+test_critical_services() {
+ info "testing critical service rules in ${NYM_CHAIN}"
+
+ local tcp_ports=(22 53 443 853 1194)
+ local udp_ports=(53 123 1194)
+ local failures=0
+
+ for port in "${tcp_ports[@]}"; do
+ if iptables -t filter -C "$NYM_CHAIN" -p tcp --dport "$port" -j ACCEPT 2>/dev/null; then
+ ok "tcp port $port allowed"
+ else
+ if iptables-save | grep -E "^-A $NYM_CHAIN.*tcp.*dpts:" | grep -q "$port"; then
+ ok "tcp port $port allowed by range"
+ else
+ error "tcp port $port missing"
+ ((failures++))
+ fi
+ fi
+ done
+
+ for port in "${udp_ports[@]}"; do
+ if iptables -t filter -C "$NYM_CHAIN" -p udp --dport "$port" -j ACCEPT 2>/dev/null; then
+ ok "udp port $port allowed"
+ else
+ if iptables-save | grep -E "^-A $NYM_CHAIN.*udp.*dpts:" | grep -q "$port"; then
+ ok "udp port $port allowed by range"
+ else
+ error "udp port $port missing"
+ ((failures++))
+ fi
+ fi
+ done
+
+ return "$failures"
+}
+
+test_forward_chain_hook() {
+ info "testing forward chain hook direction for ${NYM_CHAIN}"
+
+ local failures=0
+
+ if iptables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then
+ ok "ipv4 forward hook ok: -i $WG_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN"
+ else
+ error "ipv4 forward hook missing or wrong"
+ ((failures++))
+ fi
+
+ if ip6tables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then
+ ok "ipv6 forward hook ok: -i $WG_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN"
+ else
+ error "ipv6 forward hook missing or wrong"
+ ((failures++))
+ fi
+
+ return "$failures"
+}
+
+test_default_reject_rule() {
+ info "testing default reject rule position in ${NYM_CHAIN}"
+
+ local last_rule_v4
+ last_rule_v4=$(iptables -S "$NYM_CHAIN" | awk '/^-A /{rule=$0} END{print rule}')
+ if [[ "$last_rule_v4" != "-A $NYM_CHAIN -j REJECT --reject-with icmp-port-unreachable" ]]; then
+ error "default reject missing or not last in ipv4 chain"
+ return 1
+ fi
+
+ local last_rule_v6
+ last_rule_v6=$(ip6tables -S "$NYM_CHAIN" | awk '/^-A /{rule=$0} END{print rule}')
+ if [[ "$last_rule_v6" != "-A $NYM_CHAIN -j REJECT --reject-with icmp6-port-unreachable" ]]; then
+ error "default reject missing or not last in ipv6 chain"
+ return 1
+ fi
+
+ ok "default reject confirmed at end of ${NYM_CHAIN}"
+}
+
+exit_policy_run_tests() {
+ local skip_default=0
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --skip-default-reject) skip_default=1; shift ;;
+ *) error "unknown test option: $1"; return 1 ;;
+ esac
+ done
+
+ local total=0
+ local failed=0
+
+ test_forward_chain_hook || ((failed += 1))
+ ((total += 1))
+
+ test_port_range_rules || ((failed += 1))
+ ((total += 1))
+
+ test_critical_services || ((failed += 1))
+ ((total += 1))
+
+ if [[ $skip_default -eq 0 ]]; then
+ test_default_reject_rule || ((failed += 1))
+ ((total += 1))
+ fi
+
+ info "tests run: ${GREEN}$total${YELLOW}, test failed: ${RED}$failed${NC}"
+ if [[ $failed -eq 0 ]]; then
+ ok "all exit policy tests passed"
+ else
+ error "some exit policy tests failed"
+ fi
+
+ return "$failed"
+}
+
+###############################################################################
+# part 5: high level workflows
+###############################################################################
+
+nym_tunnel_setup() {
+ # this mirrors your previous chain of calls but inside one script
+ info "running full tunnel setup for ${TUNNEL_INTERFACE} and ${WG_INTERFACE}"
+
+ check_tunnel_iptables "$TUNNEL_INTERFACE"
+ remove_duplicate_rules "$TUNNEL_INTERFACE"
+ remove_duplicate_rules "$WG_INTERFACE"
+ check_tunnel_iptables "$TUNNEL_INTERFACE"
+
+ adjust_ip_forwarding
+
+ apply_iptables_rules "$TUNNEL_INTERFACE"
+ check_tunnel_iptables "$TUNNEL_INTERFACE"
+
+ apply_iptables_rules "$WG_INTERFACE"
+
+ configure_dns_and_icmp_wg
+ adjust_ip_forwarding
+ check_ipv6_ipv4_forwarding
+
+ joke_through_tunnel "$TUNNEL_INTERFACE"
+ joke_through_tunnel "$WG_INTERFACE"
+
+ ok "full tunnel setup completed"
+}
+
+exit_policy_install() {
+ info "installing nym wireguard exit policy for ${WG_INTERFACE} via ${NETWORK_DEVICE}"
+ exit_policy_install_deps
+ adjust_ip_forwarding
+ create_nym_chain
+ setup_nat_rules
+ apply_port_allowlist
+ apply_spamhaus_blocklist
+ add_default_reject_rule
+ save_iptables_rules
+ ok "nym exit policy installed"
+}
+
+complete_networking_configuration() {
+ info "starting complete networking configuration: tunnels + exit policy"
+
+ nym_tunnel_setup
+ exit_policy_install
+ check_firewall_setup || error "firewall order checks reported problems, please review output"
+ exit_policy_run_tests || error "exit policy tests reported problems, please review output"
+
+ ok "complete networking configuration finished"
+}
+
+###############################################################################
+# cli
+###############################################################################
+
+cmd="${1:-help}"
+log "COMMAND: $cmd ARGS: $*"
+
+case "$cmd" in
+ nym_tunnel_setup)
+ nym_tunnel_setup
+ status=$?
+ ;;
+ exit_policy_install)
+ exit_policy_install
+ status=$?
+ ;;
+ complete_networking_configuration)
+ complete_networking_configuration
+ status=$?
+ ;;
+
+ # tunnel manager cmds
+ fetch_ipv6_address_nym_tun)
+ fetch_ipv6_address "$TUNNEL_INTERFACE"
+ status=$?
+ ;;
+ fetch_and_display_ipv6)
+ fetch_and_display_ipv6
+ status=$?
+ ;;
+ apply_iptables_rules)
+ apply_iptables_rules "$TUNNEL_INTERFACE"
+ status=$?
+ ;;
+ apply_iptables_rules_wg)
+ apply_iptables_rules "$WG_INTERFACE"
+ status=$?
+ ;;
+ check_nymtun_iptables)
+ check_tunnel_iptables "$TUNNEL_INTERFACE"
+ status=$?
+ ;;
+ check_nym_wg_tun)
+ check_tunnel_iptables "$WG_INTERFACE"
+ status=$?
+ ;;
+ check_ipv6_ipv4_forwarding)
+ check_ipv6_ipv4_forwarding
+ status=$?
+ ;;
+ check_ip_routing)
+ check_ip_routing
+ status=$?
+ ;;
+ perform_pings)
+ perform_pings
+ status=$?
+ ;;
+ joke_through_the_mixnet)
+ joke_through_tunnel "$TUNNEL_INTERFACE"
+ status=$?
+ ;;
+ joke_through_wg_tunnel)
+ joke_through_tunnel "$WG_INTERFACE"
+ status=$?
+ ;;
+ configure_dns_and_icmp_wg)
+ configure_dns_and_icmp_wg
+ status=$?
+ ;;
+ adjust_ip_forwarding)
+ adjust_ip_forwarding
+ status=$?
+ ;;
+ remove_duplicate_rules)
+ remove_duplicate_rules "${2:-}"
+ status=$?
+ ;;
+
+ # exit policy manager cmds
+ exit_policy_status)
+ show_exit_policy_status
+ status=$?
+ ;;
+ check_firewall_setup)
+ check_firewall_setup
+ status=$?
+ ;;
+ exit_policy_test_connectivity)
+ test_exit_policy_connectivity
+ status=$?
+ ;;
+ exit_policy_clear)
+ clear_exit_policy_rules
+ status=$?
+ ;;
+ exit_policy_tests)
+ shift
+ exit_policy_run_tests "$@"
+ status=$?
+ ;;
+
+ help|--help|-h)
+ cat < [args]
+
+high level workflows:
+ complete_networking_configuration Install tunnel interfaces, setup networking, iptables, wg exit policy & tests
+ nym_tunnel_setup Install tunnel interfaces & setup networking
+ exit_policy_install Install and configure wireguard exit policy
+tunnel and nat helpers:
+ adjust_ip_forwarding Enable ipv4/ipv6 forwarding via sysctl.d
+ apply_iptables_rules Apply nat/forward rules for ${TUNNEL_INTERFACE}
+ apply_iptables_rules_wg Apply nat/forward rules for ${WG_INTERFACE}
+ check_ip_routing Show ipv4 and ipv6 routes
+ check_ipv6_ipv4_forwarding Show ipv4/ipv6 forwarding flags
+ check_nym_wg_tun Inspect forward chain for ${WG_INTERFACE}
+ check_nymtun_iptables Inspect forward chain for ${TUNNEL_INTERFACE}
+ configure_dns_and_icmp_wg Allow ping and dns ports on this host
+ fetch_and_display_ipv6 Show ipv6 on uplink ${NETWORK_DEVICE}
+ fetch_ipv6_address_nym_tun Show global ipv6 address on ${TUNNEL_INTERFACE}
+ joke_through_the_mixnet Test via ${TUNNEL_INTERFACE} with joke
+ joke_through_wg_tunnel Test via ${WG_INTERFACE} with joke
+ perform_pings Test ipv4 and ipv6 pings
+ remove_duplicate_rules Deduplicate FORWARD and ${NYM_CHAIN} rules for (required).
+
+exit policy manager:
+ check_firewall_setup Run ordering sanity check (dns/icmp + FORWARD jump)
+ exit_policy_clear Remove ${NYM_CHAIN} chains and hooks
+ exit_policy_install Install exit policy (iptables rules and blocklist)
+ exit_policy_status Show status of exit policy and forwarding
+ exit_policy_test_connectivity Test connectivity via ${WG_INTERFACE}
+ exit_policy_tests [--skip-default-reject]
+ Run verification tests on exit policy (options: --skip-default-reject).
+
+environment overrides:
+ NETWORK_DEVICE Auto-detected uplink (e.g., eth0). Set manually if detection fails.
+ TUNNEL_INTERFACE Default: nymtun0. Requires root privileges (sudo) to manage.
+ WG_INTERFACE Default: nymwg - Must match your WireGuard interface name.
+
+EOF
+ status=0
+ ;;
+
+ *)
+ error "unknown command: $cmd"
+ info "run with 'help' for usage"
+ exit 1
+ ;;
+esac
+
+if [[ "$cmd" != help && "$cmd" != "--help" && "$cmd" != "-h" && ${status:-1} -eq 0 ]]; then
+ echo ""
+ echo "Logs saved locally at: $LOG_FILE"
+ ok "operation ${cmd} completed"
+fi
+END_TIME=$(date +%s)
+ELAPSED=$((END_TIME - START_TIME))
+echo "----- $(date '+%Y-%m-%d %H:%M:%S') END operation ${cmd} (status $status, duration ${ELAPSED}s) -----" >> "$LOG_FILE"
+exit $status
diff --git a/scripts/nym-node-setup/nym-node-cli.py b/scripts/nym-node-setup/nym-node-cli.py
index ebae150523b..9d99fc4ad0e 100755
--- a/scripts/nym-node-setup/nym-node-cli.py
+++ b/scripts/nym-node-setup/nym-node-cli.py
@@ -1,6 +1,6 @@
#!/usr/bin/python3
-__version__ = "1.1.0"
+__version__ = "1.2.0"
__default_branch__ = "develop"
import os
@@ -22,17 +22,25 @@ class NodeSetupCLI:
def __init__(self, args):
self.branch = args.dev
self.welcome_message = self.print_welcome_message()
- self.mode = self.prompt_mode()
+ self.mode = self._get_or_prompt_mode(args)
self.prereqs_install_sh = self.fetch_script("nym-node-prereqs-install.sh")
- self.env_vars_install_sh = self.fetch_script("setup-env-vars.sh")
self.node_install_sh = self.fetch_script("nym-node-install.sh")
self.service_config_sh = self.fetch_script("setup-systemd-service-file.sh")
self.start_node_systemd_service_sh = self.fetch_script("start-node-systemd-service.sh")
- self.landing_page_html = self._check_gwx_mode() and self.fetch_script("landing-page.html")
- self.nginx_proxy_wss_sh = self._check_gwx_mode() and self.fetch_script("nginx_proxy_wss_sh")
- self.tunnel_manager_sh = self._check_gwx_mode() and self.fetch_script("network_tunnel_manager.sh")
- self.wg_ip_tables_manager_sh = self._check_gwx_mode() and self.fetch_script("wireguard-exit-policy-manager.sh")
- self.wg_ip_tables_test_sh = self._check_gwx_mode() and self.fetch_script("exit-policy-tests.sh")
+ self.is_gwx = self.mode == "exit-gateway"
+ if self.is_gwx:
+ self.landing_page_html = self.fetch_script("landing-page.html")
+ self.nginx_proxy_wss_sh = self.fetch_script("nginx_proxy_wss_sh")
+ self.tunnel_manager_sh = self.fetch_script("network_tunnel_manager.sh")
+ self.quic_bridge_deployment_sh = self.fetch_script("quic_bridge_deployment.sh")
+ else:
+ self.landing_page_html = None
+ self.nginx_proxy_wss_sh = None
+ self.tunnel_manager_sh = None
+ self.wg_ip_tables_manager_sh = None
+ self.wg_ip_tables_test_sh = None
+ self.quic_bridge_deployment_sh = None
+
def print_welcome_message(self):
"""Welcome user, warns for needed pre-reqs and asks for confimation"""
@@ -45,7 +53,7 @@ def print_welcome_message(self):
self.print_character("=", 41)
msg = \
"Before you begin, make sure that:\n"\
- "1. You run this setup on Debian based Linux (ie Ubuntu)\n"\
+ "1. You run this setup on Debian based Linux (ie Ubuntu 22.04 LTS)\n"\
"2. You run this installation program from a root shell\n"\
"3. You meet minimal requirements: https://nym.com/docs/operators/nodes\n"\
"4. You accept Operators Terms & Conditions: https://nym.com/operators-validators-terms\n"\
@@ -59,43 +67,103 @@ def print_welcome_message(self):
else:
print("Without confirming the points above, we cannot continue.")
exit(1)
+
+ def ensure_env_values(self, args):
+ """Collect env vars from args or prompt interactively, then save to env.sh."""
+ env_file = Path("env.sh")
+ fields = [
+ ("hostname", "HOSTNAME", "Enter hostname (if you don't use a DNS, press enter): "),
+ ("location", "LOCATION", "Enter node location (country code or name): "),
+ ("email", "EMAIL", "Enter your email: "),
+ ("moniker", "MONIKER", "Enter node public moniker (visible in explorer & NymVPN app): "),
+ ("description", "DESCRIPTION", "Enter short node public description: "),
+ ]
- def prompt_mode(self):
- """Ask user to insert node functionality and save it in python and bash envs"""
- mode = input(
- "\nEnter the mode you want to run nym-node in: "
- "\n1. mixnode "
- "\n2. entry-gateway "
- "\n3. exit-gateway (works as entry-gateway as well) "
- "\nPress 1, 2 or 3 and enter:\n"
- ).strip()
-
- if mode in ("1", "mixnode"):
- mode = "mixnode"
- elif mode in ("2", "entry-gateway"):
- mode = "entry-gateway"
- elif mode in ("3", "exit-gateway"):
- mode = "exit-gateway"
- else:
- print("Only numbers 1, 2 or 3 are accepted.")
- raise SystemExit(1)
+ existing = self._read_env_file(env_file)
+ updated = {}
- # save mode for this Python instance
- self.mode = mode
- os.environ["MODE"] = mode
+ for arg_name, key, prompt in fields:
+ cli_val = getattr(args, arg_name, None)
+ value = cli_val.strip() if cli_val else existing.get(key) or input(prompt).strip()
+ updated[key] = value
+ os.environ[key] = value
+
+ # autodetect PUBLIC_IP if not already set
+ if not os.environ.get("PUBLIC_IP"):
+ try:
+ ip = subprocess.run(["curl", "-fsS4", "https://ifconfig.me"],
+ capture_output=True, text=True, timeout=5)
+ if ip.returncode == 0 and ip.stdout.strip():
+ updated["PUBLIC_IP"] = ip.stdout.strip()
+ os.environ["PUBLIC_IP"] = ip.stdout.strip()
+ except subprocess.TimeoutExpired:
+ print("[WARN] Timeout expired while trying to fetch public IP with curl.")
+ except FileNotFoundError:
+ print("[WARN] 'curl' command not found. Please install curl or set PUBLIC_IP manually.")
+ except subprocess.CalledProcessError as e:
+ print(f"[WARN] Error while running curl to fetch public IP: {e}")
+
+ # write all collected variables to env.sh in one go
+ self._upsert_env_vars(updated, env_file)
+
+ print(f"[OK] Updated env.sh with {len(updated)} entries.")
+
+
+
+
+ def _upsert_env_vars(self, updates: dict, env_file: Path = Path("env.sh")):
+ existing = self._read_env_file(env_file)
+ existing.update(updates)
+ with env_file.open("w") as f:
+ for k, v in existing.items():
+ f.write(f'export {k}="{v}"\n')
+ os.environ.update(updates)
+
+ def _read_env_file(self, env_file: Path) -> dict:
+ env = {}
+ if env_file.exists():
+ for line in env_file.read_text().splitlines():
+ if line.startswith("export ") and "=" in line:
+ k, v = line.replace("export ", "", 1).split("=", 1)
+ env[k.strip()] = v.strip().strip('"')
+ return env
+
+ def _get_or_prompt_mode(self, args):
+ """Resolve MODE from --mode, env.sh, os.environ, or prompt; persist to env.sh."""
- # persist to env.sh so other scripts can source it
env_file = Path("env.sh")
- with env_file.open("a") as f:
- f.write(f'export MODE="{mode}"\n')
- # source env.sh so future bash subprocesses see it immediately
- subprocess.run("source ./env.sh", shell=True, executable="/bin/bash")
+ # CLI arg
+ mode = getattr(args, "mode", None)
+ if mode:
+ mode = mode.strip().lower()
+ self._upsert_env_vars({"MODE": mode})
+ print(f"Mode set to '{mode}' from CLI argument.")
+ return mode
+
+ # env.sh (replaces manual read)
+ existing = self._read_env_file(env_file)
+ mode = existing.get("MODE")
+ if mode:
+ os.environ["MODE"] = mode
+ return mode
+
+ # process env
+ if os.environ.get("MODE"):
+ return os.environ["MODE"]
+
+ # prompt
+ mode = input(
+ "\nEnter node mode (mixnode / entry-gateway / exit-gateway): "
+ ).strip().lower()
+ if mode not in ("mixnode", "entry-gateway", "exit-gateway"):
+ print("Invalid mode. Must be one of: mixnode, entry-gateway, exit-gateway.")
+ raise SystemExit(1)
+ self._upsert_env_vars({"MODE": mode})
print(f"Mode set to '{mode}' — stored in env.sh and sourced for immediate use.")
return mode
-
def fetch_script(self, script_name):
"""Fetches needed scripts according to a defined mode"""
# print header only the first time
@@ -119,16 +187,15 @@ def _return_script_url(self, script_init_name):
github_raw_nymtech_nym_scripts_url = f"https://raw.githubusercontent.com/nymtech/nym/refs/heads/{self.branch}/scripts/"
scripts_urls = {
"nym-node-prereqs-install.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/nym-node-prereqs-install.sh",
- "setup-env-vars.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/setup-env-vars.sh",
"nym-node-install.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/nym-node-install.sh",
"setup-systemd-service-file.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/setup-systemd-service-file.sh",
"start-node-systemd-service.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/start-node-systemd-service.sh",
"nginx_proxy_wss_sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/setup-nginx-proxy-wss.sh",
"landing-page.html": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/landing-page.html",
- "network_tunnel_manager.sh": f"https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/network_tunnel_manager.sh",
- "wireguard-exit-policy-manager.sh": f"https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh",
- "exit-policy-tests.sh": f"https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/exit-policy-tests.sh",
+ "network_tunnel_manager.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/network-tunnel-manager.sh",
+ "quic_bridge_deployment.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/quic_bridge_deployment.sh"
}
+
return scripts_urls[script_init_name]
def run_script(
@@ -200,62 +267,61 @@ def _write_temp_script(self, script_text: str) -> Path:
def _check_gwx_mode(self):
"""Helper: Several fns run only for GWx - this fn checks this condition"""
- if self.mode == "exit-gateway":
- return True
- else:
- return False
+ return self.mode == "exit-gateway"
- def check_wg_enabled(self):
- """Checks if Wireguard is enabled and if not, prompts user if they want to enable it, stores it to env.sh"""
+ def check_wg_enabled(self, args=None):
+ """Determine if WireGuard is enabled; precedence: CLI > env > env.sh > prompt. Persist normalized value."""
+ env_file = os.path.join(os.getcwd(), "env.sh")
- env_file = os.path.abspath(os.path.join(os.getcwd(), "env.sh"))
+ def norm(v):
+ return "true" if str(v).strip().lower() == "true" else "false"
- def norm(v): # -> "true" or "false"
- return "true" if str(v).strip().lower() in ("1", "true", "yes", "y") else "false"
+ val = None
- # precedence: process env → env.sh → prompt
- val = os.environ.get("WIREGUARD")
+ # CLI argument
+ if args and getattr(args, "wireguard", None) is not None:
+ val = norm(getattr(args, "wireguard"))
+ print(f"[INFO] WireGuard mode provided via CLI: {val}")
- if val is None and os.path.isfile(env_file):
- try:
- with open(env_file, "r", encoding="utf-8") as f:
- m = re.search(r'^\s*export\s+WIREGUARD\s*=\s*"?([^"\n]+)"?', f.read(), re.M)
- if m:
- val = m.group(1)
- except Exception:
- pass
+ # Environment variable
+ val = val or os.environ.get("WIREGUARD")
+ # env.sh file
+ if val is None:
+ envs = self._read_env_file(Path(env_file))
+ val = envs.get("WIREGUARD")
+
+ # Prompt
if val is None:
ans = input(
"\nWireGuard is not configured.\n"
"Nodes routing WireGuard can be listed as both entry and exit in the app.\n"
- "Enable WireGuard support? (y/n): "
+ "Enable WireGuard support? (Y/n): "
).strip().lower()
- val = "true" if ans in ("y", "yes") else "false"
+ val = "true" if ans in ("", "y", "yes") else "false"
val = norm(val)
os.environ["WIREGUARD"] = val
- # persist to env.sh (replace or append)
+ # Persist to env.sh
try:
text = ""
if os.path.isfile(env_file):
- with open(env_file, "r", encoding="utf-8") as f:
+ with open(env_file, encoding="utf-8") as f:
text = f.read()
if re.search(r'^\s*export\s+WIREGUARD\s*=.*$', text, re.M):
text = re.sub(r'^\s*export\s+WIREGUARD\s*=.*$', f'export WIREGUARD="{val}"', text, flags=re.M)
else:
- if text and not text.endswith("\n"):
- text += "\n"
- text += f'export WIREGUARD="{val}"\n'
+ text = (text.rstrip("\n") + "\n" if text else "") + f'export WIREGUARD="{val}"\n'
with open(env_file, "w", encoding="utf-8") as f:
f.write(text)
print(f'WIREGUARD={val} saved to {env_file}')
- except Exception as e:
+ except OSError as e:
print(f"Warning: could not write {env_file}: {e}")
- return (val == "true")
+ return val == "true"
+
def run_bash_command(self, command, args=None, *, env=None, cwd=None, check=True):
"""
@@ -279,25 +345,13 @@ def run_tunnel_manager_setup(self):
"""A standalone fn to pass full cmd list needed for correct setup and test network tunneling, using an external script"""
print(
"\n* * * Setting up network configuration for mixnet IP router and Wireguard tunneling * * *"
- "\nMore info: https://nym.com/docs/operators/nodes/nym-node/configuration#1-download-network_tunnel_managersh-make-executable-and-run"
+ "\nMore info: https://nym.com/docs/operators/nodes/nym-node/configuration#routing-configuration"
"\nThis may take a while; follow the steps below and don't kill the process..."
)
# each entry is the exact argv to pass to the script
steps = [
- ["check_nymtun_iptables"],
- ["remove_duplicate_rules", "nymtun0"],
- ["remove_duplicate_rules", "nymwg"],
- ["check_nymtun_iptables"],
- ["adjust_ip_forwarding"],
- ["apply_iptables_rules"],
- ["check_nymtun_iptables"],
- ["apply_iptables_rules_wg"],
- ["configure_dns_and_icmp_wg"],
- ["adjust_ip_forwarding"],
- ["check_ipv6_ipv4_forwarding"],
- ["joke_through_the_mixnet"],
- ["joke_through_wg_tunnel"],
+ ["complete_networking_configuration"]
]
for argv in steps:
@@ -316,10 +370,17 @@ def setup_test_wg_ip_tables(self):
"Setting up Wireguard IP tables to match Nym exit policy for mixnet, stored at: https://nymtech.net/.wellknown/network-requester/exit-policy.txt"
"\nThis may take a while, follow the steps below and don't kill the process..."
)
- self.run_script(self.wg_ip_tables_manager_sh, args=["install"])
- self.run_script(self.wg_ip_tables_manager_sh, args=["status"])
- self.run_script(self.wg_ip_tables_test_sh)
+ self.run_script(self.tunnel_manager_sh, args=["exit_policy_install"])
+ def quic_bridge_deploy(self):
+ """Setup QUIC bridge and configuration using external script"""
+ print("\n* * * Installing and configuring QUIC bridges * * *")
+ answer = input("\nDo you want to install, setup and run QUIC bridge? (Y/n) ").strip().lower()
+
+ if answer in ("", "y", "yes"):
+ self.run_script(self.quic_bridge_deployment_sh, args=["full_bridge_setup"])
+ else:
+ print("Skipping QUIC bridge setup.")
def run_nym_node_as_service(self):
"""Starts /etc/systemd/system/nym-node.service based on prompt using external script"""
@@ -347,8 +408,8 @@ def run_nym_node_as_service(self):
if is_active:
while True:
- ans = input(f"{service} is already running. Restart it now? (y/n):\n").strip().lower()
- if ans == "y":
+ ans = input(f"{service} is already running. Restart it now? (Y/n):\n").strip().lower()
+ if ans in ("", "Y", "y"):
self.run_script(self.start_node_systemd_service_sh, args=["restart-poll"], env=run_env)
return
elif ans == "n":
@@ -358,8 +419,8 @@ def run_nym_node_as_service(self):
print("Invalid input. Please press 'y' or 'n' and press enter.")
else:
while True:
- ans = input(f"{service} is not running. Start it now? (y/n):\n").strip().lower()
- if ans == "y":
+ ans = input(f"{service} is not running. Start it now? (Y/n):\n").strip().lower()
+ if ans in ("", "Y", "y"):
self.run_script(self.start_node_systemd_service_sh, args=["start-poll"], env=run_env)
return
elif ans == "n":
@@ -510,8 +571,12 @@ def _env_with_envfile(self) -> dict:
def run_node_installation(self,args):
"""Main function called by argparser command install running full node install flow"""
+ self.ensure_env_values(args)
+ # Pass uplink override to all helper scripts if provided
+ if getattr(args, "uplink_dev", None):
+ os.environ["UPLINK_DEV"] = args.uplink_dev
+ os.environ["NETWORK_DEVICE"] = args.uplink_dev
self.run_script(self.prereqs_install_sh)
- self.run_script(self.env_vars_install_sh)
self.run_script(self.node_install_sh)
self.run_script(self.service_config_sh)
self._check_gwx_mode() and self.run_script(self.nginx_proxy_wss_sh)
@@ -521,7 +586,7 @@ def run_node_installation(self,args):
self.run_tunnel_manager_setup()
if self.check_wg_enabled():
self.setup_test_wg_ip_tables()
- self.setup_test_wg_ip_tables()
+ self.quic_bridge_deploy()
@@ -537,7 +602,7 @@ def parser_main(self):
version=f"nym-node-cli {__version__}"
)
parent.add_argument("-d", "--dev", metavar="BRANCH",
- help="Define github branch",
+ help="Define github branch (default: develop)",
type=str,
default=argparse.SUPPRESS)
parent.add_argument("-v", "--verbose", action="store_true",
@@ -553,11 +618,38 @@ def parser_main(self):
subparsers = parser.add_subparsers(dest="command", help="subcommands")
subparsers.required = True
- p_install = subparsers.add_parser(
+ install_parser = subparsers.add_parser(
"install", parents=[parent],
help="Starts nym-node installation setup CLI",
aliases=["i", "I"], add_help=True
)
+ install_parser.add_argument(
+ "--mode",
+ choices=["mixnode", "entry-gateway", "exit-gateway"],
+ help="Node mode: 'mixnode', 'entry-gateway', or 'exit-gateway'",
+ )
+ install_parser.add_argument(
+ "--wireguard-enabled",
+ choices=["true", "false"],
+ help="WireGuard functionality switch: true / false"
+ )
+ install_parser.add_argument("--hostname", help="Node domain / hostname")
+ install_parser.add_argument("--location", help="Node location (country code or name)")
+ install_parser.add_argument("--email", help="Contact email for the node operator")
+ install_parser.add_argument("--moniker", help="Public moniker displayed in explorer & NymVPN app")
+ install_parser.add_argument("--description", help="Short public description of the node")
+ install_parser.add_argument("--public-ip", help="External IPv4 address (autodetected if omitted)")
+ install_parser.add_argument("--nym-node-binary", help="URL for nym-node binary (autodetected if omitted)")
+ install_parser.add_argument("--uplink-dev", help="Override uplink interface used for NAT/FORWARD (e.g., 'eth0'; autodetected if omitted)")
+
+ # generic fallback
+ install_parser.add_argument(
+ "--env",
+ action="append",
+ metavar="KEY=VALUE",
+ help="(Optional) Extra ENV VARS, e.g. --env CUSTOM_KEY=value",
+ )
+
args = parser.parse_args()
diff --git a/scripts/nym-node-setup/nym-node-install.sh b/scripts/nym-node-setup/nym-node-install.sh
index 3ea25687f59..af36c92017b 100644
--- a/scripts/nym-node-setup/nym-node-install.sh
+++ b/scripts/nym-node-setup/nym-node-install.sh
@@ -34,8 +34,9 @@ check_existing_config() {
if [[ "${resp}" =~ ^([Rr][Ee][Ss][Ee][Tt])$ ]]; then
echo
- read -r -p "We are going to remove the existing node with configuration $NODE_CONFIG_DIR and replace it with a fresh one, do you want to back up the old one first? (y/n) " backup_ans
- if [[ "${backup_ans}" =~ ^[Yy]$ ]]; then
+ echo "We are going to remove the existing node with configuration $NODE_CONFIG_DIR and replace it with a fresh one."
+ read -r -p "back up the old one first? (Y/n) " backup_ans
+ if [[ -z "${backup_ans}" || "${backup_ans}" =~ ^[Yy]$ ]]; then
ts="$(date +%Y%m%d-%H%M%S)"
backup_dir="$HOME/.nym/backup/$(basename "$NODE_CONFIG_DIR")-$ts"
echo "Backing up to: $backup_dir"
@@ -181,24 +182,27 @@ fi
NYM_NODE="$HOME/nym-binaries/nym-node"
-# if binary already exists, ask to overwrite; if yes, remove first
+# if binary already exists, ask to overwrite; if yes, remove first; if no, skip download
if [[ -e "${NYM_NODE}" ]]; then
echo
echo -e "\n* * * A nym-node binary already exists at: ${NYM_NODE}"
- read -r -p "Overwrite with the latest release? (y/n): " ow_ans
- if [[ "${ow_ans}" =~ ^[Yy]$ ]]; then
- echo "Removing existing binary to avoid 'text file busy'..."
+ read -r -p "Overwrite with the latest release? (Y/n): " ow_ans
+
+ if [[ -z "${ow_ans}" || "${ow_ans}" =~ ^[Yy]$ ]]; then
+ echo "Removing existing binary..."
rm -f "${NYM_NODE}"
+ download_nym_node "$LATEST_TAG_URL" "$NYM_NODE"
else
- echo "Keeping existing binary."
+ echo "Keeping existing binary. Skipping download."
fi
-fi
-download_nym_node "$LATEST_TAG_URL" "$NYM_NODE"
+else
+ # binary does not exist → must download
+ download_nym_node "$LATEST_TAG_URL" "$NYM_NODE"
+fi
echo -e "\n * * * Making binary executable * * *"
chmod +x "${NYM_NODE}"
-
echo "---------------------------------------------------"
echo "Nym node binary downloaded:"
"${NYM_NODE}" --version || true
@@ -225,17 +229,18 @@ WIREGUARD="${WIREGUARD:-}"
if [[ ( "$MODE" == "entry-gateway" || "$MODE" == "exit-gateway" ) && ( -n "${ASK_WG:-}" || -z "$WIREGUARD" ) ]]; then
echo
echo "Gateways can also route WireGuard in NymVPN."
- echo "Enabling it means your node may be listed as both entry and exit in the app."
- # show current default in the prompt if present
def_hint=""
[[ -n "${WIREGUARD}" ]] && def_hint=" [current: ${WIREGUARD}]"
- read -r -p "Enable WireGuard support? (y/n)${def_hint}: " answer || true
- case "${answer:-}" in
- [Yy]* ) WIREGUARD="true" ;;
- [Nn]* ) WIREGUARD="false" ;;
- * ) : ;; # keep existing value if user just pressed enter
- esac
+
+ read -r -p "Enable WireGuard support? (Y/n)${def_hint}: " answer || true
+
+ if [[ -z "${answer}" || "${answer}" =~ ^[Yy]$ ]]; then
+ WIREGUARD="true"
+ elif [[ "${answer}" =~ ^[Nn]$ ]]; then
+ WIREGUARD="false"
+ fi
fi
+
# final default only if still empty
WIREGUARD="${WIREGUARD:-false}"
diff --git a/scripts/nym-node-setup/nym-node-prereqs-install.sh b/scripts/nym-node-setup/nym-node-prereqs-install.sh
index 63f3264ff01..e462ecb5c92 100644
--- a/scripts/nym-node-setup/nym-node-prereqs-install.sh
+++ b/scripts/nym-node-setup/nym-node-prereqs-install.sh
@@ -1,6 +1,11 @@
#!/bin/bash
-# update, upgrade & install dependencies
+if [[ "$(id -u)" -ne 0 ]]; then
+ echo "This script must be run as root."
+ exit 1
+fi
+
+# update, upgrade and install dependencies
echo -e "\n* * * Installing needed prerequisities * * *"
apt update -y && apt --fix-broken install
@@ -8,11 +13,9 @@ apt upgrade
apt install apt ca-certificates jq curl wget ufw jq tmux pkg-config build-essential libssl-dev git ntp ntpdate neovim tree tmux tig nginx -y
apt install ufw --fix-missing
-
-
# enable & setup firewall
echo -e "\n* * * Setting up firewall using ufw * * * "
-echo "Please enable the firewall in the next prompt for node proper routing!"
+echo "Please enable the firewall in the next prompt for node proper routing."
echo
ufw enable
ufw allow 22/tcp # SSH - you're in control of these ports
@@ -24,6 +27,6 @@ ufw allow 8080/tcp # Nym specific - nym-node-api
ufw allow 9000/tcp # Nym Specific - clients port
ufw allow 9001/tcp # Nym specific - wss port
ufw allow 51822/udp # WireGuard
-ufw allow 'Nginx Full' && \
+ufw allow in on nymwg to any port 51830 proto tcp # bandwidth queries/topup - inside the tunnel
ufw reload && \
ufw status
diff --git a/scripts/nym-node-setup/quic_bridge_deployment.sh b/scripts/nym-node-setup/quic_bridge_deployment.sh
index f7dc60e0f9f..a1fe29ff3ac 100644
--- a/scripts/nym-node-setup/quic_bridge_deployment.sh
+++ b/scripts/nym-node-setup/quic_bridge_deployment.sh
@@ -7,6 +7,7 @@
# RUN AS ROOT
set -euo pipefail
+set +o errtrace
# Colors
RED="\033[0;31m"
@@ -17,25 +18,37 @@ BOLD="\033[1m"
RESET="\033[0m"
# Logging
-LOG_FILE="/var/log/nym-bridge-helper.log"
+LOG_FILE="/var/log/nym/quic_bridge_deployment.log"
mkdir -p "$(dirname "$LOG_FILE")"
+
+# rotate log if >10MB BEFORE writing START header
+if [[ -f "$LOG_FILE" && $(stat -c%s "$LOG_FILE") -gt 10485760 ]]; then
+ mv "$LOG_FILE" "${LOG_FILE}.1"
+fi
+
touch "$LOG_FILE"
chmod 640 "$LOG_FILE"
+
+echo "----- $(date '+%Y-%m-%d %H:%M:%S') START quic-bridge-manager -----" | tee -a "$LOG_FILE"
echo -e "${CYAN}Logs are being saved locally to:${RESET} $LOG_FILE"
echo -e "${CYAN}These logs never leave your machine.${RESET}"
echo "" | tee -a "$LOG_FILE"
-# safe logger
+# safe logger function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
-# simple redirection that keeps function scope intact
+# global redirection, strip ANSI before writing to log
add_log_redirection() {
- exec > >(tee -a "$LOG_FILE") 2>&1
+ exec > >(tee >(sed -u 's/\x1b\[[0-9;]*m//g' >> "$LOG_FILE"))
+ exec 2> >(tee >(sed -u 's/\x1b\[[0-9;]*m//g' >> "$LOG_FILE") >&2)
}
add_log_redirection
+START_TIME=$(date +%s)
+
+
# Constants / Paths
REQUIRED_CMDS=(ip jq curl openssl dpkg)
BRIDGE_BIN="/usr/local/bin/nym-bridge"
@@ -47,7 +60,17 @@ NYM_ETC_BRIDGES="$NYM_ETC_DIR/bridges.toml"
NYM_ETC_CLIENT_PARAMS_DEFAULT="$NYM_ETC_DIR/client_bridge_params.json"
SERVICE_FILE="/etc/systemd/system/nym-bridge.service"
-NET_DEV="$(ip route show default 2>/dev/null | awk '/default/ {print $5}' || true)"
+NET_DEV="${UPLINK_DEV:-}"
+if [[ -z "$NET_DEV" ]]; then
+ NET_DEV="$(ip -o route show default 2>/dev/null | awk '{print $5}' | head -n1)"
+ [[ -z "$NET_DEV" ]] && NET_DEV="$(ip -o route show default table all 2>/dev/null | awk '{print $5}' | head -n1)"
+fi
+if [[ -z "$NET_DEV" ]]; then
+ echo -e "${RED}Cannot determine uplink interface. Set UPLINK_DEV.${RESET}" | tee -a "$LOG_FILE"
+ exit 1
+fi
+echo "Using uplink device: $NET_DEV"
+
WG_IFACE="nymwg"
# Root check
@@ -57,13 +80,29 @@ if [[ "$(id -u)" -ne 0 ]]; then
fi
# UI helpers
-hr() { echo -e "${YELLOW}----------------------------------------${RESET}"; }
+hr() { echo -e "${YELLOW}----------------------------------------${RESET}" ; }
title() { echo -e "\n${YELLOW}==========================================${RESET}\n${YELLOW} $1${RESET}\n${YELLOW}==========================================${RESET}\n"; }
ok() { echo -e "${GREEN}$1${RESET}"; }
warn() { echo -e "${YELLOW}$1${RESET}"; }
err() { echo -e "${RED}$1${RESET}"; }
info() { echo -e "${CYAN}$1${RESET}"; }
-press_enter() { read -r -p "$1"; }
+press_enter() {
+ echo -n "$1" > /dev/tty
+ read -r _ < /dev/tty
+}
+
+
+# Disable pauses and interactive prompts for noninteractive mode
+if [[ "${NONINTERACTIVE:-0}" == "1" ]]; then
+ press_enter() { :; }
+ echo_prompt() { :; }
+else
+ press_enter() {
+ echo -n "$1" > /dev/tty
+ read -r _ < /dev/tty
+ }
+ echo_prompt() { echo -n "$1"; }
+fi
# Helper: detect dpkg dependency failure for libc6>=2.34
deb_depends_libc_too_old() {
@@ -176,13 +215,31 @@ verify_bridge_prerequisites() {
}
adjust_ip_forwarding() {
- title "Adjusting IP Forwarding"
- sed -i '/^net\.ipv4\.ip_forward=/d' /etc/sysctl.conf
- sed -i '/^net\.ipv6\.conf\.all\.forwarding=/d' /etc/sysctl.conf
- echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
- echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf
- sysctl -p /etc/sysctl.conf
- ok "IPv4/IPv6 forwarding enabled."
+ title "Checking IP forwarding"
+ local v4 v6
+ v4="$(cat /proc/sys/net/ipv4/ip_forward 2>/dev/null || echo 0)"
+ v6="$(cat /proc/sys/net/ipv6/conf/all/forwarding 2>/dev/null || echo 0)"
+
+ if [[ "$v4" == "1" ]]; then
+ ok "IPv4 forwarding is enabled."
+ else
+ warn "IPv4 forwarding is not enabled."
+ fi
+
+ if [[ "$v6" == "1" ]]; then
+ ok "IPv6 forwarding is enabled."
+ else
+ warn "IPv6 forwarding is not enabled."
+ fi
+
+ if [[ "$v4" != "1" || "$v6" != "1" ]]; then
+ echo
+ echo "To enable forwarding and routing consistently, run the network tunnel manager script as root."
+ echo "For example:"
+ echo " ./network-tunnel-manager.sh complete_networking_configuration"
+ echo "or:"
+ echo " ./network-tunnel-manager.sh adjust_ip_forwarding"
+ fi
}
# Install nym-bridge
@@ -377,6 +434,11 @@ User=root
ExecStart=$BRIDGE_BIN --config $NYM_ETC_BRIDGES
Restart=on-failure
RestartSec=5
+LimitNOFILE=65535
+ProtectSystem=full
+ProtectHome=yes
+PrivateTmp=yes
+
[Install]
WantedBy=multi-user.target
@@ -390,21 +452,40 @@ EOF
# IPTABLES & helpers
apply_bridge_iptables_rules() {
- title "Applying iptables rules"
- iptables -I INPUT -i "$WG_IFACE" -j ACCEPT || true
- ip6tables -I INPUT -i "$WG_IFACE" -j ACCEPT || true
- iptables -t nat -A POSTROUTING -o "$NET_DEV" -j MASQUERADE || true
- ip6tables -t nat -A POSTROUTING -o "$NET_DEV" -j MASQUERADE || true
- iptables-save > /etc/iptables/rules.v4
- ip6tables-save > /etc/iptables/rules.v6
- ok "iptables rules applied."
+ title "Checking iptables rules for bridge routing"
+
+ echo "Inspecting current iptables state for interface ${WG_IFACE} and uplink ${NET_DEV}."
+ echo
+
+ echo "IPv4 FORWARD:"
+ iptables -L FORWARD -n -v 2>/dev/null | sed -n '1,20p' || echo "iptables not available."
+ echo
+ echo "IPv4 NAT POSTROUTING:"
+ iptables -t nat -L POSTROUTING -n -v 2>/dev/null | sed -n '1,20p' || true
+ echo
+ echo "IPv6 FORWARD:"
+ ip6tables -L FORWARD -n -v 2>/dev/null | sed -n '1,20p' || true
+ echo
+ echo "IPv6 NAT POSTROUTING:"
+ ip6tables -t nat -L POSTROUTING -n -v 2>/dev/null | sed -n '1,20p' || true
+
+ echo
+ echo "This script no longer changes iptables. To configure routing and NAT for nym, use the network tunnel manager script."
+ echo "For example (run as root):"
+ echo " ./network-tunnel-manager.sh complete_networking_configuration"
}
configure_dns_and_icmp() {
- title "Allow ICMP and DNS"
- iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT || true
- ip6tables -A INPUT -p ipv6-icmp -j ACCEPT || true
- ok "ICMP and DNS rules applied."
+ title "Checking ICMP and DNS firewall rules"
+
+ echo "IPv4 INPUT rules related to ICMP and DNS:"
+ iptables -L INPUT -n -v 2>/dev/null | grep -E 'icmp|dpt:53' || echo "no matching IPv4 rules shown."
+ echo
+ echo "IPv6 INPUT rules related to ICMP and DNS:"
+ ip6tables -L INPUT -n -v 2>/dev/null | grep -E 'icmp|dpt:53' || echo "no matching IPv6 rules shown."
+
+ echo
+ echo "If ping or DNS are blocked for bridge traffic, adjust your firewall using the network tunnel manager script or your chosen firewall tool."
}
# Full interactive setup
@@ -429,6 +510,8 @@ full_bridge_setup() {
echo ""
echo "Step 2/6: Installing bridge binary..."
install_bridge_binary
+ echo "[Bridge Install] $(date '+%F %T') $( $BRIDGE_BIN --version 2>/dev/null || echo 'nym-bridge (unknown)')" \
+ >> /var/log/nym/nym-bridge-version.log
press_enter "Press Enter to continue..."
echo ""
@@ -447,7 +530,7 @@ full_bridge_setup() {
press_enter "Press Enter to continue..."
echo ""
- echo "Step 6/6: Configuring network rules (optional but recommended)..."
+ echo "Step 6/6: Checking network rules and forwarding status..."
adjust_ip_forwarding
apply_bridge_iptables_rules
configure_dns_and_icmp
@@ -468,10 +551,6 @@ full_bridge_setup() {
echo -e "${YELLOW}------------------------------------------${RESET}"
echo -e "All done! You can safely close this session."
echo -e "${YELLOW}------------------------------------------${RESET}"
- echo ""
- echo "Logs saved locally at: $LOG_FILE"
- echo "Operation 'full_bridge_setup' completed."
- echo ""
hr
echo -e "${CYAN}Next steps and verification:${RESET}"
@@ -509,22 +588,26 @@ full_bridge_setup() {
graceful_exit() {
local exit_code=$?
- echo ""
- echo -e "${YELLOW}------------------------------------------${RESET}"
+ END_TIME=$(date +%s)
+ ELAPSED=$((END_TIME - START_TIME))
+
+ # Only print success message when there were NO errors
if [[ $exit_code -eq 0 ]]; then
- echo -e "${GREEN}Setup completed successfully. Exiting cleanly.${RESET}"
- else
- echo -e "${RED}Script exited with errors (code: $exit_code).${RESET}"
- echo "Check the log at: $LOG_FILE"
+ echo "Operation '${COMMAND}' completed."
fi
- echo -e "${YELLOW}------------------------------------------${RESET}"
- echo ""
- exec >&- 2>&-
+
+ # END footer always logged
+ echo "----- $(date '+%Y-%m-%d %H:%M:%S') END operation ${COMMAND} (status $exit_code, duration ${ELAPSED}s) -----" >> "$LOG_FILE"
+
exit $exit_code
}
-trap graceful_exit EXIT
# Command menu
+COMMAND="${1:-help}"
+trap 'log "ERROR: exit=$? line=$LINENO cmd=$(printf "%q" "$BASH_COMMAND")"' ERR
+
+trap graceful_exit EXIT
+
case "${1:-}" in
full_bridge_setup) full_bridge_setup ;;
install_bridge_binary) install_bridge_binary ;;
@@ -550,5 +633,3 @@ case "${1:-}" in
;;
esac
-echo "Operation '${1:-help}' completed."
-
diff --git a/scripts/nym-node-setup/setup-env-vars.sh b/scripts/nym-node-setup/setup-env-vars.sh
index 3eafd93ba5f..e119a61fd68 100644
--- a/scripts/nym-node-setup/setup-env-vars.sh
+++ b/scripts/nym-node-setup/setup-env-vars.sh
@@ -39,16 +39,6 @@ while true; do
esac
done
-# try to get the latest binary URL (non-fatal if missing)
-LATEST_BINARY=$(
- curl -fsSL https://github.com/nymtech/nym/releases/latest \
- | grep -Eo 'href="/nymtech/nym/releases/download/[^"]+/nym-node"' \
- | head -n1 \
- | cut -d'"' -f2
-)
-if [[ -z "${LATEST_BINARY:-}" ]]; then
- echo "WARNING: Could not determine latest nym-node binary URL right now. The installer will resolve it later."
-fi
PUBLIC_IP=$(curl -fsS -4 https://ifconfig.me || true)
PUBLIC_IP=${PUBLIC_IP:-""}
diff --git a/scripts/nym-node-setup/setup-nginx-proxy-wss.sh b/scripts/nym-node-setup/setup-nginx-proxy-wss.sh
index eae4d7165e0..cf1378f5b6c 100644
--- a/scripts/nym-node-setup/setup-nginx-proxy-wss.sh
+++ b/scripts/nym-node-setup/setup-nginx-proxy-wss.sh
@@ -1,315 +1,136 @@
#!/usr/bin/env bash
set -euo pipefail
-# load env (prefer absolute ENV_FILE injected by Python CLI; fallback to ./env.sh)
+if [[ "$(id -u)" -ne 0 ]]; then
+ echo "This script must be run as root."
+ exit 1
+fi
+
+# load env
if [[ -n "${ENV_FILE:-}" && -f "${ENV_FILE}" ]]; then
set -a; . "${ENV_FILE}"; set +a
elif [[ -f "./env.sh" ]]; then
set -a; . ./env.sh; set +a
fi
-: "${HOSTNAME:?HOSTNAME not set in env.sh}"
-: "${EMAIL:?EMAIL not set in env.sh}"
+: "${HOSTNAME:?HOSTNAME not set}"
+: "${EMAIL:?EMAIL not set}"
-export SYSTEMD_PAGER=""
-export SYSTEMD_COLORS="0"
-DEBIAN_FRONTEND=noninteractive
+export DEBIAN_FRONTEND=noninteractive
-# sanity check
-if [[ "${HOSTNAME}" == "localhost" || "${HOSTNAME}" == "127.0.0.1" || "${HOSTNAME}" == "ubuntu" ]]; then
- echo "ERROR: HOSTNAME cannot be 'localhost'. Use a public FQDN." >&2
- exit 1
-fi
-
-echo -e "\n* * * Starting nginx configuration for landing page, reverse proxy and WSS * * *"
-
-# set paths & ports vars
WEBROOT="/var/www/${HOSTNAME}"
-LE_ACME_DIR="/var/www/letsencrypt"
SITES_AVAIL="/etc/nginx/sites-available"
SITES_EN="/etc/nginx/sites-enabled"
-BASE_HTTP="${SITES_AVAIL}/${HOSTNAME}" # :80 vhost
-BASE_HTTPS="${SITES_AVAIL}/${HOSTNAME}-ssl" # :443 vhost (we’ll write it ourselves)
-WSS_AVAIL="${SITES_AVAIL}/wss-config-nym"
-BACKUP_DIR="/etc/nginx/sites-backups"
-
-NYM_PORT_HTTP="${NYM_PORT_HTTP:-8080}"
-NYM_PORT_WSS="${NYM_PORT_WSS:-9000}"
-WSS_LISTEN_PORT="${WSS_LISTEN_PORT:-9001}"
-
-mkdir -p "${WEBROOT}" "${LE_ACME_DIR}" "${BACKUP_DIR}" "${SITES_AVAIL}" "${SITES_EN}"
-
-# helpers
-neat_backup() {
- local file="$1"; [[ -f "$file" ]] || return 0
- local sha_now; sha_now="$(sha256sum "$file" | awk '{print $1}')" || return 0
- local tag; tag="$(basename "$file")"
- local latest="${BACKUP_DIR}/${tag}.latest"
- if [[ -f "$latest" ]]; then
- local sha_prev; sha_prev="$(awk '{print $1}' "$latest")"
- [[ "$sha_now" == "$sha_prev" ]] && return 0
- fi
- cp -a "$file" "${BACKUP_DIR}/${tag}.bak.$(date +%s)"
- echo "$sha_now ${tag}" > "$latest"
- ls -1t "${BACKUP_DIR}/${tag}.bak."* 2>/dev/null | tail -n +6 | xargs -r rm -f
-}
-ensure_enabled() {
- local src="$1"; local name; name="$(basename "$src")"
- ln -sf "$src" "${SITES_EN}/${name}"
-}
+HTTP_CONF="${SITES_AVAIL}/${HOSTNAME}"
+WSS_CONF="${SITES_AVAIL}/wss-config-nym"
-cert_ok() {
- [[ -s "/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem" && -s "/etc/letsencrypt/live/${HOSTNAME}/privkey.pem" ]]
-}
+echo
+echo "* * * Starting nginx configuration for landing page, reverse proxy and WSS * * *"
+
+###############################################################################
+# step 1: ensure landing page exists (local fetch -> github -> template)
+###############################################################################
-fetch_landing_html() {
- local url="https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/landing-page.html"
- mkdir -p "${WEBROOT}"
+mkdir -p "${WEBROOT}"
- if command -v curl >/dev/null 2>&1; then
- curl -fsSL "$url" -o "${WEBROOT}/index.html" || true
- else
- wget -qO "${WEBROOT}/index.html" "$url" || true
- fi
+SCRIPT_DIR="$(dirname "${ENV_FILE:-./env.sh}")"
+LOCAL_FETCHED_PAGE="${SCRIPT_DIR}/landing-page.html"
- if [[ ! -s "${WEBROOT}/index.html" ]]; then
- cat > "${WEBROOT}/index.html" <<'HTML'
+if [[ -s "${LOCAL_FETCHED_PAGE}" ]]; then
+ cp "${LOCAL_FETCHED_PAGE}" "${WEBROOT}/index.html"
+elif curl -fsSL \
+ https://raw.githubusercontent.com/nymtech/nym/develop/scripts/nym-node-setup/landing-page.html \
+ -o "${WEBROOT}/index.html"; then
+ :
+else
+ cat > "${WEBROOT}/index.html" <
-
-
-
-
- Nym Exit Gateway
-
-
-
- Nym Exit Gateway
- This is a Nym Exit Gateway. The operator of this router has no access to any of the data routing through that due to encryption design.
+
+nym node
+
+nym exit gateway
+this is a nym exit gateway.
+Operator contact: ${EMAIL}
-HTML
- fi
-}
-
-inject_email() {
- local file="${WEBROOT}/index.html"
- [[ -n "${EMAIL:-}" && -s "$file" ]] || return 0
-
- # Escape characters that would break sed replacement
- local esc_email
- esc_email="$(printf '%s' "$EMAIL" | sed -e 's/[&/\]/\\&/g')"
-
- # try to update existing meta (case-insensitive on the name attr)
- if grep -qiE ']+name=["'"'"']contact:email["'"'"']' "$file"; then
- sed -i -E \
- "s|(]+name=[\"']contact:email[\"'][^>]*content=\")[^\"]*(\"[^>]*>)|\1${esc_email}\2|I" \
- "$file" || true
- return 0
- fi
-
- # insert before if present (case-insensitive)
- if grep -qi '' "$file"; then
- awk -v email="$EMAIL" '
- BEGIN{IGNORECASE=1}
- /<\/head>/ && !done {
- print " "
- done=1
- }
- { print }
- ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
- return 0
- fi
-
- # fallback: append at end
- printf '\n\n' "$EMAIL" >> "$file" || true
-}
+EOF
+fi
-fetch_logo() {
- local logo_url="https://raw.githubusercontent.com/nymtech/websites/refs/heads/main/www/nym.com/public/images/Nym_meta_Image.png?token=GHSAT0AAAAAACEERII7URYRTFACZ4F2OWZ42GMCPBQ"
- mkdir -p "${WEBROOT}/images"
- if [[ ! -s "${WEBROOT}/images/nym_logo.png" ]]; then
- if command -v curl >/dev/null 2>&1; then
- curl -fsSL "$logo_url" -o "${WEBROOT}/images/nym_logo.png" || true
- else
- wget -qO "${WEBROOT}/images/nym_logo.png" "$logo_url" || true
- fi
- fi
-}
+echo "Landing page at ${WEBROOT}/index.html"
-reload_nginx() { nginx -t && systemctl reload nginx; }
+###############################################################################
+# step 2: remove default site and old configs, restart nginx
+###############################################################################
-# landing page (idempotent)
-fetch_landing_html
-inject_email
-fetch_logo
-echo "Landing page at ${WEBROOT}/index.html"
+echo "Cleaning existing nginx configuration"
-# disable default and stale SSL configs
+# remove default nginx site
[[ -L "${SITES_EN}/default" ]] && unlink "${SITES_EN}/default" || true
-for f in "${SITES_EN}"/*; do
- [[ -L "$f" ]] || continue
- if grep -q "/etc/letsencrypt/live/localhost" "$f"; then
- echo "Disabling vhost referencing localhost cert: $f"; unlink "$f"
- fi
-done
-for f in "${SITES_EN}"/*; do
- [[ -L "$f" ]] || continue
- if grep -qE 'listen\s+.*443' "$f"; then
- cert=$(awk '/ssl_certificate[ \t]+/ {print $2}' "$f" | tr -d ';' | head -n1)
- key=$(awk '/ssl_certificate_key[ \t]+/ {print $2}' "$f" | tr -d ';' | head -n1)
- if [[ -n "${cert:-}" && ! -s "$cert" ]] || [[ -n "${key:-}" && ! -s "$key" ]]; then
- echo "Disabling SSL vhost with missing cert/key: $f"; unlink "$f"
- fi
- fi
-done
-
-# HTTP :80 vhost (ACME-safe, proxy to :8080)
-neat_backup "${BASE_HTTP}"
-cat > "${BASE_HTTP}" < 8080)
+###############################################################################
+
+cat > "${HTTP_CONF}" </dev/null; then
- echo "WARNING: Can't reach Let's Encrypt directory. We'll still keep HTTP up." >&2
-fi
-THIS_IP="$(curl -fsS -4 https://ifconfig.me || true)"
-DNS_IP="$(getent ahostsv4 "${HOSTNAME}" 2>/dev/null | awk '{print $1; exit}')"
-echo "Public IPv4: ${THIS_IP:-unknown} DNS A(${HOSTNAME}): ${DNS_IP:-unresolved}"
-if [[ -n "${THIS_IP:-}" && -n "${DNS_IP:-}" && "${THIS_IP}" != "${DNS_IP}" ]]; then
- echo "WARNING: DNS for ${HOSTNAME} does not match this server's public IPv4."
-fi
-timedatectl show -p NTPSynchronized --value 2>/dev/null | grep -qi yes || timedatectl set-ntp true || true
-
-# install certbot if missing
-if ! command -v certbot >/dev/null 2>&1; then
- if command -v snap >/dev/null 2>&1; then
- snap install core || true; snap refresh core || true
- snap install --classic certbot; ln -sf /snap/bin/certbot /usr/bin/certbot
- else
- apt-get update -y >/dev/null 2>&1 || true
- apt-get install -y certbot >/dev/null 2>&1 || true
- fi
-fi
-# issue/renew via WEBROOT (no nginx auto-edit), non-fatal if it fails
-STAGING_FLAG=""; [[ "${CERTBOT_STAGING:-0}" == "1" ]] && STAGING_FLAG="--staging" && echo "Using Let's Encrypt STAGING."
-if ! cert_ok; then
- certbot certonly --non-interactive --agree-tos -m "${EMAIL}" -d "${HOSTNAME}" \
- --webroot -w "${LE_ACME_DIR}" ${STAGING_FLAG} || true
-fi
+ln -sf "${HTTP_CONF}" "${SITES_EN}/${HOSTNAME}"
-# create own 443 vhost (only if certs exist)
-if cert_ok; then
- neat_backup "${BASE_HTTPS}"
- cat > "${BASE_HTTPS}" </dev/null 2>&1 || true
+apt-get install -y certbot python3-certbot-nginx >/dev/null 2>&1 || true
- add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+echo "Requesting Let's Encrypt certificate for ${HOSTNAME}"
- location = /favicon.ico { return 204; access_log off; log_not_found off; }
+certbot --nginx --non-interactive --agree-tos --redirect --reuse-key \
+ -m "${EMAIL}" -d "${HOSTNAME}" || true
- location / {
- try_files \$uri \$uri/ @app;
- }
+###############################################################################
+# step 5: create WSS 9001 config using certbot-generated certs
+###############################################################################
- location @app {
- proxy_pass http://127.0.0.1:${NYM_PORT_HTTP};
- proxy_http_version 1.1;
- proxy_set_header X-Real-IP \$remote_addr;
- proxy_set_header Host \$host;
- proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
- }
-}
-EOF
- ensure_enabled "${BASE_HTTPS}"
+if [[ -s "/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem" ]]; then
+ echo "Certificate detected, creating WSS config"
- # optional: redirect HTTP->HTTPS (keeps ACME path in HTTP too via separate small server)
- neat_backup "${BASE_HTTP}"
- cat > "${BASE_HTTP}" < "${WSS_CONF}" < "${WSS_AVAIL}" </dev/null; then
- echo -e "${GREEN}✓ Rule exists: $chain $protocol port range $start_port:$end_port${NC}"
- return 0
- else
- echo -e "${RED}✗ Rule missing: $chain $protocol port range $start_port:$end_port${NC}"
-
- echo -e "${YELLOW}Dumping all rules in $chain:${NC}"
- iptables -L "$chain" -n | grep "$protocol"
-
- return 1
- fi
-}
-
-# Test port range rules
-test_port_range_rules() {
- echo -e "${YELLOW}Testing Port Range Rules...${NC}"
-
- # Select the essential port ranges for testing
- local port_ranges=(
- "20-21:tcp:FTP"
- "80-81:tcp:HTTP"
- "2082-2083:tcp:CPanel"
- "5222-5223:tcp:XMPP"
- "27000-27050:tcp:Steam (sampling)"
- "989-990:tcp:FTP over TLS"
- "5000-5005:tcp:RTP/VoIP"
- "8087-8088:tcp:Simplify Media"
- "8232-8233:tcp:Zcash"
- "8332-8333:tcp:Bitcoin"
- "18080-18081:tcp:Monero"
- )
-
- local total_failures=0
-
- for range in "${port_ranges[@]}"; do
- IFS=':' read -r port_range protocol service <<< "$range"
-
- # Extract start and end ports
- local start_port=$(echo "$port_range" | cut -d'-' -f1)
- local end_port=$(echo "$port_range" | cut -d'-' -f2)
-
- echo -e "${YELLOW}Testing $service $protocol port range $port_range${NC}"
-
- if iptables -t filter -C "$NYM_CHAIN" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT 2>/dev/null; then
- echo -e "${GREEN}✓ Rule exists: $NYM_CHAIN $protocol port range $start_port:$end_port${NC}"
- else
- echo -e "${RED}✗ Rule missing: $NYM_CHAIN $protocol port range $start_port:$end_port${NC}"
- ((total_failures++))
-
- echo -e "${YELLOW}Existing rules for protocol $protocol:${NC}"
- iptables -L "$NYM_CHAIN" -n | grep "$protocol"
- fi
- done
-
- if [ $total_failures -eq 0 ]; then
- return 0
- else
- return 1
- fi
-}
-
-test_critical_services() {
- echo -e "${YELLOW}Testing Critical Service Rules...${NC}"
-
- local tcp_services=(
- 22 # SSH
- 53 # DNS
- 443 # HTTPS
- 853 # DNS over TLS
- 1194 # OpenVPN
- )
-
- local udp_services=(
- 53 # DNS
- 123 # NTP
- 1194 # OpenVPN
- )
-
- local failures=0
-
- # Test TCP services
- for port in "${tcp_services[@]}"; do
- local rule_found=false
-
- # First check for exact match
- if iptables -t filter -C "$NYM_CHAIN" -p tcp --dport "$port" -j ACCEPT 2>/dev/null; then
- echo -e "${GREEN}✓ Rule exists: NYM-EXIT tcp port $port${NC}"
- rule_found=true
- else
- # If not found as exact port, search for it in port ranges
- # This checks if the port is covered by any range rule
- if iptables-save | grep -E "^-A $NYM_CHAIN.*tcp.*dpts:" | grep -qP "dpts:(\d+:)?$port(:|\d+)" || \
- iptables-save | grep -E "^-A $NYM_CHAIN.*tcp.*dpts:" | grep -qP "dpts:$port:"; then
- echo -e "${GREEN}✓ Rule exists: NYM-EXIT tcp port $port (covered by a range rule)${NC}"
- rule_found=true
- else
- echo -e "${RED}✗ Rule missing: NYM-EXIT tcp port $port${NC}"
- ((failures++))
- fi
- fi
- done
-
- # Test UDP services - similar approach
- for port in "${udp_services[@]}"; do
- local rule_found=false
-
- if iptables -t filter -C "$NYM_CHAIN" -p udp --dport "$port" -j ACCEPT 2>/dev/null; then
- echo -e "${GREEN}✓ Rule exists: NYM-EXIT udp port $port${NC}"
- rule_found=true
- else
- # If not found as exact port, search for it in port ranges
- if iptables-save | grep -E "^-A $NYM_CHAIN.*udp.*dpts:" | grep -qP "dpts:(\d+:)?$port(:|\d+)" || \
- iptables-save | grep -E "^-A $NYM_CHAIN.*udp.*dpts:" | grep -qP "dpts:$port:"; then
- echo -e "${GREEN}✓ Rule exists: NYM-EXIT udp port $port (covered by a range rule)${NC}"
- rule_found=true
- else
- echo -e "${RED}✗ Rule missing: NYM-EXIT udp port $port${NC}"
- ((failures++))
- fi
- fi
- done
-
- echo -e "${YELLOW}Relevant existing rules for HTTP (port 80):${NC}"
- iptables-save | grep -E "$NYM_CHAIN.*tcp" | grep -E "(dpt|dpts):.*80"
-
- return $failures
-}
-
-# Verify default reject rule exists
-test_default_reject_rule() {
- echo -e "${YELLOW}This test takes some time, do not quit the process${NC}"
- echo
- echo -e "${YELLOW}Testing Default Reject Rule...${NC}"
-
- # Try different patterns to detect the reject rule
- if iptables -L "$NYM_CHAIN" | grep -q "REJECT.*all.*anywhere.*anywhere.*reject-with"; then
- echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
- return 0
- elif iptables -L "$NYM_CHAIN" | grep -q "REJECT.*all -- .*everywhere.*everywhere"; then
- echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
- return 0
- elif iptables -L "$NYM_CHAIN" | grep -q "REJECT.*all.*0.0.0.0/0.*0.0.0.0/0"; then
- echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
- return 0
- elif iptables -n -L "$NYM_CHAIN" | grep -qE "REJECT.*all.*0.0.0.0/0.*0.0.0.0/0"; then
- echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
- return 0
- elif iptables -L "$NYM_CHAIN" | tail -1 | grep -q "REJECT"; then
- echo -e "${GREEN}✓ Default REJECT rule exists at the end of chain${NC}"
- return 0
- else
- echo -e "${RED}✗ Default REJECT rule missing${NC}"
- # Display the last 3 rules in the chain for debugging
- echo -e "${YELLOW}Last 3 rules in the chain:${NC}"
- iptables -L "$NYM_CHAIN" | tail -3
- return 1
- fi
-}
-
-run_all_tests() {
- local total_failures=0
- local total_tests=0
- local skip_default_reject=false
-
- # Parse arguments
- while [[ $# -gt 0 ]]; do
- case "$1" in
- --skip-default-reject)
- skip_default_reject=true
- shift
- ;;
- *)
- echo -e "${RED}Unknown argument: $1${NC}"
- exit 1
- ;;
- esac
- done
-
- local test_functions=(
- "test_port_range_rules"
- "test_critical_services"
- )
-
- if [ "$skip_default_reject" = false ]; then
- test_functions+=("test_default_reject_rule")
- fi
-
- echo -e "${YELLOW}Running Nym Exit Policy Verification Tests...${NC}"
-
- for test_func in "${test_functions[@]}"; do
- ((total_tests++))
- $test_func
- if [ $? -ne 0 ]; then
- ((total_failures++))
- echo -e "${RED}Test $test_func FAILED${NC}"
- else
- echo -e "${GREEN}Test $test_func PASSED${NC}"
- fi
- done
-
- echo -e "\n${YELLOW}Test Summary:${NC}"
- echo -e "Total Tests: $total_tests"
- echo -e "Failures: $total_failures"
-
- if [ $total_failures -eq 0 ]; then
- echo -e "${GREEN}All Tests Passed Successfully!${NC}"
- exit 0
- else
- echo -e "${RED}Some Tests Failed. Please review the iptables configuration.${NC}"
- exit 1
- fi
-}
-
-if [[ $EUID -ne 0 ]]; then
- echo -e "${RED}This script must be run as root${NC}"
- exit 1
-fi
-
-# Run the tests
-run_all_tests "$@"
diff --git a/scripts/wireguard-exit-policy/validate-exit-blocking-test.sh b/scripts/wireguard-exit-policy/validate-exit-blocking-test.sh
deleted file mode 100644
index e93d28a24fe..00000000000
--- a/scripts/wireguard-exit-policy/validate-exit-blocking-test.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/bin/bash
-
-validate_exit_policy() {
- echo "=== Nym Exit Policy Blocking Validation ==="
-
- # Check iptables rules
- echo "Checking iptables NYM-EXIT chain:"
- sudo iptables -L NYM-EXIT -v -n
-
- # Test IP ranges and individual IPs
- test_ips=(
- "5.188.10.0/24" # Blocked network range
- "31.132.36.50" # Specific blocked IP
- "37.9.42.100" # Another blocked IP
- )
-
- for target in "${test_ips[@]}"; do
- echo -e "\n\e[33mTesting blocking for $target\e[0m"
-
- # Multiple connection test methods
- methods=(
- "ping -c 4 -W 2"
- "curl -m 5 http://$target"
- "nc -z -w 5 $target 80"
- "telnet $target 80"
- )
-
- for method in "${methods[@]}"; do
- echo -n "Testing with $method: "
- if sudo timeout 5 $method >/dev/null 2>&1; then
- echo -e "\e[31mFAILED: Connection succeeded (Blocking ineffective)\e[0m"
- else
- echo -e "\e[32mBLOCKED\e[0m"
- fi
- done
- done
-}
-
-# Run the test
-validate_exit_policy
\ No newline at end of file
diff --git a/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh b/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh
deleted file mode 100644
index bce3d09711c..00000000000
--- a/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh
+++ /dev/null
@@ -1,679 +0,0 @@
-#!/bin/bash
-#
-# Nym Wireguard Exit Policy Manager
-# Version: 1.0.0
-#
-# This script manages iptables rules for Nym Wireguard exit policies
-# Features:
-# - Implements the Nym exit policy from official documentation
-# - Makes rules persistent across reboots
-# - Provides commands to inspect and manage rules
-# - Groups rules logically for easier management
-# - Integrates with existing Nym node configuration
-#
-# Usage: ./nym-exit-policy.sh [command]
-
-set -e
-
-NETWORK_DEVICE=$(ip route show default | awk '/default/ {print $5}')
-WG_INTERFACE="nymwg"
-NYM_CHAIN="NYM-EXIT"
-POLICY_FILE="/etc/nym/exit-policy.txt"
-EXIT_POLICY_LOCATION="https://nymtech.net/.wellknown/network-requester/exit-policy.txt"
-
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[0;33m'
-NC='\033[0m'
-
-add_port_rules() {
- local chain="$1"
- local port="$2"
- local protocol="${3:-tcp}"
-
- # Check if the port contains a range
- if [[ "$port" == *"-"* ]]; then
- # Port range handling - add as a single rule with a range
- local start_port=$(echo "$port" | cut -d'-' -f1)
- local end_port=$(echo "$port" | cut -d'-' -f2)
-
- if ! $chain -C "$NYM_CHAIN" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT 2>/dev/null; then
- $chain -A "$NYM_CHAIN" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT
- echo -e " ${GREEN}Added: $NYM_CHAIN $protocol port range $start_port:$end_port${NC}"
- fi
- else
- # Single port handling
- if ! $chain -C "$NYM_CHAIN" -p "$protocol" --dport "$port" -j ACCEPT 2>/dev/null; then
- $chain -A "$NYM_CHAIN" -p "$protocol" --dport "$port" -j ACCEPT
- echo -e " ${GREEN}Added: $NYM_CHAIN $protocol port $port${NC}"
- fi
- fi
-}
-
-install_dependencies() {
- if ! dpkg -s iptables-persistent >/dev/null 2>&1; then
- echo -e "${YELLOW}Installing iptables-persistent...${NC}"
- apt-get update
- DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent
- echo -e "${GREEN}iptables-persistent installed.${NC}"
- else
- echo -e "${GREEN}iptables-persistent is already installed.${NC}"
- fi
-
- # Check for other required dependencies
- for cmd in iptables ip6tables ip grep sed awk wget curl; do
- if ! command -v "$cmd" &>/dev/null; then
- echo -e "${YELLOW}Installing $cmd...${NC}"
- apt-get install -y "$cmd"
- fi
- done
-}
-
-configure_ip_forwarding() {
- echo -e "${YELLOW}Configuring IP forwarding...${NC}"
-
- # Remove any existing forwarding settings to avoid duplicates
- sed -i "/^net.ipv6.conf.all.forwarding=/d" /etc/sysctl.conf
- sed -i "/^net.ipv4.ip_forward=/d" /etc/sysctl.conf
-
- # Add forwarding settings
- echo "net.ipv6.conf.all.forwarding=1" | tee -a /etc/sysctl.conf
- echo "net.ipv4.ip_forward=1" | tee -a /etc/sysctl.conf
-
- # Apply changes
- sysctl -p /etc/sysctl.conf
-
- # Verify settings
- ipv4_forwarding=$(cat /proc/sys/net/ipv4/ip_forward)
- ipv6_forwarding=$(cat /proc/sys/net/ipv6/conf/all/forwarding)
-
- if [ "$ipv4_forwarding" == "1" ] && [ "$ipv6_forwarding" == "1" ]; then
- echo -e "${GREEN}IP forwarding configured successfully.${NC}"
- else
- echo -e "${RED}Failed to configure IP forwarding.${NC}"
- exit 1
- fi
-}
-
-create_nym_chain() {
- echo -e "${YELLOW}Creating Nym exit policy chain...${NC}"
-
- # Check if the chain already exists
- if iptables -L "$NYM_CHAIN" &>/dev/null; then
- echo -e "${YELLOW}Chain $NYM_CHAIN already exists. Flushing it...${NC}"
- iptables -F "$NYM_CHAIN"
- else
- echo -e "${YELLOW}Creating chain $NYM_CHAIN...${NC}"
- iptables -N "$NYM_CHAIN"
- fi
-
- # Do the same for IPv6
- if ip6tables -L "$NYM_CHAIN" &>/dev/null; then
- echo -e "${YELLOW}Chain $NYM_CHAIN already exists in ip6tables. Flushing it...${NC}"
- ip6tables -F "$NYM_CHAIN"
- else
- echo -e "${YELLOW}Creating chain $NYM_CHAIN in ip6tables...${NC}"
- ip6tables -N "$NYM_CHAIN"
- fi
-
- # Link it to the FORWARD chain if not already linked
- if ! iptables -C FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN" 2>/dev/null; then
- echo -e "${YELLOW}Linking $NYM_CHAIN to FORWARD chain...${NC}"
- iptables -A FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN"
- fi
-
- # Link IPv6 chain
- if ! ip6tables -C FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN" 2>/dev/null; then
- echo -e "${YELLOW}Linking $NYM_CHAIN to IPv6 FORWARD chain...${NC}"
- ip6tables -A FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN"
- fi
-}
-
-setup_nat_rules() {
- echo -e "${YELLOW}Setting up NAT rules...${NC}"
-
- # Check if NAT rule for IPv4 exists
- if ! iptables -t nat -C POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE 2>/dev/null; then
- iptables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE
- echo -e "${GREEN}Added IPv4 NAT rule.${NC}"
- else
- echo -e "${GREEN}IPv4 NAT rule already exists.${NC}"
- fi
-
- # Check if NAT rule for IPv6 exists
- if ! ip6tables -t nat -C POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE 2>/dev/null; then
- ip6tables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE
- echo -e "${GREEN}Added IPv6 NAT rule.${NC}"
- else
- echo -e "${GREEN}IPv6 NAT rule already exists.${NC}"
- fi
-
- # Setup forwarding rules for Wireguard interface
- if ! iptables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null; then
- iptables -A FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT
- echo -e "${GREEN}Added IPv4 forwarding rule (WG → Internet).${NC}"
- fi
-
- if ! iptables -C FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null; then
- iptables -A FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT
- echo -e "${GREEN}Added IPv4 forwarding rule (Internet → WG for established connections).${NC}"
- fi
-
- # IPv6 forwarding rules
- if ! ip6tables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null; then
- ip6tables -A FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT
- echo -e "${GREEN}Added IPv6 forwarding rule (WG → Internet).${NC}"
- fi
-
- if ! ip6tables -C FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null; then
- ip6tables -A FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT
- echo -e "${GREEN}Added IPv6 forwarding rule (Internet → WG for established connections).${NC}"
- fi
-}
-
-configure_dns_and_icmp() {
- echo -e "${YELLOW}Configuring DNS and ICMP rules...${NC}"
-
- # ICMP rules for ping
- if ! iptables -C INPUT -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null; then
- iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
- echo -e "${GREEN}Added IPv4 ICMP rule (allow ping requests).${NC}"
- fi
-
- if ! iptables -C OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT 2>/dev/null; then
- iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
- echo -e "${GREEN}Added IPv4 ICMP rule (allow ping replies).${NC}"
- fi
-
- # ICMPv6 rules for ping6
- if ! ip6tables -C INPUT -p ipv6-icmp -j ACCEPT 2>/dev/null; then
- ip6tables -A INPUT -p ipv6-icmp -j ACCEPT
- echo -e "${GREEN}Added IPv6 ICMP rule (allow ping6).${NC}"
- fi
-
- # DNS rules
- if ! iptables -C INPUT -p udp --dport 53 -j ACCEPT 2>/dev/null; then
- iptables -A INPUT -p udp --dport 53 -j ACCEPT
- echo -e "${GREEN}Added IPv4 DNS rule (UDP).${NC}"
- fi
-
- if ! iptables -C INPUT -p tcp --dport 53 -j ACCEPT 2>/dev/null; then
- iptables -A INPUT -p tcp --dport 53 -j ACCEPT
- echo -e "${GREEN}Added IPv4 DNS rule (TCP).${NC}"
- fi
-
- # IPv6 DNS rules
- if ! ip6tables -C INPUT -p udp --dport 53 -j ACCEPT 2>/dev/null; then
- ip6tables -A INPUT -p udp --dport 53 -j ACCEPT
- echo -e "${GREEN}Added IPv6 DNS rule (UDP).${NC}"
- fi
-
- if ! ip6tables -C INPUT -p tcp --dport 53 -j ACCEPT 2>/dev/null; then
- ip6tables -A INPUT -p tcp --dport 53 -j ACCEPT
- echo -e "${GREEN}Added IPv6 DNS rule (TCP).${NC}"
- fi
-}
-
-# Apply Spamhaus blocklist from the Nym exit policy
-apply_spamhaus_blocklist() {
- echo -e "${YELLOW}Applying Spamhaus blocklist...${NC}"
-
- # Create directory if not exists
- mkdir -p "$(dirname "$POLICY_FILE")"
-
- # Try to download the policy file
- echo -e "${YELLOW}Downloading exit policy from $EXIT_POLICY_LOCATION${NC}"
- if ! wget -q "$EXIT_POLICY_LOCATION" -O "$POLICY_FILE" 2>/dev/null; then
- echo -e "${RED}Failed to download exit policy. Using minimal blocklist.${NC}"
-
- # Create a minimal policy file with a few entries
- cat >"$POLICY_FILE" </dev/null; then
- iptables -A "$NYM_CHAIN" -d "$ip_range" -j REJECT
- fi
-
- # Apply IPv6 rules for IPv6 addresses
- if [[ "$ip_range" == *":"* ]] && ! ip6tables -C "$NYM_CHAIN" -d "$ip_range" -j REJECT 2>/dev/null; then
- ip6tables -A "$NYM_CHAIN" -d "$ip_range" -j REJECT
- fi
- fi
- done
-
- echo -e "${GREEN}Blocklist applied successfully.${NC}"
-}
-
-add_default_reject_rule() {
- echo -e "${YELLOW}Adding default reject rule...${NC}"
-
- # First remove any existing plain reject rules (without specific destinations)
- iptables -D "$NYM_CHAIN" -j REJECT 2>/dev/null || true
- iptables -D "$NYM_CHAIN" -j REJECT --reject-with icmp-port-unreachable 2>/dev/null || true
- ip6tables -D "$NYM_CHAIN" -j REJECT 2>/dev/null || true
- ip6tables -D "$NYM_CHAIN" -j REJECT --reject-with icmp6-port-unreachable 2>/dev/null || true
-
- # Add the default catch-all reject rule (must be the last rule in the chain)
- iptables -A "$NYM_CHAIN" -j REJECT --reject-with icmp-port-unreachable
- ip6tables -A "$NYM_CHAIN" -j REJECT --reject-with icmp6-port-unreachable
-
- echo -e "${GREEN}Default reject rule added successfully.${NC}"
-}
-
-apply_port_allowlist() {
- echo -e "${YELLOW}Applying allowed ports...${NC}"
-
- # Dictionary of services and their ports
- declare -A PORT_MAPPINGS=(
- ["FTP"]="20-21"
- ["SSH"]="22"
- ["WHOIS"]="43"
- ["DNS"]="53"
- ["Finger"]="79"
- ["HTTP"]="80-81"
- ["Kerberos"]="88"
- ["POP3"]="110"
- ["NTP"]="123"
- ["IMAP"]="143"
- ["IMAP3"]="220"
- ["LDAP"]="389"
- ["HTTPS"]="443"
- ["SMBWindowsFileShare"]="445"
- ["Kpasswd"]="464"
- ["RTSP"]="554"
- ["LDAPS"]="636"
- ["SILC"]="706"
- ["KerberosAdmin"]="749"
- ["DNSOverTLS"]="853"
- ["Rsync"]="873"
- ["VMware"]="902-904"
- ["RemoteHTTPS"]="981"
- ["FTPOverTLS"]="989-990"
- ["NetnewsAdmin"]="991"
- ["TelnetOverTLS"]="992"
- ["IMAPOverTLS"]="993"
- ["POP3OverTLS"]="995"
- ["OpenVPN"]="1194"
- ["QTServerAdmin"]="1220"
- ["PKTKRB"]="1293"
- ["MSSQL"]="1433"
- ["VLSILicenseManager"]="1500"
- ["OracleDB"]="1521"
- ["Sametime"]="1533"
- ["GroupWise"]="1677"
- ["PPTP"]="1723"
- ["RTSPAlt"]="1755"
- ["MSNP"]="1863"
- ["NFS"]="2049"
- ["CPanel"]="2082-2083"
- ["GNUnet"]="2086-2087"
- ["NBX"]="2095-2096"
- ["Zephyr"]="2102-2104"
- ["XboxLive"]="3074"
- ["MySQL"]="3306"
- ["SVN"]="3690"
- ["RWHOIS"]="4321"
- ["Virtuozzo"]="4643"
- ["RTPVOIP"]="5000-5005"
- ["MMCC"]="5050"
- ["ICQ"]="5190"
- ["XMPP"]="5222-5223"
- ["AndroidMarket"]="5228"
- ["PostgreSQL"]="5432"
- ["MongoDBDefault"]="27017"
- ["Electrum"]="8082"
- ["SimplifyMedia"]="8087-8088"
- ["Zcash"]="8232-8233"
- ["Bitcoin"]="8332-8333"
- ["HTTPSALT"]="8443"
- ["TeamSpeak"]="8767"
- ["MQTTS"]="8883"
- ["HTTPProxy"]="8888"
- ["TorORPort"]="9001"
- ["TorDirPort"]="9030"
- ["Tari"]="9053"
- ["Gaming"]="9339"
- ["Git"]="9418"
- ["HTTPSALT2"]="9443"
- ["Lightning"]="9735"
- ["NDMP"]="10000"
- ["OpenPGP"]="11371"
- ["Monero"]="18080-18081"
- ["MoneroRPC"]="18089"
- ["GoogleVoice"]="19294"
- ["EnsimControlPanel"]="19638"
- ["DarkFiTor"]="25551"
- ["Minecraft"]="25565"
- ["DarkFi"]="26661"
- ["Steam"]="27000-27050"
- ["ElectrumSSL"]="50002"
- ["MOSH"]="60000-61000"
- ["Mumble"]="64738"
- )
-
- # Add TCP and UDP rules for each allowed port
- for service in "${!PORT_MAPPINGS[@]}"; do
- port="${PORT_MAPPINGS[$service]}"
- echo -e "${YELLOW}Adding rules for $service (Port: $port)${NC}"
-
- # Add both TCP and UDP rules for all services
- add_port_rules iptables "$port" "tcp"
- add_port_rules ip6tables "$port" "tcp"
- add_port_rules iptables "$port" "udp"
- add_port_rules ip6tables "$port" "udp"
- done
-
- add_default_reject_rule
-
- echo -e "${GREEN}Port allowlist applied successfully.${NC}"
-}
-
-safe_iptables_rule_remove() {
- local chain="$1"
- local table="${2:-filter}"
- local interface="$3"
-
- # Remove rule if it exists
- if iptables -t "$table" -C "$chain" -o "$interface" -j "$NYM_CHAIN" 2>/dev/null; then
- iptables -t "$table" -D "$chain" -o "$interface" -j "$NYM_CHAIN"
- fi
-}
-
-safe_ip6tables_rule_remove() {
- local chain="$1"
- local table="${2:-filter}"
- local interface="$3"
-
- # Remove rule if it exists
- if ip6tables -t "$table" -C "$chain" -o "$interface" -j "$NYM_CHAIN" 2>/dev/null; then
- ip6tables -t "$table" -D "$chain" -o "$interface" -j "$NYM_CHAIN"
- fi
-}
-
-clear_rules() {
- echo -e "${YELLOW}Clearing Nym exit policy rules...${NC}"
-
- # Flush all rules in the NYM-EXIT chain
- iptables -F "$NYM_CHAIN" 2>/dev/null || true
- ip6tables -F "$NYM_CHAIN" 2>/dev/null || true
-
- # Remove the chain from FORWARD if it exists
- iptables -D FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN" 2>/dev/null || true
- ip6tables -D FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN" 2>/dev/null || true
-
- # Delete the chains
- iptables -X "$NYM_CHAIN" 2>/dev/null || true
- ip6tables -X "$NYM_CHAIN" 2>/dev/null || true
-
- echo -e "${GREEN}Nym exit policy rules cleared successfully.${NC}"
-}
-
-remove_duplicate_rules() {
- local interface="$1"
-
- if [[ -z "$interface" ]]; then
- echo -e "${RED}Error: No interface specified. Usage: $0 remove-duplicates ${NC}" >&2
- exit 1
- fi
-
- echo -e "${YELLOW}Detecting and removing duplicate rules for $interface...${NC}"
-
- # Verbose duplicate rule detection for IPv4
- echo -e "${YELLOW}Checking IPv4 duplicate rules:${NC}"
- iptables-save | grep -E "(-A FORWARD|-A $NYM_CHAIN)" | grep "$interface" | sort | uniq -d && {
- echo -e "${RED}Duplicate IPv4 rules found! Removing...${NC}"
- # Remove duplicates by saving unique rules
- iptables-save | grep -E "(-A FORWARD|-A $NYM_CHAIN)" | grep "$interface" | sort | uniq | while read -r rule; do
- # Carefully remove duplicates
- full_rule=$(echo "$rule" | sed 's/^-A/iptables -D/')
- eval "$full_rule" 2>/dev/null
- done
- } || echo -e "${GREEN}No duplicate IPv4 rules found.${NC}"
-
- # Verbose duplicate rule detection for IPv6
- echo -e "${YELLOW}Checking IPv6 duplicate rules:${NC}"
- ip6tables-save | grep -E "(-A FORWARD|-A $NYM_CHAIN)" | grep "$interface" | sort | uniq -d && {
- echo -e "${RED}Duplicate IPv6 rules found! Removing...${NC}"
- # Remove duplicates by saving unique rules
- ip6tables-save | grep -E "(-A FORWARD|-A $NYM_CHAIN)" | grep "$interface" | sort | uniq | while read -r rule; do
- # Carefully remove duplicates
- full_rule=$(echo "$rule" | sed 's/^-A/ip6tables -D/')
- eval "$full_rule" 2>/dev/null
- done
- } || echo -e "${GREEN}No duplicate IPv6 rules found.${NC}"
-
- # Additional verification
- echo -e "\n${YELLOW}Rule verification:${NC}"
- echo "IPv4 Rules:"
- iptables -L FORWARD -v -n | grep "$interface"
- echo "IPv6 Rules:"
- ip6tables -L FORWARD -v -n | grep "$interface"
-
- echo -e "${GREEN}Duplicate rule removal process completed.${NC}"
-}
-
-save_rules() {
- echo -e "${YELLOW}Saving iptables rules to make them persistent...${NC}"
-
- if [ -d "/etc/iptables" ]; then
- # For Debian/Ubuntu with iptables-persistent
- iptables-save >/etc/iptables/rules.v4
- ip6tables-save >/etc/iptables/rules.v6
- echo -e "${GREEN}Rules saved to /etc/iptables/rules.v4 and /etc/iptables/rules.v6${NC}"
- else
- # Fallback method
- iptables-save >/etc/iptables.rules
- ip6tables-save >/etc/ip6tables.rules
- echo -e "${GREEN}Rules saved to /etc/iptables.rules and /etc/ip6tables.rules${NC}"
-
- # Add loading script to rc.local if it doesn't exist
- if [ ! -f "/etc/network/if-pre-up.d/iptables" ]; then
- cat >/etc/network/if-pre-up.d/iptables </dev/null; then
- echo -e "${RED}WARNING: Wireguard interface $WG_INTERFACE not found!${NC}"
- return 1
- fi
-
- # Interface details
- echo -e "\n${YELLOW}Interface Details:${NC}"
- ip link show "$WG_INTERFACE"
-
- # IP Addresses
- echo -e "\n${YELLOW}IP Addresses:${NC}"
- ip -4 addr show dev "$WG_INTERFACE"
- ip -6 addr show dev "$WG_INTERFACE"
-
- # Iptables Chain Status
- echo -e "\n${YELLOW}Iptables Chains:${NC}"
- {
- echo "IPv4 Chain:"
- iptables -L "$NYM_CHAIN" -n -v
- echo -e "\nIPv6 Chain:"
- ip6tables -L "$NYM_CHAIN" -n -v
- } || echo "One or both chains not found"
-
- # Forwarding Status
- echo -e "\n${YELLOW}IP Forwarding:${NC}"
- echo "IPv4: $(cat /proc/sys/net/ipv4/ip_forward)"
- echo "IPv6: $(cat /proc/sys/net/ipv6/conf/all/forwarding)"
-}
-
-test_connectivity() {
- echo -e "${YELLOW}Testing connectivity through $WG_INTERFACE...${NC}"
-
- # More comprehensive interface check
- interface_info=$(ip link show "$WG_INTERFACE" 2>/dev/null)
-
- if [ -z "$interface_info" ]; then
- echo -e "${RED}Interface $WG_INTERFACE not found!${NC}"
- return 1
- fi
-
- # Check for multiple possible interface states
- if ! echo "$interface_info" | grep -qE "state (UP|UNKNOWN|DORMANT)"; then
- echo -e "${RED}Interface $WG_INTERFACE is not in an active state!${NC}"
- echo "$interface_info"
- return 1
- fi
-
- # Detailed interface information
- echo -e "${GREEN}Interface Details:${NC}"
- echo "$interface_info"
-
- # Get IP addresses with more robust method
- ipv4_address=$(ip -4 addr show dev "$WG_INTERFACE" | grep -oP '(?<=inet\s)\d+\.\d+\.\d+\.\d+/\d+' | cut -d'/' -f1 | head -n1)
- ipv6_address=$(ip -6 addr show dev "$WG_INTERFACE" scope global | grep -oP '(?<=inet6\s)[0-9a-f:]+/\d+' | cut -d'/' -f1 | head -n1)
-
- echo -e "${GREEN}IPv4 Address:${NC} ${ipv4_address:-Not found}"
- echo -e "${GREEN}IPv6 Address:${NC} ${ipv6_address:-Not found}"
-
- # Connectivity tests
- if [[ -n "$ipv4_address" ]]; then
- echo -e "${YELLOW}Testing IPv4 connectivity from $ipv4_address...${NC}"
-
- # Ping test
- if timeout 5 ping -c 3 -I "$ipv4_address" 8.8.8.8 >/dev/null 2>&1; then
- echo -e "${GREEN}IPv4 connectivity to 8.8.8.8: Success${NC}"
- else
- echo -e "${RED}IPv4 connectivity to 8.8.8.8: Failed${NC}"
- fi
-
- # DNS resolution test
- if timeout 5 ping -c 3 -I "$ipv4_address" google.com >/dev/null 2>&1; then
- echo -e "${GREEN}IPv4 DNS resolution: Success${NC}"
- else
- echo -e "${RED}IPv4 DNS resolution: Failed${NC}"
- fi
-
- # HTTP(S) connectivity test
- if command -v curl &>/dev/null; then
- if timeout 5 curl -s --interface "$ipv4_address" -o /dev/null -w "%{http_code}" https://www.google.com | grep -q "200"; then
- echo -e "${GREEN}IPv4 HTTPS connectivity: Success${NC}"
- else
- echo -e "${RED}IPv4 HTTPS connectivity: Failed${NC}"
- fi
- fi
- else
- echo -e "${RED}No IPv4 address configured on $WG_INTERFACE${NC}"
- fi
-
- # Similar tests for IPv6 if available
- if [[ -n "$ipv6_address" ]]; then
- echo -e "${YELLOW}Testing IPv6 connectivity from $ipv6_address...${NC}"
-
- if timeout 5 ping6 -c 3 -I "$ipv6_address" 2001:4860:4860::8888 >/dev/null 2>&1; then
- echo -e "${GREEN}IPv6 connectivity to Google DNS: Success${NC}"
- else
- echo -e "${RED}IPv6 connectivity to Google DNS: Failed${NC}"
- fi
-
- if timeout 5 ping6 -c 3 -I "$ipv6_address" google.com >/dev/null 2>&1; then
- echo -e "${GREEN}IPv6 DNS resolution: Success${NC}"
- else
- echo -e "${RED}IPv6 DNS resolution: Failed${NC}"
- fi
-
- if command -v curl &>/dev/null; then
- if timeout 5 curl -s --interface "$ipv6_address" -o /dev/null -w "%{http_code}" https://www.google.com | grep -q "200"; then
- echo -e "${GREEN}IPv6 HTTPS connectivity: Success${NC}"
- else
- echo -e "${RED}IPv6 HTTPS connectivity: Failed${NC}"
- fi
- fi
- else
- echo -e "${YELLOW}No IPv6 address configured on $WG_INTERFACE${NC}"
- fi
-
- echo -e "${GREEN}Connectivity tests completed.${NC}"
-}
-
-main() {
- # Check for root privileges
- if [ "$(id -u)" -ne 0 ]; then
- echo -e "${RED}This script must be run as root${NC}" >&2
- exit 1
- fi
-
- # Parse command-line arguments
- case "$1" in
- install)
- install_dependencies
- configure_ip_forwarding
- create_nym_chain
- setup_nat_rules
- configure_dns_and_icmp
- apply_spamhaus_blocklist
- apply_port_allowlist
- save_rules
- echo -e "${GREEN}Nym exit policy installed successfully.${NC}"
- ;;
- status)
- show_status
- ;;
- test)
- test_connectivity
- ;;
- clear)
- clear_rules
- echo -e "${GREEN}Nym exit policy rules cleared.${NC}"
- ;;
- remove-duplicates)
- remove_duplicate_rules "$2"
- ;;
- help | --help | -h)
- echo "Usage: $0 [command]"
- echo ""
- echo "Commands:"
- echo " install Install and configure Nym exit policy"
- echo " status Show current Nym exit policy status"
- echo " test Test connectivity through Wireguard interface"
- echo " clear Remove all Nym exit policy rules"
- echo " remove-duplicates Remove duplicate iptables rules for an interface"
- echo " help Show this help message"
- ;;
- *)
- echo -e "${RED}Invalid command. Use '$0 help' for usage information.${NC}" >&2
- exit 1
- ;;
- esac
-}
-
-main "$@"