|
| 1 | +#!/bin/sh |
| 2 | + |
| 3 | +# |
| 4 | +# Adds TCP/UDP port forwarding rules to the pf firewall (MacOS/BSD). |
| 5 | +# |
| 6 | +# Adds rules for both TCP and UDP in addition to those from /etc/pf.conf. |
| 7 | +# Requires an existing rdr-anchor entry in /etc/pf.conf. |
| 8 | +# Only adds rules temporarily, without changing any files. |
| 9 | +# |
| 10 | +# Usage: ./forward-ports.sh [[nic:]port=[ip:]port [...]] |
| 11 | +# |
| 12 | +# If no network interface is given, forwards from all interfaces. |
| 13 | +# If no IP is given, forwards to 127.0.0.1. |
| 14 | +# If no port forwarding rule is given, resets to the rules from /etc/pf.conf. |
| 15 | +# |
| 16 | +# e.g. forwarding ports 80 and 443 on network interface en0 to ports 8080 and |
| 17 | +# 8443 on localhost respectively: |
| 18 | +# ./forward-ports.sh en0:80=8080 en0:443=8443 |
| 19 | +# |
| 20 | +# Copyright 2019, Sebastian Tschan |
| 21 | +# https://blueimp.net |
| 22 | +# |
| 23 | +# Licensed under the MIT license: |
| 24 | +# https://opensource.org/licenses/MIT |
| 25 | +# |
| 26 | + |
| 27 | +set -e |
| 28 | + |
| 29 | +RULES= |
| 30 | +NEWLINE=' |
| 31 | +' |
| 32 | + |
| 33 | +print_usage_exit() { |
| 34 | + if [ -n "$RULES" ]; then |
| 35 | + printf '\nError in custom rules:\n%s\n' "$RULES" >&2 |
| 36 | + fi |
| 37 | + echo "Usage: $0 [[nic:]port=[ip:]port [...]]" >&2 |
| 38 | + exit 1 |
| 39 | +} |
| 40 | + |
| 41 | +print_nat_rules() { |
| 42 | + echo |
| 43 | + echo 'Loaded NAT rules:' |
| 44 | + sudo pfctl -s nat 2>/dev/null |
| 45 | + echo |
| 46 | +} |
| 47 | + |
| 48 | +# Print usage and exit if option arguments like "-h" are used: |
| 49 | +if [ "${1#-}" != "$1" ]; then print_usage_exit; fi |
| 50 | + |
| 51 | +while test $# -gt 0; do |
| 52 | + # Separate the from=to parts: |
| 53 | + from=${1%=*} |
| 54 | + to=${1#*=} |
| 55 | + # If from part has a nic defined, extract it, else forward from all: |
| 56 | + case "$from" in |
| 57 | + *:*) nic="on ${from%:*}";; |
| 58 | + *) nic=;; |
| 59 | + esac |
| 60 | + # Extract the port to forward from: |
| 61 | + from_port=${from##*:} |
| 62 | + # If to part has an IP defined, extract it, else forward to 127.0.0.1: |
| 63 | + case "$to" in |
| 64 | + *:*) to_ip=${to%:*};; |
| 65 | + *) to_ip=127.0.0.1;; |
| 66 | + esac |
| 67 | + # Extract the port to forward to: |
| 68 | + to_port=${to##*:} |
| 69 | + # Create the packet filter (pf) forwarding rule for both TCP and UDP: |
| 70 | + rule=$( |
| 71 | + printf \ |
| 72 | + 'rdr pass %s inet proto %s from any to any port %s -> %s port %s' \ |
| 73 | + "$nic" '{tcp udp}' "$from_port" "$to_ip" "$to_port" |
| 74 | + ) |
| 75 | + # Add it to the list of rules: |
| 76 | + RULES="$RULES$rule$NEWLINE" |
| 77 | + shift |
| 78 | +done |
| 79 | + |
| 80 | +# Add the rules after the line matching "rdr-anchor" in /etc/pf.conf, print the |
| 81 | +# combined rules to STDOUT and load the rules into pf from STDIN. |
| 82 | +# Finally, display the loaded NAT rules or print the script usage on failure: |
| 83 | +# shellcheck disable=SC2015 |
| 84 | +printf %s "$RULES" | sed -e '/rdr-anchor/r /dev/stdin' /etc/pf.conf | |
| 85 | +sudo pfctl -Ef - 2>/dev/null && print_nat_rules || print_usage_exit |
0 commit comments