223 lines
6 KiB
Bash
Executable file
223 lines
6 KiB
Bash
Executable file
#!/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
|
|
|
|
"$@"
|