Created
February 13, 2026 12:34
-
-
Save deas/b28b194f347fc668d3af26b6a8993827 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -euo pipefail | |
| # Check for root privileges | |
| if [[ $EUID -ne 0 ]]; then | |
| echo "Error: This script must be run as root" >&2 | |
| exit 1 | |
| fi | |
| show_usage() { | |
| echo "Usage:" | |
| echo " $0 <name> <destination_ip>[:port] <source_ip1> [source_ip2] ... - Create DNAT rules" | |
| echo " $0 <name> remove - Remove DNAT rules" | |
| echo " $0 list - List active multi-redirect tables" | |
| echo "" | |
| echo "Examples:" | |
| echo " $0 myapp 192.168.1.10:8080 10.0.0.1 10.0.0.2 - Forward port 8080 from sources" | |
| echo " $0 myapp 192.168.1.10 10.0.0.1 10.0.0.2 - Forward all ports from sources" | |
| exit 1 | |
| } | |
| # Show usage if no arguments or help requested | |
| if [[ $# -eq 0 ]] || [[ "${1:-}" == "-h" ]] || [[ "${1:-}" == "--help" ]]; then | |
| show_usage | |
| fi | |
| # Handle list command (no name required) | |
| if [[ "$1" == "list" ]]; then | |
| echo "Active DNAT multi-redirect tables:" | |
| nft list tables 2>/dev/null | grep "table ip" | awk '{print $3}' | while read -r table; do | |
| if nft list table ip "$table" 2>/dev/null | grep -q "dnat to"; then | |
| echo " - $table" | |
| fi | |
| done | |
| exit 0 | |
| fi | |
| # Now we need at least a name | |
| if [[ $# -lt 1 ]]; then | |
| show_usage | |
| fi | |
| TABLE_NAME="$1" | |
| # Check if second argument is "remove" | |
| if [[ "${2:-}" == "remove" ]]; then | |
| if [[ $# -ne 2 ]]; then | |
| echo "Error: remove takes no additional arguments" >&2 | |
| show_usage | |
| fi | |
| if ! nft list table ip "$TABLE_NAME" >/dev/null 2>&1; then | |
| echo "Table '$TABLE_NAME' does not exist" >&2 | |
| exit 1 | |
| fi | |
| nft delete table ip "$TABLE_NAME" | |
| echo "Removed DNAT rules for table: $TABLE_NAME" | |
| exit 0 | |
| fi | |
| # Create mode: name destination_ip[:port] source_ip1 [source_ip2 ...] | |
| if [[ $# -lt 3 ]]; then | |
| echo "Error: Invalid number of arguments. Need at least: name, destination_ip[:port], and one source_ip" >&2 | |
| show_usage | |
| fi | |
| DEST="$2" | |
| shift 2 | |
| SOURCE_IPS=("$@") | |
| # Parse destination - can be IP:port or just IP (for all ports) | |
| DST_IP="" | |
| PORT="" | |
| if [[ "$DEST" =~ ^([^:]+):([0-9]+)$ ]]; then | |
| # IP:port format | |
| DST_IP="${BASH_REMATCH[1]}" | |
| PORT="${BASH_REMATCH[2]}" | |
| elif [[ "$DEST" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| # IP only - forward all ports | |
| DST_IP="$DEST" | |
| PORT="" | |
| else | |
| echo "Error: Destination must be IP:port (e.g., 192.168.1.10:8080) or IP address (for all ports)" >&2 | |
| exit 1 | |
| fi | |
| # Enable IP forwarding | |
| sysctl -w net.ipv4.ip_forward=1 >/dev/null | |
| # Build nftables rules for source IPs | |
| PREROUTING_RULES="" | |
| OUTPUT_RULES="" | |
| for src_ip in "${SOURCE_IPS[@]}"; do | |
| if [[ -n "$PORT" ]]; then | |
| # Specific port forwarding | |
| PREROUTING_RULES+=" tcp dport $PORT ip daddr $src_ip dnat to $DST_IP:$PORT | |
| " | |
| OUTPUT_RULES+=" tcp dport $PORT ip daddr $src_ip dnat to $DST_IP:$PORT | |
| " | |
| else | |
| # All ports forwarding | |
| PREROUTING_RULES+=" ip daddr $src_ip dnat to $DST_IP | |
| " | |
| OUTPUT_RULES+=" ip daddr $src_ip dnat to $DST_IP | |
| " | |
| fi | |
| done | |
| # Create or replace the nftables table | |
| nft " | |
| add table ip $TABLE_NAME | |
| flush table ip $TABLE_NAME | |
| table ip $TABLE_NAME { | |
| chain prerouting { | |
| type nat hook prerouting priority dstnat; policy accept; | |
| $PREROUTING_RULES } | |
| chain output { | |
| type nat hook output priority dstnat; policy accept; | |
| $OUTPUT_RULES } | |
| chain postrouting { | |
| type nat hook postrouting priority srcnat; policy accept; | |
| ip daddr $DST_IP masquerade | |
| } | |
| } | |
| " | |
| SOURCE_IPS_STR=$(IFS=, ; echo "${SOURCE_IPS[*]}") | |
| if [[ -n "$PORT" ]]; then | |
| echo "Created DNAT rules: $SOURCE_IPS_STR:$PORT -> $DST_IP:$PORT (table: $TABLE_NAME)" | |
| else | |
| echo "Created DNAT rules: $SOURCE_IPS_STR -> $DST_IP (all ports, table: $TABLE_NAME)" | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment