Skip to content

Instantly share code, notes, and snippets.

@riyad
Last active June 27, 2024 07:56
Show Gist options
  • Save riyad/390694af9d8bf8ef150428594467fd9a to your computer and use it in GitHub Desktop.
Save riyad/390694af9d8bf8ef150428594467fd9a to your computer and use it in GitHub Desktop.
OpenWRT firewall script to configure network prefix translation for IPv6
#!/bin/sh
#
# Author: Riyad Preukschas <riyad@informatik.uni-bremen.de>
# License: Mozilla Public License 2.0
# SPDX-License-Identifier: MPL-2.0
#
# OpenWRT firewall script for configuring NPTv6 (network prefix translation).
#
# # Installation
#
# - Install the `iptables-mod-nat-extra` package
# - Save this script to /etc/firewall.npt6 on your OpenWRT device.
# - Run the following commands to
# - to add this script to the backup archive
# - instruct the firewall to run this script when it reloads
# - restart the firewall to pick up the new configuration
#
# ```
# cat << "EOF" >> /etc/sysupgrade.conf
# /etc/firewall.npt6
# EOF
# uci -q delete firewall.npt6
# uci set firewall.npt6="include"
# uci set firewall.npt6.path="/etc/firewall.npt6"
# uci set firewall.npt6.reload="1"
# uci commit firewall
# /etc/init.d/firewall restart
# ```
#
# # Configuration
#
# You can configure NPT mappings by adding config sections to /etc/config/firewall:
#
# ```
# config npt6
# option src_interface 'lan' # interface name as found in /etc/config/network or the "Network > Interfaces" page of LuCI.
# option dest_interface 'wan6_wg' # see above
# option output '1' # map outgoing connections (src -> dest). (default '1')
# # This should be enough to connect to devices in the destination network.
# option input '0' # (optional) map incoming connections (dest -> src). (default '0')
# # WARNING: You should have firewall rules denying unsolicited
# # connections especially when `dest_interface` connects to the Internet.
# # e.g. OpenWRT's "WAN" firewall zone should deny these
# # connections by default (i.e. Input: "reject").
# # You should probably use specific "Port Forwards"
# # or "Traffic Rules" to poke holes into your firewall.
# ```
#
# You can add multiple sections if you want to use NTP between multiple source and destination networks.
#
# Run `/etc/init.d/firewall reload` to apply new configurations.
#
. /lib/functions.sh
. /lib/functions/network.sh
set -e -o pipefail
DEBUG=${DEBUG:-0}
log() {
logger -t npt6 -s "${@}"
}
debug() {
if [ $DEBUG -ne 0 ]; then
log "DEBUG: ${@}"
fi
}
ip6t() {
ip6tables "${@}"
}
ip6t_add() {
if ! ip6t -C "${@}" &> /dev/null; then
ip6t -I "${@}"
fi
}
nat6_init() {
log "Resetting NETMAP rules"
# NOTE: the original script used iptables-save here
ip6tables-save -t nat \
| sed -e "/\sNETMAP\s/d" \
| ip6tables-restore -T nat
}
npt6_forwarding_output() {
local src_network="${1}"
local dest_network="${2}"
}
handle_npt6() {
# ${config_id} contains the ID of the current section
local config_id="${1}"
local src_network
config_get src_network "${config_id}" src_interface
local dest_network
config_get dest_network "${config_id}" dest_interface
# Enable forwarding via NPT6
local output
config_get_bool output "${config_id}" output 1
local input
config_get_bool input "${config_id}" input 0
log "Firewall config=\"${config_id}\" src_interface=\"${src_network}\" dest_interface=\"${dest_network}\" output=\"${output}\" input=\"${input}\"."
if [ "${output}" -eq 0 -a "${input}" -eq 0 ]; then
return 0
fi
if [ "$(uci get network.${src_network}.ip6class 2>/dev/null)" != "local" ]; then
log "Warning: network '${src_network}' option ip6class should be 'local'!"
fi
# FIXME: add warning requiring dhcp.*.ra_default='2'?, ...ra='server'?, ...dhcpv6 'server'?
local dest_device
if ! network_get_device dest_device "${dest_network}"; then
debug "... failed. Destination network '${dest_network}' has no device."
return 0
fi
local done_net_dev
for done_net_dev in ${DONE_NETWORK_DEVICES_O}; do
if [ "${done_net_dev}" = "${dest_device}" ]; then
log " ... skipping: already configured device '${dest_device}', so leaving as is."
return 0
fi
done
log "Configuring NPTv6 for '${src_network}' -> '${dest_network}' via '${dest_device}' ..."
local dest_subnet
if ! network_get_subnet6 dest_subnet ${dest_device}; then
log " ... skipping: Destination network '${dest_network}' has no IPv6 subnet."
return 0
fi
debug "Using '${dest_device}' subnet: ${dest_subnet}"
local src_device
network_get_device src_device "${src_network}" || return 0
local src_subnet
network_get_subnet6 src_subnet ${src_network} || return 0
debug "Using '${src_network}' subnet: ${src_subnet}"
# `-r` prevents error: "-e expression #1, char 20: invalid reference \1 on `s' command's RHS"
# `.*` is needed to match (and replace) the whole line
local src_prefix="$(echo "${src_subnet}" | sed -nr 's/.*(\/\d+)$/\1/p')"
local dest_prefix="$(echo "${dest_subnet}" | sed -nr 's/.*(\/\d+)$/\1/p')"
if [ "${src_prefix}" != "${dest_prefix}" ]; then
log " ... ignoring: subnets for '${src_network}' and '${dest_network}' don't have the same IPv6 prefix length."
return 0
fi
if [ "${output}" -eq 1 ]; then
log "Mapping output from ${src_subnet} (${src_network}) to $dest_subnet ($dest_device)"
ip6t_add POSTROUTING -t nat -o "${dest_device}" \
-s "${src_subnet}" -j NETMAP --to "${dest_subnet}" \
-m comment --comment "!fw3: NPTv6 output: ${src_network} -> ${dest_device}"
fi
if [ "${input}" -eq 1 ]; then
log "Mapping input from $dest_subnet ($dest_device) to ${src_subnet} (${src_network})"
ip6t_add PREROUTING -t nat -i "${dest_device}" \
-d "${dest_subnet}" -j NETMAP --to "${src_subnet}" \
-m comment --comment "!fw3: NPTv6 input: ${dest_device} -> ${src_network}"
fi
log "Configuring NPTv6 for '${src_network}' -> '${dest_network}' via '${dest_device}' ... done"
}
main() {
nat6_init
config_load firewall
CONFIG_APPEND=1 config_load network
config_foreach handle_npt6 npt6
}
main "${@}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment