#!/bin/bash set -euo pipefail randstr() { dd if=/dev/urandom count=1 bs=3 2>/dev/null | xxd -p -g 0 } default_route() { ip route | awk '/^default/ && (NR==1) {print $5}' } find_next_subnet() { local i for i in {20..254}; do if ip route | grep -q "^172\.$i\."; then true else break fi done echo "172.$i" } nft_rev() { local family local table local chain local protocol local field local port local policy local filter local remove source "$NET_CONF_FILE" family="$3" table="$4" chain="$5" if [[ "$chain" = 'input' ]]; then protocol="$6" field="$7" port="$8" policy="$9" filter=" .nftables[] | select(.rule) | .rule | select(.family|test(\"$family\")) | select(.table|test(\"$table\")) | select(.chain|test(\"$chain\")) | select(.expr[0]?.match?.op) | select(.expr[0]?.match?.left?.payload?.protocol) | select(.expr[0]?.match?.left?.payload?.field) | select(.expr[0]?.match?.right) | select(.expr[1]?.$policy == null) | select(.expr[0].match.op == \"==\") | select(.expr[0].match.left.payload.protocol == \"$protocol\") | select(.expr[0].match.left.payload.field == \"$field\") | select(.expr[0].match.right == $port) | .handle " remove=" echo nft delete rule $table $chain handle \$handle nft delete rule $table $chain handle \$handle " elif [[ "$chain" = 'forward' ]]; then key="$6" ifname="$7" policy="${13}" filter=" .nftables[] | select(.rule) | .rule | select(.family|test(\"$family\")) | select(.table|test(\"$table\")) | select(.chain|test(\"$chain\")) | select(.expr[0]?.match?.op) | select(.expr[0]?.match?.left?.meta?.key) | select(.expr[0]?.match?.right) | select(.expr[2]?.$policy == null) | select(.expr[0].match.op == \"==\") | select(.expr[0].match.left.meta.key == \"$key\") | select(.expr[0].match.right == \"$ifname\") | .handle " remove=" echo nft delete rule $table $chain handle \$handle nft delete rule $table $chain handle \$handle " else echo "Warning: Don't know how to reverse 'nft $*'" 1>&2 nft "$@" return 0 fi set +u if [[ -z "$restore_nft_file" ]]; then restore_nft_file="$(mktemp)" echo "#!/usr/bin/env sh" > "$restore_nft_file" chmod 700 "$restore_nft_file" chown root:root "$restore_nft_file" echo "restore_nft_file=$restore_nft_file" >> "$NET_CONF_FILE" fi set -u echo "handle=\"\$(nft --json list ruleset | jq '$filter')\"" >> "$restore_nft_file" echo "$remove" >> "$restore_nft_file" nft "$@" } get_bridge_name() { for i in {1..255}; do br_name="br$i" link_names="$(ip link list | awk -F:\ '/^[0-9]+: br.*/ {print $2}')" if [[ -z "$link_names" ]] || echo "$link_names" | grep -vq "$br_name"; then echo "$br_name" return 0 fi done return 1 } get_tap_name() { for i in {1..255}; do tap_name="tap$i" link_names="$(ip link list | awk -F:\ '/^[0-9]+: tap.*/ {print $2}')" if [[ -z "$link_names" ]] || echo "$link_names" | grep -vq "$tap_name"; then echo "$tap_name" return 0 fi done return 1 } create() { local next_subnet local bridge_name local dhcp_subnet local dhcp_range local tap_name local dnsmasq_pid next_subnet="$(find_next_subnet)" dhcp_subnet="$next_subnet.0.1/24" dhcp_range="$next_subnet.0.100,$next_subnet.0.200" bridge_name="$(get_bridge_name)" tap_name="$(get_tap_name)" echo "$bridge_name" echo "$tap_name" echo > "$NET_CONF_FILE" set -x modprobe tun tap ip link add "$bridge_name" type bridge && sleep .1 ip tuntap add dev "$tap_name" mode tap && sleep .1 ip link set dev "$tap_name" master "$bridge_name" && sleep .1 ip link set dev "$bridge_name" up && sleep .1 ip link set dev "$tap_name" up && sleep .1 ip addr add "$dhcp_subnet" dev "$bridge_name" && sleep .1 ip link set dev "$bridge_name" up && sleep .1 ip link set dev "$tap_name" up && sleep .1 set +x ip addr dnsmasq -d --interface="$bridge_name" --bind-interface --dhcp-range="$dhcp_range" & dnsmasq_pid="$!" { echo "bridge_name='$bridge_name'" echo "tap_name='$tap_name'" echo "dnsmasq_pid='$dnsmasq_pid'" } >> "$NET_CONF_FILE" disown -h "$dnsmasq_pid" echo "nft_ruleset='$(nft -s list ruleset)'" >> "$NET_CONF_FILE" # dhcp nft_rev add rule ip filter input udp dport 67 accept nft_rev add rule ip filter input tcp dport 67 accept # dns nft_rev add rule ip filter input udp dport 53 accept nft_rev add rule ip filter input tcp dport 53 accept # scream nft_rev add rule ip filter input udp dport 4010 accept nft_rev add rule ip filter input tcp dport 4010 accept # forward bridge nft_rev add rule ip filter forward iifname "$bridge_name" \ counter packets 0 bytes 0 accept nft_rev add rule ip filter forward oifname "$bridge_name" \ counter packets 0 bytes 0 accept nft_rev add rule ip nat postrouting oifname "$(default_route)" \ counter masquerade } delete() { source "$NET_CONF_FILE" kill "$dnsmasq_pid" ip link del "$tap_name" ip link del "$bridge_name" # nft flush ruleset # nft -f - <<< "$nft_ruleset" "$restore_nft_file" rm "$restore_nft_file" rm "$NET_CONF_FILE" } if [[ -z "$NET_CONF_FILE" ]]; then echo Please specify the configuration file path \ with NET_CONF_FILE >&2 exit 1 fi if [[ "$EUID" -ne 0 ]]; then echo "Please run as root" >&2 exit 2 fi "$@"