diff options
Diffstat (limited to 'package/firewall')
-rw-r--r-- | package/firewall/Makefile | 58 | ||||
-rw-r--r-- | package/firewall/files/bin/fw | 49 | ||||
-rw-r--r-- | package/firewall/files/firewall.config | 176 | ||||
-rw-r--r-- | package/firewall/files/firewall.hotplug | 22 | ||||
-rwxr-xr-x | package/firewall/files/firewall.init | 27 | ||||
-rw-r--r-- | package/firewall/files/firewall.upgrade | 1 | ||||
-rw-r--r-- | package/firewall/files/firewall.user | 4 | ||||
-rw-r--r-- | package/firewall/files/lib/config.sh | 101 | ||||
-rw-r--r-- | package/firewall/files/lib/core.sh | 159 | ||||
-rw-r--r-- | package/firewall/files/lib/core_forwarding.sh | 44 | ||||
-rw-r--r-- | package/firewall/files/lib/core_init.sh | 338 | ||||
-rw-r--r-- | package/firewall/files/lib/core_interface.sh | 205 | ||||
-rw-r--r-- | package/firewall/files/lib/core_redirect.sh | 130 | ||||
-rw-r--r-- | package/firewall/files/lib/core_rule.sh | 110 | ||||
-rw-r--r-- | package/firewall/files/lib/fw.sh | 324 | ||||
-rw-r--r-- | package/firewall/files/lib/uci_firewall.sh | 5 | ||||
-rw-r--r-- | package/firewall/files/reflection.hotplug | 132 |
17 files changed, 1885 insertions, 0 deletions
diff --git a/package/firewall/Makefile b/package/firewall/Makefile new file mode 100644 index 000000000..05f42a62e --- /dev/null +++ b/package/firewall/Makefile @@ -0,0 +1,58 @@ +# +# Copyright (C) 2008-2012 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. + +include $(TOPDIR)/rules.mk + +PKG_NAME:=firewall + +PKG_VERSION:=2 +PKG_RELEASE:=53 + +include $(INCLUDE_DIR)/package.mk + +define Package/firewall + SECTION:=net + CATEGORY:=Base system + URL:=http://openwrt.org/ + TITLE:=OpenWrt firewall + MAINTAINER:=Jo-Philipp Wich <xm@subsignal.org> + DEPENDS:=+iptables +kmod-ipt-conntrack +kmod-ipt-nat + PKGARCH:=all +endef + +define Package/firewall/description + UCI based firewall for OpenWrt +endef + +define Build/Compile + true +endef + +define Package/firewall/conffiles +/etc/config/firewall +/etc/firewall.user +endef + +define Package/firewall/install + $(INSTALL_DIR) $(1)/lib/firewall + $(INSTALL_DATA) ./files/lib/*.sh $(1)/lib/firewall + $(INSTALL_DIR) $(1)/sbin + $(INSTALL_BIN) ./files/bin/fw $(1)/sbin + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_DATA) ./files/firewall.config $(1)/etc/config/firewall + $(INSTALL_DIR) $(1)/etc/init.d/ + $(INSTALL_BIN) ./files/firewall.init $(1)/etc/init.d/firewall + $(INSTALL_DIR) $(1)/etc/hotplug.d/iface + $(INSTALL_DATA) ./files/firewall.hotplug $(1)/etc/hotplug.d/iface/20-firewall + $(INSTALL_DIR) $(1)/etc/hotplug.d/firewall + $(INSTALL_DATA) ./files/reflection.hotplug $(1)/etc/hotplug.d/firewall/10-nat-reflection + $(INSTALL_DIR) $(1)/etc + $(INSTALL_DATA) ./files/firewall.user $(1)/etc + $(INSTALL_DIR) $(1)/lib/upgrade/keep.d + $(INSTALL_DATA) ./files/firewall.upgrade $(1)/lib/upgrade/keep.d/firewall +endef + +$(eval $(call BuildPackage,firewall)) diff --git a/package/firewall/files/bin/fw b/package/firewall/files/bin/fw new file mode 100644 index 000000000..5d20cc83e --- /dev/null +++ b/package/firewall/files/bin/fw @@ -0,0 +1,49 @@ +#!/bin/sh +FW_LIBDIR=/lib/firewall + +. /lib/functions.sh +. ${FW_LIBDIR}/fw.sh + +case "$(type fw)" in + *function) ;; + *) exit 255;; +esac + +usage() { + echo $0 "<command>" "<family>" "<table>" "<chain>" "<target>" "{" "<rules>" "}" + exit 0 +} + +cmd=$1 +shift +case "$cmd" in + --help|help) usage ;; + start|stop|reload|restart) + . ${FW_LIBDIR}/core.sh + fw_$cmd + exit $? + ;; +esac + +fam=$1 +shift +case "$fam" in + ip) + fam=i + if [ $# -gt 2 ]; then + for p in $(seq 2 $(($# - 1))); do + if eval "[ \$$p == '}' ]"; then + fam=I + break + fi + done + fi ;; + ip4) fam=4 ;; + ip6) fam=6 ;; + arp) fam=a ;; + eth) fam=e ;; + -*) exec $0 $cmd ${fam##*-} "$@" ;; +esac + +fw "$cmd" "$fam" "$@" +exit $? diff --git a/package/firewall/files/firewall.config b/package/firewall/files/firewall.config new file mode 100644 index 000000000..a87413904 --- /dev/null +++ b/package/firewall/files/firewall.config @@ -0,0 +1,176 @@ +config defaults + option syn_flood 1 + option input ACCEPT + option output ACCEPT + option forward REJECT +# Uncomment this line to disable ipv6 rules +# option disable_ipv6 1 + +config zone + option name lan + option network 'lan' + option input ACCEPT + option output ACCEPT + option forward REJECT + +config zone + option name wan + option network 'wan' + option input REJECT + option output ACCEPT + option forward REJECT + option masq 1 + option mtu_fix 1 + +config forwarding + option src lan + option dest wan + +# We need to accept udp packets on port 68, +# see https://dev.openwrt.org/ticket/4108 +config rule + option name Allow-DHCP-Renew + option src wan + option proto udp + option dest_port 68 + option target ACCEPT + option family ipv4 + +# Allow IPv4 ping +config rule + option name Allow-Ping + option src wan + option proto icmp + option icmp_type echo-request + option family ipv4 + option target ACCEPT + +# Allow DHCPv6 replies +# see https://dev.openwrt.org/ticket/10381 +config rule + option name Allow-DHCPv6 + option src wan + option proto udp + option src_ip fe80::/10 + option src_port 547 + option dest_ip fe80::/10 + option dest_port 546 + option family ipv6 + option target ACCEPT + +# Allow essential incoming IPv6 ICMP traffic +config rule + option name Allow-ICMPv6-Input + option src wan + option proto icmp + list icmp_type echo-request + list icmp_type echo-reply + list icmp_type destination-unreachable + list icmp_type packet-too-big + list icmp_type time-exceeded + list icmp_type bad-header + list icmp_type unknown-header-type + list icmp_type router-solicitation + list icmp_type neighbour-solicitation + list icmp_type router-advertisement + list icmp_type neighbour-advertisement + option limit 1000/sec + option family ipv6 + option target ACCEPT + +# Allow essential forwarded IPv6 ICMP traffic +config rule + option name Allow-ICMPv6-Forward + option src wan + option dest * + option proto icmp + list icmp_type echo-request + list icmp_type echo-reply + list icmp_type destination-unreachable + list icmp_type packet-too-big + list icmp_type time-exceeded + list icmp_type bad-header + list icmp_type unknown-header-type + option limit 1000/sec + option family ipv6 + option target ACCEPT + +# include a file with users custom iptables rules +config include + option path /etc/firewall.user + + +### EXAMPLE CONFIG SECTIONS +# do not allow a specific ip to access wan +#config rule +# option src lan +# option src_ip 192.168.45.2 +# option dest wan +# option proto tcp +# option target REJECT + +# block a specific mac on wan +#config rule +# option dest wan +# option src_mac 00:11:22:33:44:66 +# option target REJECT + +# block incoming ICMP traffic on a zone +#config rule +# option src lan +# option proto ICMP +# option target DROP + +# port redirect port coming in on wan to lan +#config redirect +# option src wan +# option src_dport 80 +# option dest lan +# option dest_ip 192.168.16.235 +# option dest_port 80 +# option proto tcp + +# port redirect of remapped ssh port (22001) on wan +#config redirect +# option src wan +# option src_dport 22001 +# option dest lan +# option dest_port 22 +# option proto tcp + +# allow IPsec/ESP and ISAKMP passthrough +#config rule +# option src wan +# option dest lan +# option protocol esp +# option target ACCEPT + +#config rule +# option src wan +# option dest lan +# option src_port 500 +# option dest_port 500 +# option proto udp +# option target ACCEPT + +### FULL CONFIG SECTIONS +#config rule +# option src lan +# option src_ip 192.168.45.2 +# option src_mac 00:11:22:33:44:55 +# option src_port 80 +# option dest wan +# option dest_ip 194.25.2.129 +# option dest_port 120 +# option proto tcp +# option target REJECT + +#config redirect +# option src lan +# option src_ip 192.168.45.2 +# option src_mac 00:11:22:33:44:55 +# option src_port 1024 +# option src_dport 80 +# option dest_ip 194.25.2.129 +# option dest_port 120 +# option proto tcp diff --git a/package/firewall/files/firewall.hotplug b/package/firewall/files/firewall.hotplug new file mode 100644 index 000000000..52e779848 --- /dev/null +++ b/package/firewall/files/firewall.hotplug @@ -0,0 +1,22 @@ +#!/bin/sh +# This script is executed as part of the hotplug event with +# HOTPLUG_TYPE=iface, triggered by various scripts when an interface +# is configured (ACTION=ifup) or deconfigured (ACTION=ifdown). The +# interface is available as INTERFACE, the real device as DEVICE. + +[ "$DEVICE" == "lo" ] && exit 0 + +. /lib/functions.sh +. /lib/firewall/core.sh + +fw_init +fw_is_loaded || exit 0 + +case "$ACTION" in + ifup) + fw_configure_interface "$INTERFACE" add "$DEVICE" & + ;; + ifdown) + fw_configure_interface "$INTERFACE" del "$DEVICE" + ;; +esac diff --git a/package/firewall/files/firewall.init b/package/firewall/files/firewall.init new file mode 100755 index 000000000..a2fd0a0e9 --- /dev/null +++ b/package/firewall/files/firewall.init @@ -0,0 +1,27 @@ +#!/bin/sh /etc/rc.common +# Copyright (C) 2008-2010 OpenWrt.org + +START=45 + +FW_LIBDIR=/lib/firewall + +fw() { + . $FW_LIBDIR/core.sh + fw_$1 +} + +start() { + fw start +} + +stop() { + fw stop +} + +restart() { + fw restart +} + +reload() { + fw reload +} diff --git a/package/firewall/files/firewall.upgrade b/package/firewall/files/firewall.upgrade new file mode 100644 index 000000000..64f63eb39 --- /dev/null +++ b/package/firewall/files/firewall.upgrade @@ -0,0 +1 @@ +/etc/firewall.user diff --git a/package/firewall/files/firewall.user b/package/firewall/files/firewall.user new file mode 100644 index 000000000..1ccbd0165 --- /dev/null +++ b/package/firewall/files/firewall.user @@ -0,0 +1,4 @@ +# This file is interpreted as shell script. +# Put your custom iptables rules here, they will +# be executed with each firewall (re-)start. + diff --git a/package/firewall/files/lib/config.sh b/package/firewall/files/lib/config.sh new file mode 100644 index 000000000..8b2399fc8 --- /dev/null +++ b/package/firewall/files/lib/config.sh @@ -0,0 +1,101 @@ +# Copyright (C) 2009-2010 OpenWrt.org +# Copyright (C) 2009 Malte S. Stretz <http://msquadrat.de> +# +# This is a temporary file, I hope to have some of this stuff merged into +# /lib/functions.sh (without the fw_ prefix of course) one day. + +fw_config_append() { # <package> + CONFIG_APPEND=1 config_load "$@" + unset CONFIG_APPEND +} + +fw_config_once() { # <function> <type> + local func=$1 + local type=$2 + shift 2 + + local config=cfg00nil + fw_config__once() { + config=$1 + } + config_foreach fw_config__once "$type" + + $func $config "$@" +} + +fw_config_get_section() { # <config> <prefix> <type> <name> <default> ... + local config=$1 + local prefix=$2 + shift 2 + + [ -n "$config" ] || return 1 + [ -n "$prefix" ] && { + prefix="${prefix}_" + export ${NO_EXPORT:+-n} -- "${prefix}NAME"="${config}" + config_get "${prefix}TYPE" "$config" TYPE + } + + local enabled + config_get_bool enabled "$config" enabled 1 + [ $enabled -eq 1 ] || return 1 + + [ "$1" == '{' ] && shift + while [ $# -ge 3 ]; do + local type=$1 + local name=$2 + local dflt=$3 + shift 3 + # TODO: Move handling of defaults to /lib/functions.sh + # and get replace the case block with the following + # two lines: + # type=${type#string} + # config_get${type:+_${type}} "${prefix}${name}" "$config" "$name" "$dflt" || return + case "$type" in + string) + local tmp + config_get tmp "$config" "$name" || return + [ -z "$tmp" ] && tmp=$dflt + export ${NO_EXPORT:+-n} -- "${prefix}${name}=${tmp}" + continue + ;; + boolean) + type=bool + ;; + esac; + + local cmd=${prefix}config_get_${type} + type $cmd > /dev/null || { + cmd=config_get_${type} + } + type $cmd > /dev/null || { + echo "config type $type (for $name) not supported" >&2 + return 1 + } + $cmd "${prefix}${name}" "$config" "$name" "$dflt" || return + done +} + +config_get_ipaddr() { + local varn=$1 + local conf=$2 + local name=$3 + local dflt=$4 + + local addr + config_get addr "$conf" "$name" || return + [ -n "$addr" ] || addr=$dflt + + local mask=${addr#*/} + [ "$mask" != "$addr" ] || mask= + addr=${addr%/*} + + local vers= + case "$addr" in + *:*) vers=6; mask="${mask:-128}" ;; + *.*) vers=4; mask="${mask:-32}" ;; + esac + + export ${NO_EXPORT:+-n} -- "${varn}=${addr}" + export ${NO_EXPORT:+-n} -- "${varn}_prefixlen=${mask}" + export ${NO_EXPORT:+-n} -- "${varn}_version=${vers}" +} diff --git a/package/firewall/files/lib/core.sh b/package/firewall/files/lib/core.sh new file mode 100644 index 000000000..93d4d2e29 --- /dev/null +++ b/package/firewall/files/lib/core.sh @@ -0,0 +1,159 @@ +# Copyright (C) 2009-2010 OpenWrt.org + +FW_LIBDIR=${FW_LIBDIR:-/lib/firewall} + +. $FW_LIBDIR/fw.sh +include /lib/network + +fw_start() { + fw_init + + FW_DEFAULTS_APPLIED= + + fw_is_loaded && { + echo "firewall already loaded" >&2 + exit 1 + } + + uci_set_state firewall core "" firewall_state + + fw_clear DROP + + fw_callback pre core + + echo "Loading defaults" + fw_config_once fw_load_defaults defaults + + echo "Loading zones" + config_foreach fw_load_zone zone + + echo "Loading forwardings" + config_foreach fw_load_forwarding forwarding + + echo "Loading rules" + config_foreach fw_load_rule rule + + echo "Loading redirects" + config_foreach fw_load_redirect redirect + + echo "Loading includes" + config_foreach fw_load_include include + + [ -z "$FW_NOTRACK_DISABLED" ] && { + echo "Optimizing conntrack" + config_foreach fw_load_notrack_zone zone + } + + echo "Loading interfaces" + config_foreach fw_configure_interface interface add + + fw_callback post core + + uci_set_state firewall core zones "$FW_ZONES" + uci_set_state firewall core loaded 1 +} + +fw_stop() { + fw_init + + fw_callback pre stop + + local z n i + config_get z core zones + for z in $z; do + config_get n core "${z}_networks" + for n in $n; do + config_get i core "${n}_ifname" + [ -n "$i" ] && env -i ACTION=remove ZONE="$z" \ + INTERFACE="$n" DEVICE="$i" /sbin/hotplug-call firewall + done + + config_get i core "${z}_tcpmss" + [ "$i" == 1 ] && { + fw del i m FORWARD zone_${z}_MSSFIX + fw del i m zone_${z}_MSSFIX + } + done + + fw_clear ACCEPT + + fw_callback post stop + + uci_revert_state firewall + config_clear + + local h + for h in $FW_HOOKS; do unset $h; done + + unset FW_HOOKS + unset FW_INITIALIZED +} + +fw_restart() { + fw_stop + fw_start +} + +fw_reload() { + fw_restart +} + +fw_is_loaded() { + local bool=$(uci_get_state firewall.core.loaded) + return $((! ${bool:-0})) +} + + +fw_die() { + echo "Error:" "$@" >&2 + fw_log error "$@" + fw_stop + exit 1 +} + +fw_log() { + local level="$1" + [ -n "$2" ] && shift || level=notice + [ "$level" != error ] || echo "Error: $@" >&2 + logger -t firewall -p user.$level "$@" +} + + +fw_init() { + [ -z "$FW_INITIALIZED" ] || return 0 + + . $FW_LIBDIR/config.sh + + scan_interfaces + fw_config_append firewall + + local hooks="core stop defaults zone notrack synflood" + local file lib hk pp + for file in $FW_LIBDIR/core_*.sh; do + . $file + hk=$(basename $file .sh) + hk=${hk#core_} + append hooks $hk + done + for file in $FW_LIBDIR/*.sh; do + lib=$(basename $file .sh) + lib=${lib##[0-9][0-9]_} + case $lib in + core*|fw|config|uci_firewall) continue ;; + esac + . $file + for hk in $hooks; do + for pp in pre post; do + type ${lib}_${pp}_${hk}_cb >/dev/null && { + append FW_CB_${pp}_${hk} ${lib} + append FW_HOOKS FW_CB_${pp}_${hk} + } + done + done + done + + fw_callback post init + + FW_INITIALIZED=1 + return 0 +} diff --git a/package/firewall/files/lib/core_forwarding.sh b/package/firewall/files/lib/core_forwarding.sh new file mode 100644 index 000000000..c4a968143 --- /dev/null +++ b/package/firewall/files/lib/core_forwarding.sh @@ -0,0 +1,44 @@ +# Copyright (C) 2009-2010 OpenWrt.org + +fw_config_get_forwarding() { + [ "${forwarding_NAME}" != "$1" ] || return + fw_config_get_section "$1" forwarding { \ + string _name "$1" \ + string name "" \ + string src "" \ + string dest "" \ + string family "" \ + } || return + [ -n "$forwarding_name" ] || forwarding_name=$forwarding__name +} + +fw_load_forwarding() { + fw_config_get_forwarding "$1" + + fw_callback pre forwarding + + local chain=forward + [ -n "$forwarding_src" ] && { + chain=zone_${forwarding_src}_forward + } + + local target=ACCEPT + [ -n "$forwarding_dest" ] && { + target=zone_${forwarding_dest}_ACCEPT + } + + local mode + fw_get_family_mode mode ${forwarding_family:-x} ${forwarding_dest:-${forwarding_src:--}} i + + fw add $mode f $chain $target ^ + + # propagate masq zone flag + [ -n "$forwarding_src" ] && list_contains FW_CONNTRACK_ZONES $forwarding_src && { + append FW_CONNTRACK_ZONES $forwarding_dest + } + [ -n "$forwarding_dest" ] && list_contains FW_CONNTRACK_ZONES $forwarding_dest && { + append FW_CONNTRACK_ZONES $forwarding_src + } + + fw_callback post forwarding +} diff --git a/package/firewall/files/lib/core_init.sh b/package/firewall/files/lib/core_init.sh new file mode 100644 index 000000000..035647998 --- /dev/null +++ b/package/firewall/files/lib/core_init.sh @@ -0,0 +1,338 @@ +# Copyright (C) 2009-2011 OpenWrt.org +# Copyright (C) 2008 John Crispin <blogic@openwrt.org> + +FW_INITIALIZED= + +FW_ZONES= +FW_ZONES4= +FW_ZONES6= +FW_CONNTRACK_ZONES= +FW_NOTRACK_DISABLED= + +FW_DEFAULTS_APPLIED= +FW_ADD_CUSTOM_CHAINS= +FW_ACCEPT_REDIRECTS= +FW_ACCEPT_SRC_ROUTE= + +FW_DEFAULT_INPUT_POLICY=REJECT +FW_DEFAULT_OUTPUT_POLICY=REJECT +FW_DEFAULT_FORWARD_POLICY=REJECT + +FW_DISABLE_IPV4=0 +FW_DISABLE_IPV6=0 + + +fw_load_defaults() { + fw_config_get_section "$1" defaults { \ + string input $FW_DEFAULT_INPUT_POLICY \ + string output $FW_DEFAULT_OUTPUT_POLICY \ + string forward $FW_DEFAULT_FORWARD_POLICY \ + boolean drop_invalid 0 \ + boolean syn_flood 0 \ + boolean synflood_protect 0 \ + string synflood_rate 25 \ + string synflood_burst 50 \ + boolean tcp_syncookies 1 \ + boolean tcp_ecn 0 \ + boolean tcp_westwood 0 \ + boolean tcp_window_scaling 1 \ + boolean accept_redirects 0 \ + boolean accept_source_route 0 \ + boolean custom_chains 1 \ + boolean disable_ipv6 0 \ + } || return + [ -n "$FW_DEFAULTS_APPLIED" ] && { + fw_log error "duplicate defaults section detected, skipping" + return 1 + } + FW_DEFAULTS_APPLIED=1 + + FW_DEFAULT_INPUT_POLICY=$defaults_input + FW_DEFAULT_OUTPUT_POLICY=$defaults_output + FW_DEFAULT_FORWARD_POLICY=$defaults_forward + + FW_ADD_CUSTOM_CHAINS=$defaults_custom_chains + + FW_ACCEPT_REDIRECTS=$defaults_accept_redirects + FW_ACCEPT_SRC_ROUTE=$defaults_accept_source_route + + FW_DISABLE_IPV6=$defaults_disable_ipv6 + + fw_callback pre defaults + + # Seems like there are only one sysctl for both IP versions. + for s in syncookies ecn westwood window_scaling; do + eval "sysctl -e -w net.ipv4.tcp_${s}=\$defaults_tcp_${s}" >/dev/null + done + fw_sysctl_interface all + + fw add i f INPUT ACCEPT { -m conntrack --ctstate RELATED,ESTABLISHED } + fw add i f OUTPUT ACCEPT { -m conntrack --ctstate RELATED,ESTABLISHED } + fw add i f FORWARD ACCEPT { -m conntrack --ctstate RELATED,ESTABLISHED } + + [ $defaults_drop_invalid == 1 ] && { + fw add i f INPUT DROP { -m conntrack --ctstate INVALID } + fw add i f OUTPUT DROP { -m conntrack --ctstate INVALID } + fw add i f FORWARD DROP { -m conntrack --ctstate INVALID } + FW_NOTRACK_DISABLED=1 + } + + fw add i f INPUT ACCEPT { -i lo } + fw add i f OUTPUT ACCEPT { -o lo } + + # Compatibility to old 'syn_flood' parameter + [ $defaults_syn_flood == 1 ] && \ + defaults_synflood_protect=1 + + [ "${defaults_synflood_rate%/*}" == "$defaults_synflood_rate" ] && \ + defaults_synflood_rate="$defaults_synflood_rate/second" + + [ $defaults_synflood_protect == 1 ] && { + echo "Loading synflood protection" + fw_callback pre synflood + fw add i f syn_flood + fw add i f syn_flood RETURN { \ + -p tcp --syn \ + -m limit --limit "${defaults_synflood_rate}" --limit-burst "${defaults_synflood_burst}" \ + } + fw add i f syn_flood DROP + fw add i f INPUT syn_flood { -p tcp --syn } + fw_callback post synflood + } + + [ $defaults_custom_chains == 1 ] && { + echo "Adding custom chains" + fw add i f input_rule + fw add i f output_rule + fw add i f forwarding_rule + fw add i n prerouting_rule + fw add i n postrouting_rule + + fw add i f INPUT input_rule + fw add i f OUTPUT output_rule + fw add i f FORWARD forwarding_rule + fw add i n PREROUTING prerouting_rule + fw add i n POSTROUTING postrouting_rule + } + + fw add i f input + fw add i f output + fw add i f forward + + fw add i f INPUT input + fw add i f OUTPUT output + fw add i f FORWARD forward + + fw add i f reject + fw add i f reject REJECT { --reject-with tcp-reset -p tcp } + fw add i f reject REJECT { --reject-with port-unreach } + + fw_set_filter_policy + + fw_callback post defaults +} + + +fw_config_get_zone() { + [ "${zone_NAME}" != "$1" ] || return + fw_config_get_section "$1" zone { \ + string name "$1" \ + string network "" \ + string input "$FW_DEFAULT_INPUT_POLICY" \ + string output "$FW_DEFAULT_OUTPUT_POLICY" \ + string forward "$FW_DEFAULT_FORWARD_POLICY" \ + boolean masq 0 \ + string masq_src "" \ + string masq_dest "" \ + boolean conntrack 0 \ + boolean mtu_fix 0 \ + boolean custom_chains "$FW_ADD_CUSTOM_CHAINS" \ + boolean log 0 \ + string log_limit 10 \ + string family "" \ + } || return + [ -n "$zone_name" ] || zone_name=$zone_NAME + [ -n "$zone_network" ] || zone_network=$zone_name +} + +fw_load_zone() { + fw_config_get_zone "$1" + + list_contains FW_ZONES $zone_name && { + fw_log error "zone ${zone_name}: duplicated zone, skipping" + return 0 + } + append FW_ZONES $zone_name + + fw_callback pre zone + + [ $zone_conntrack = 1 -o $zone_masq = 1 ] && \ + append FW_CONNTRACK_ZONES "$zone_name" + + local mode + case "$zone_family" in + *4) + mode=4 + append FW_ZONES4 $zone_name + uci_set_state firewall core ${zone_name}_ipv4 1 + ;; + *6) + mode=6 + append FW_ZONES6 $zone_name + uci_set_state firewall core ${zone_name}_ipv6 1 + ;; + *) + mode=i + append FW_ZONES4 $zone_name + append FW_ZONES6 $zone_name + uci_set_state firewall core ${zone_name}_ipv4 1 + uci_set_state firewall core ${zone_name}_ipv6 1 + ;; + esac + + local chain=zone_${zone_name} + + fw add $mode f ${chain}_ACCEPT + fw add $mode f ${chain}_DROP + fw add $mode f ${chain}_REJECT + + # TODO: Rename to ${chain}_input + fw add $mode f ${chain} + fw add $mode f ${chain} ${chain}_${zone_input} $ + + fw add $mode f ${chain}_forward + fw add $mode f ${chain}_forward ${chain}_${zone_forward} $ + + # TODO: add ${chain}_output + fw add $mode f output ${chain}_${zone_output} $ + + # TODO: Rename to ${chain}_MASQUERADE + fw add $mode n ${chain}_nat + fw add $mode n ${chain}_prerouting + + fw add $mode r ${chain}_notrack + + [ $zone_mtu_fix == 1 ] && { + fw add $mode m ${chain}_MSSFIX + fw add $mode m FORWARD ${chain}_MSSFIX ^ + uci_set_state firewall core ${zone_name}_tcpmss 1 + } + + [ $zone_custom_chains == 1 ] && { + [ $FW_ADD_CUSTOM_CHAINS == 1 ] || \ + fw_die "zone ${zone_name}: custom_chains globally disabled" + + fw add $mode f input_${zone_name} + fw add $mode f ${chain} input_${zone_name} ^ + + fw add $mode f forwarding_${zone_name} + fw add $mode f ${chain}_forward forwarding_${zone_name} ^ + + fw add $mode n prerouting_${zone_name} + fw add $mode n ${chain}_prerouting prerouting_${zone_name} ^ + } + + [ "$zone_log" == 1 ] && { + [ "${zone_log_limit%/*}" == "$zone_log_limit" ] && \ + zone_log_limit="$zone_log_limit/minute" + + local t + for t in REJECT DROP; do + fw add $mode f ${chain}_${t} LOG ^ \ + { -m limit --limit $zone_log_limit --log-prefix "$t($zone_name): " } + done + + [ $zone_mtu_fix == 1 ] && \ + fw add $mode m ${chain}_MSSFIX LOG ^ \ + { -m limit --limit $zone_log_limit --log-prefix "MSSFIX($zone_name): " } + } + + # NB: if MASQUERADING for IPv6 becomes available we'll need a family check here + if [ "$zone_masq" == 1 ]; then + local msrc mdst + for msrc in ${zone_masq_src:-0.0.0.0/0}; do + case "$msrc" in + *.*) fw_get_negation msrc '-s' "$msrc" ;; + *) fw_get_subnet4 msrc '-s' "$msrc" || break ;; + esac + + for mdst in ${zone_masq_dest:-0.0.0.0/0}; do + case "$mdst" in + *.*) fw_get_negation mdst '-d' "$mdst" ;; + *) fw_get_subnet4 mdst '-d' "$mdst" || break ;; + esac + + fw add $mode n ${chain}_nat MASQUERADE $ { $msrc $mdst } + done + done + fi + + fw_callback post zone +} + +fw_load_notrack_zone() { + fw_config_get_zone "$1" + list_contains FW_CONNTRACK_ZONES "${zone_name}" && return + + fw_callback pre notrack + + fw add i r zone_${zone_name}_notrack NOTRACK $ + + fw_callback post notrack +} + + +fw_load_include() { + local name="$1" + + local path + config_get path ${name} path + + [ -e $path ] && ( + config() { + fw_log error "You cannot use UCI in firewall includes!" >&2 + exit 1 + } + . $path + ) +} + + +fw_clear() { + local policy=$1 + + fw_set_filter_policy $policy + + local tab + for tab in f n r; do + fw del i $tab + done +} + +fw_set_filter_policy() { + local policy=$1 + + local chn tgt + for chn in INPUT OUTPUT FORWARD; do + eval "tgt=\${policy:-\${FW_DEFAULT_${chn}_POLICY}}" + [ $tgt == "REJECT" ] && tgt=reject + [ $tgt == "ACCEPT" -o $tgt == "DROP" ] || { + fw add i f $chn $tgt $ + tgt=DROP + } + fw policy i f $chn $tgt + done +} + + +fw_callback() { + local pp=$1 + local hk=$2 + + local libs lib + eval "libs=\$FW_CB_${pp}_${hk}" + [ -n "$libs" ] || return + for lib in $libs; do + ${lib}_${pp}_${hk}_cb + done +} diff --git a/package/firewall/files/lib/core_interface.sh b/package/firewall/files/lib/core_interface.sh new file mode 100644 index 000000000..3d6718431 --- /dev/null +++ b/package/firewall/files/lib/core_interface.sh @@ -0,0 +1,205 @@ +# Copyright (C) 2009-2012 OpenWrt.org + +fw__uci_state_add() { + local var="$1" + local item="$2" + + local val="$(uci_get_state firewall core $var)" + local e1; for e1 in $item; do + local e2; for e2 in $val; do + [ "$e1" = "$e2" ] && e1="" + done + val="${val:+$val${e1:+ }}$e1" + done + + uci_toggle_state firewall core $var "$val" +} + +fw__uci_state_del() { + local var="$1" + local item="$2" + + local rest="" + local val="$(uci_get_state firewall core $var)" + local e1; for e1 in $val; do + local e2; for e2 in $item; do + [ "$e1" = "$e2" ] && e1="" + done + rest="${rest:+$rest${e1:+ }}$e1" + done + + uci_toggle_state firewall core $var "$rest" +} + +fw_configure_interface() { + local iface=$1 + local action=$2 + local ifname=$3 + local aliasnet=$4 + + [ "$action" == "add" ] && { + local status=$(uci_get_state network "$iface" up 0) + [ "$status" == 1 ] || [ -n "$aliasnet" ] || return 0 + } + + [ -n "$ifname" ] || { + ifname=$(uci_get_state network "$iface" ifname) + ifname="${ifname%%:*}" + [ -z "$ifname" ] && return 0 + } + + [ "$ifname" == "lo" ] && return 0 + + fw_callback pre interface + + fw__do_rules() { + local action=$1 + local zone=$2 + local chain=zone_${zone} + local ifname=$3 + local subnet=$4 + + local inet onet mode + fw_get_family_mode mode x $zone i + + case "$mode/$subnet" in + # Zone supports v6 only or dual, need v6 + G6/*:*|i/*:*) + inet="-s $subnet -d ::/0" + onet="-s ::/0 -d $subnet" + mode=6 + ;; + + # Zone supports v4 only or dual, need v4 + G4/*.*.*.*|i/*.*.*.*) + inet="-s $subnet -d 0.0.0.0/0" + onet="-s 0.0.0.0/0 -d $subnet" + mode=4 + ;; + + # Need v6 while zone is v4 + */*:*) fw_log info "zone $zone does not support IPv6 address family, skipping"; return ;; + + # Need v4 while zone is v6 + */*.*) fw_log info "zone $zone does not support IPv4 address family, skipping"; return ;; + + # Strip prefix + *) mode="${mode#G}" ;; + esac + + lock /var/run/firewall-interface.lock + + fw $action $mode f ${chain}_ACCEPT ACCEPT $ { -o "$ifname" $onet } + fw $action $mode f ${chain}_ACCEPT ACCEPT $ { -i "$ifname" $inet } + fw $action $mode f ${chain}_DROP DROP $ { -o "$ifname" $onet } + fw $action $mode f ${chain}_DROP DROP $ { -i "$ifname" $inet } + fw $action $mode f ${chain}_REJECT reject $ { -o "$ifname" $onet } + fw $action $mode f ${chain}_REJECT reject $ { -i "$ifname" $inet } + + [ "$(uci_get_state firewall core "${zone}_tcpmss")" == 1 ] && \ + fw $action $mode m ${chain}_MSSFIX TCPMSS $ \ + { -o "$ifname" -p tcp --tcp-flags SYN,RST SYN --clamp-mss-to-pmtu $onet } + + fw $action $mode f input ${chain} $ { -i "$ifname" $inet } + fw $action $mode f forward ${chain}_forward $ { -i "$ifname" $inet } + fw $action $mode n PREROUTING ${chain}_prerouting $ { -i "$ifname" $inet } + fw $action $mode r PREROUTING ${chain}_notrack $ { -i "$ifname" $inet } + fw $action $mode n POSTROUTING ${chain}_nat $ { -o "$ifname" $onet } + + lock -u /var/run/firewall-interface.lock + } + + local old_zones old_ifname old_subnets + config_get old_zones core "${iface}_zone" + [ -n "$old_zones" ] && { + config_get old_ifname core "${iface}_ifname" + config_get old_subnets core "${iface}_subnets" + + local z + for z in $old_zones; do + local n + for n in ${old_subnets:-""}; do + fw_log info "removing $iface ($old_ifname${n:+ alias $n}) from zone $z" + fw__do_rules del $z $old_ifname $n + done + + [ -n "$old_subnets" ] || { + fw__uci_state_del "${z}_networks" "$iface" + env -i ACTION=remove ZONE="$z" INTERFACE="$iface" DEVICE="$ifname" /sbin/hotplug-call firewall + } + done + + local old_aliases + config_get old_aliases core "${iface}_aliases" + + local a + for a in $old_aliases; do + fw_configure_interface "$a" del "$old_ifname" + done + + uci_revert_state firewall core "${iface}_zone" + uci_revert_state firewall core "${iface}_ifname" + uci_revert_state firewall core "${iface}_subnets" + uci_revert_state firewall core "${iface}_aliases" + } + + [ "$action" == del ] && return + + [ -z "$aliasnet" ] && { + local aliases + config_get aliases "$iface" aliases + + local a + for a in $aliases; do + local ipaddr netmask ip6addr + config_get ipaddr "$a" ipaddr + config_get netmask "$a" netmask + config_get ip6addr "$a" ip6addr + + [ -n "$ipaddr" ] && fw_configure_interface "$a" add "" "$ipaddr${netmask:+/$netmask}" + [ -n "$ip6addr" ] && fw_configure_interface "$a" add "" "$ip6addr" + done + + fw_sysctl_interface $ifname + fw_callback post interface + + uci_toggle_state firewall core "${iface}_aliases" "$aliases" + } || { + local subnets= + config_get subnets core "${iface}_subnets" + append subnets "$aliasnet" + + config_set core "${iface}_subnets" "$subnets" + uci_toggle_state firewall core "${iface}_subnets" "$subnets" + } + + local new_zones= + load_zone() { + fw_config_get_zone "$1" + list_contains zone_network "$iface" || return + + fw_log info "adding $iface ($ifname${aliasnet:+ alias $aliasnet}) to zone $zone_name" + fw__do_rules add ${zone_name} "$ifname" "$aliasnet" + append new_zones $zone_name + + [ -n "$aliasnet" ] || { + fw__uci_state_add "${zone_name}_networks" "${zone_network}" + env -i ACTION=add ZONE="$zone_name" INTERFACE="$iface" DEVICE="$ifname" /sbin/hotplug-call firewall + } + } + config_foreach load_zone zone + + uci_toggle_state firewall core "${iface}_zone" "$new_zones" + uci_toggle_state firewall core "${iface}_ifname" "$ifname" +} + +fw_sysctl_interface() { + local ifname=$1 + { + sysctl -w net.ipv4.conf.${ifname}.accept_redirects=$FW_ACCEPT_REDIRECTS + sysctl -w net.ipv6.conf.${ifname}.accept_redirects=$FW_ACCEPT_REDIRECTS + sysctl -w net.ipv4.conf.${ifname}.accept_source_route=$FW_ACCEPT_SRC_ROUTE + sysctl -w net.ipv6.conf.${ifname}.accept_source_route=$FW_ACCEPT_SRC_ROUTE + } >/dev/null 2>/dev/null +} + diff --git a/package/firewall/files/lib/core_redirect.sh b/package/firewall/files/lib/core_redirect.sh new file mode 100644 index 000000000..fe396c1c1 --- /dev/null +++ b/package/firewall/files/lib/core_redirect.sh @@ -0,0 +1,130 @@ +# Copyright (C) 2009-2010 OpenWrt.org + +fw_config_get_redirect() { + [ "${redirect_NAME}" != "$1" ] || return + fw_config_get_section "$1" redirect { \ + string _name "$1" \ + string name "" \ + string src "" \ + ipaddr src_ip "" \ + ipaddr src_dip "" \ + string src_mac "" \ + string src_port "" \ + string src_dport "" \ + string dest "" \ + ipaddr dest_ip "" \ + string dest_port "" \ + string proto "tcpudp" \ + string family "" \ + string target "DNAT" \ + string extra "" \ + } || return + [ -n "$redirect_name" ] || redirect_name=$redirect__name +} + +fw_load_redirect() { + fw_config_get_redirect "$1" + + fw_callback pre redirect + + local fwdchain natchain natopt nataddr natports srcdaddr srcdports + if [ "$redirect_target" == "DNAT" ]; then + [ -n "${redirect_src#*}" -a -n "$redirect_dest_ip$redirect_dest_port" ] || { + fw_log error "DNAT redirect ${redirect_name}: needs src and dest_ip or dest_port, skipping" + return 0 + } + + fwdopt="" + fwdchain="" + + # Check whether only ports are given or whether the given dest ip is local, + # in this case match only DNATed traffic and allow it on input, not forward + if [ -z "$redirect_dest_ip" ] || /sbin/ifconfig | grep -qE "addr:${redirect_dest_ip//./\\.}\b"; then + fwdopt="-m conntrack --ctstate DNAT" + fwdchain="zone_${redirect_src}" + else + fwdchain="zone_${redirect_src}_forward" + fi + + natopt="--to-destination" + natchain="zone_${redirect_src}_prerouting" + nataddr="$redirect_dest_ip" + fw_get_port_range natports "${redirect_dest_port#!}" "-" + + fw_get_negation srcdaddr '-d' "${redirect_src_dip:+$redirect_src_dip/$redirect_src_dip_prefixlen}" + fw_get_port_range srcdports "$redirect_src_dport" ":" + fw_get_negation srcdports '--dport' "$srcdports" + + list_contains FW_CONNTRACK_ZONES $redirect_src || \ + append FW_CONNTRACK_ZONES $redirect_src + + elif [ "$redirect_target" == "SNAT" ]; then + [ -n "${redirect_dest#*}" -a -n "$redirect_src_dip" ] || { + fw_log error "SNAT redirect ${redirect_name}: needs dest and src_dip, skipping" + return 0 + } + + fwdchain="${redirect_src:+zone_${redirect_src}_forward}" + + natopt="--to-source" + natchain="zone_${redirect_dest}_nat" + nataddr="$redirect_src_dip" + fw_get_port_range natports "${redirect_src_dport#!}" "-" + + fw_get_negation srcdaddr '-d' "${redirect_dest_ip:+$redirect_dest_ip/$redirect_dest_ip_prefixlen}" + fw_get_port_range srcdports "$redirect_dest_port" ":" + fw_get_negation srcdports '--dport' "$srcdports" + + list_contains FW_CONNTRACK_ZONES $redirect_dest || \ + append FW_CONNTRACK_ZONES $redirect_dest + + else + fw_log error "redirect ${redirect_name}: target must be either DNAT or SNAT, skipping" + return 0 + fi + + local mode + fw_get_family_mode mode ${redirect_family:-x} ${redirect_src:-$redirect_dest} I + + local srcaddr + fw_get_negation srcaddr '-s' "${redirect_src_ip:+$redirect_src_ip/$redirect_src_ip_prefixlen}" + + local srcports + fw_get_port_range srcports "$redirect_src_port" ":" + fw_get_negation srcports '--sport' "$srcports" + + local destaddr + fw_get_negation destaddr '-d' "${redirect_dest_ip:+$redirect_dest_ip/$redirect_dest_ip_prefixlen}" + + local destports + fw_get_port_range destports "${redirect_dest_port:-$redirect_src_dport}" ":" + fw_get_negation destports '--dport' "$destports" + + [ "$redirect_proto" == "tcpudp" ] && redirect_proto="tcp udp" + local pr; for pr in $redirect_proto; do + fw_get_negation pr '-p' "$pr" + local sm; for sm in ${redirect_src_mac:-""}; do + fw_get_negation sm '--mac-source' "$sm" + fw add $mode n $natchain $redirect_target + \ + { $redirect_src_ip $redirect_dest_ip } { \ + $srcaddr $srcdaddr $pr \ + $srcports $srcdports \ + ${sm:+-m mac $sm} \ + $natopt $nataddr${natports:+:$natports} \ + $redirect_options \ + } + + fw add $mode f ${fwdchain:-forward} ACCEPT + \ + { $redirect_src_ip $redirect_dest_ip } { \ + $srcaddr $destaddr \ + $pr \ + $srcports $destports \ + ${sm:+-m mac $sm} \ + $fwdopt \ + $redirect_extra \ + } + done + done + + fw_callback post redirect +} diff --git a/package/firewall/files/lib/core_rule.sh b/package/firewall/files/lib/core_rule.sh new file mode 100644 index 000000000..f49c42af5 --- /dev/null +++ b/package/firewall/files/lib/core_rule.sh @@ -0,0 +1,110 @@ +# Copyright (C) 2009-2010 OpenWrt.org + +fw_config_get_rule() { + [ "${rule_NAME}" != "$1" ] || return + fw_config_get_section "$1" rule { \ + string _name "$1" \ + string name "" \ + string src "" \ + ipaddr src_ip "" \ + string src_mac "" \ + string src_port "" \ + string dest "" \ + ipaddr dest_ip "" \ + string dest_port "" \ + string icmp_type "" \ + string proto "tcpudp" \ + string target "" \ + string family "" \ + string limit "" \ + string limit_burst "" \ + string extra "" \ + } || return + [ -n "$rule_name" ] || rule_name=$rule__name +} + +fw_load_rule() { + fw_config_get_rule "$1" + + [ "$rule_target" != "NOTRACK" ] || [ -n "$rule_src" ] || [ "$rule_src" != "*" ] || { + fw_log error "NOTRACK rule ${rule_name}: needs src, skipping" + return 0 + } + + fw_callback pre rule + + local table=f + local chain=input + local target="${rule_target:-REJECT}" + if [ "$target" == "NOTRACK" ]; then + table=r + chain="zone_${rule_src}_notrack" + else + if [ -n "$rule_src" ]; then + if [ "$rule_src" != "*" ]; then + chain="zone_${rule_src}${rule_dest:+_forward}" + else + chain="${rule_dest:+forward}" + chain="${chain:-input}" + fi + fi + + if [ -n "$rule_dest" ]; then + if [ "$rule_dest" != "*" ]; then + target="zone_${rule_dest}_${target}" + elif [ "$target" = REJECT ]; then + target=reject + fi + fi + fi + + local mode + fw_get_family_mode mode ${rule_family:-x} "$rule_src" I + + local src_spec dest_spec + fw_get_negation src_spec '-s' "${rule_src_ip:+$rule_src_ip/$rule_src_ip_prefixlen}" + fw_get_negation dest_spec '-d' "${rule_dest_ip:+$rule_dest_ip/$rule_dest_ip_prefixlen}" + + [ "$rule_proto" == "tcpudp" ] && rule_proto="tcp udp" + local pr; for pr in $rule_proto; do + local sports dports itypes + case "$pr" in + icmp|icmpv6|1|58) + sports=""; dports="" + itypes="$rule_icmp_type" + ;; + *) + sports="$rule_src_port" + dports="$rule_dest_port" + itypes="" + ;; + esac + + fw_get_negation pr '-p' "$pr" + local sp; for sp in ${sports:-""}; do + fw_get_port_range sp $sp + fw_get_negation sp '--sport' "$sp" + local dp; for dp in ${dports:-""}; do + fw_get_port_range dp $dp + fw_get_negation dp '--dport' "$dp" + local sm; for sm in ${rule_src_mac:-""}; do + fw_get_negation sm '--mac-source' "$sm" + local it; for it in ${itypes:-""}; do + fw_get_negation it '--icmp-type' "$it" + fw add $mode $table $chain $target + \ + { $rule_src_ip $rule_dest_ip } { \ + $src_spec $dest_spec \ + $pr $sp $dp $it \ + ${sm:+-m mac $sm} \ + ${rule_limit:+-m limit --limit $rule_limit \ + ${rule_limit_burst:+--limit-burst $rule_limit_burst}} \ + $rule_extra \ + } + done + done + done + done + done + + fw_callback post rule +} diff --git a/package/firewall/files/lib/fw.sh b/package/firewall/files/lib/fw.sh new file mode 100644 index 000000000..76e294f56 --- /dev/null +++ b/package/firewall/files/lib/fw.sh @@ -0,0 +1,324 @@ +# Copyright (C) 2009-2010 OpenWrt.org +# Copyright (C) 2009 Malte S. Stretz + +export FW_4_ERROR=0 +export FW_6_ERROR=0 +export FW_i_ERROR=0 +export FW_e_ERROR=0 +export FW_a_ERROR=0 + +#TODO: remove this +[ "${-#*x}" == "$-" ] && { + fw() { + fw__exec "$@" + } +} || { + fw() { + local os=$- + set +x + fw__exec "$@" + local rc=$? + set -$os + return $rc + } +} + +fw__exec() { # <action> <family> <table> <chain> <target> <position> { <rules> } + local cmd fam tab chn tgt pos + local i + for i in cmd fam tab chn tgt pos; do + if [ "$1" -a "$1" != '{' ]; then + eval "$i='$1'" + shift + else + eval "$i=-" + fi + done + + fw__rc() { + export FW_${fam#G}_ERROR=$1 + return $1 + } + + fw__dualip() { + fw $cmd 4 $tab $chn $tgt $pos "$@" + fw $cmd 6 $tab $chn $tgt $pos "$@" + fw__rc $((FW_4_ERROR | FW_6_ERROR)) + } + + fw__autoip() { + local ip4 ip6 + shift + while [ "$1" != '}' ]; do + case "$1" in + *:*) ip6=1 ;; + *.*.*.*) ip4=1 ;; + esac + shift + done + shift + if [ "${ip4:-4}" == "${ip6:-6}" ]; then + echo "fw: can't mix ip4 and ip6" >&2 + return 1 + fi + local ver=${ip4:+4}${ip6:+6} + fam=i + fw $cmd ${ver:-i} $tab $chn $tgt $pos "$@" + fw__rc $? + } + + fw__has() { + local tab=${1:-$tab} + if [ $tab == '-' ]; then + type $app > /dev/null 2> /dev/null + fw__rc $(($? & 1)) + return + fi + [ "$app" != ip6tables ] || [ "$tab" != nat ] + fw__rc $? + } + + fw__err() { + local err + eval "err=\$FW_${fam}_ERROR" + fw__rc $err + } + + local app= + local pol= + case "$fam" in + *4) [ $FW_DISABLE_IPV4 == 0 ] && app=iptables || return ;; + *6) [ $FW_DISABLE_IPV6 == 0 ] && app=ip6tables || return ;; + i) fw__dualip "$@"; return ;; + I) fw__autoip "$@"; return ;; + e) app=ebtables ;; + a) app=arptables ;; + -) fw $cmd i $tab $chn $tgt $pos "$@"; return ;; + *) return 254 ;; + esac + case "$tab" in + f) tab=filter ;; + m) tab=mangle ;; + n) tab=nat ;; + r) tab=raw ;; + -) tab=filter ;; + esac + case "$cmd:$chn:$tgt:$pos" in + add:*:-:*) cmd=new-chain ;; + add:*:*:-) cmd=append ;; + add:*:*:$) cmd=append ;; + add:*:*:*) cmd=insert ;; + del:-:*:*) cmd=delete-chain; fw flush $fam $tab ;; + del:*:-:*) cmd=delete-chain; fw flush $fam $tab $chn ;; + del:*:*:*) cmd=delete ;; + flush:*) ;; + policy:*) pol=$tgt; tgt=- ;; + has:*) fw__has; return ;; + err:*) fw__err; return ;; + list:*) cmd="numeric --verbose --$cmd" ;; + *) return 254 ;; + esac + case "$chn" in + -) chn= ;; + esac + case "$tgt" in + -) tgt= ;; + esac + + local rule_offset + case "$pos" in + ^) pos=1 ;; + $) pos= ;; + -) pos= ;; + +) eval "rule_offset=\${FW__RULE_OFS_${app}_${tab}_${chn}:-1}" ;; + esac + + if ! fw__has - family || ! fw__has $tab ; then + export FW_${fam}_ERROR=0 + return 0 + fi + + case "$fam" in + G*) shift; while [ $# -gt 0 ] && [ "$1" != "{" ]; do shift; done ;; + esac + + if [ $# -gt 0 ]; then + shift + if [ $cmd == delete ]; then + pos= + fi + fi + + local cmdline="$app --table ${tab} --${cmd} ${chn} ${pol} ${rule_offset:-${pos}} ${tgt:+--jump "$tgt"}" + while [ $# -gt 1 ]; do + # special parameter handling + case "$1:$2" in + -p:icmp*|-p:1|-p:58|--protocol:icmp*|--protocol:1|--protocol:58) + [ "$app" = ip6tables ] && \ + cmdline="$cmdline -p icmpv6" || \ + cmdline="$cmdline -p icmp" + shift + ;; + --icmp-type:*|--icmpv6-type:*) + local icmp_type + if [ "$app" = ip6tables ] && fw_check_icmptype6 icmp_type "$2"; then + cmdline="$cmdline $icmp_type" + elif [ "$app" = iptables ] && fw_check_icmptype4 icmp_type "$2"; then + cmdline="$cmdline $icmp_type" + else + local fam=IPv4; [ "$app" = ip6tables ] && fam=IPv6 + fw_log info "ICMP type '$2' is not valid for $fam address family, skipping rule" + return 1 + fi + shift + ;; + *) cmdline="$cmdline $1" ;; + esac + shift + done + + [ -n "$FW_TRACE" ] && echo $cmdline >&2 + + $cmdline + + local rv=$? + [ $rv -eq 0 ] && [ -n "$rule_offset" ] && \ + export -- "FW__RULE_OFS_${app}_${tab}_${chn}=$(($rule_offset + 1))" + fw__rc $rv +} + +fw_get_port_range() { + local _var=$1 + local _ports=$2 + local _delim=${3:-:} + if [ "$4" ]; then + fw_get_port_range $_var "${_ports}-${4}" $_delim + return + fi + + local _first=${_ports%-*} + local _last=${_ports#*-} + if [ "${_first#!}" != "${_last#!}" ]; then + export -- "$_var=$_first$_delim${_last#!}" + else + export -- "$_var=$_first" + fi +} + +fw_get_family_mode() { + local _var="$1" + local _hint="$2" + local _zone="$3" + local _mode="$4" + + local _ipv4 _ipv6 + [ "$_zone" != "*" ] && { + [ -n "$FW_ZONES4$FW_ZONES6" ] && { + list_contains FW_ZONES4 "$_zone" && _ipv4=1 || _ipv4=0 + list_contains FW_ZONES6 "$_zone" && _ipv6=1 || _ipv6=0 + } || { + _ipv4=$(uci_get_state firewall core "${_zone}_ipv4" 0) + _ipv6=$(uci_get_state firewall core "${_zone}_ipv6" 0) + } + } || { + _ipv4=1 + _ipv6=1 + } + + case "$_hint:$_ipv4:$_ipv6" in + *4:1:*|*:1:0) export -n -- "$_var=G4" ;; + *6:*:1|*:0:1) export -n -- "$_var=G6" ;; + *) export -n -- "$_var=$_mode" ;; + esac +} + +fw_get_negation() { + local _var="$1" + local _flag="$2" + local _value="$3" + + [ "${_value#!}" != "$_value" ] && \ + export -n -- "$_var=! $_flag ${_value#!}" || \ + export -n -- "$_var=${_value:+$_flag $_value}" +} + +fw_get_subnet4() { + local _var="$1" + local _flag="$2" + local _name="$3" + + local _ipaddr="$(uci_get_state network "${_name#!}" ipaddr)" + local _netmask="$(uci_get_state network "${_name#!}" netmask)" + + case "$_ipaddr" in + *.*.*.*) + [ "${_name#!}" != "$_name" ] && \ + export -n -- "$_var=! $_flag $_ipaddr/${_netmask:-255.255.255.255}" || \ + export -n -- "$_var=$_flag $_ipaddr/${_netmask:-255.255.255.255}" + return 0 + ;; + esac + + export -n -- "$_var=" + return 1 +} + +fw_check_icmptype4() { + local _var="$1" + local _type="$2" + case "$_type" in + ![0-9]*) export -n -- "$_var=! --icmp-type ${_type#!}"; return 0 ;; + [0-9]*) export -n -- "$_var=--icmp-type $_type"; return 0 ;; + esac + + [ -z "$FW_ICMP4_TYPES" ] && \ + export FW_ICMP4_TYPES=$( + iptables -p icmp -h 2>/dev/null | \ + sed -n -e '/^Valid ICMP Types:/ { + n; :r; s/[()]/ /g; s/[[:space:]]\+/\n/g; p; n; b r + }' | sort -u + ) + + local _check + for _check in $FW_ICMP4_TYPES; do + if [ "$_check" = "${_type#!}" ]; then + [ "${_type#!}" != "$_type" ] && \ + export -n -- "$_var=! --icmp-type ${_type#!}" || \ + export -n -- "$_var=--icmp-type $_type" + return 0 + fi + done + + export -n -- "$_var=" + return 1 +} + +fw_check_icmptype6() { + local _var="$1" + local _type="$2" + case "$_type" in + ![0-9]*) export -n -- "$_var=! --icmpv6-type ${_type#!}"; return 0 ;; + [0-9]*) export -n -- "$_var=--icmpv6-type $_type"; return 0 ;; + esac + + [ -z "$FW_ICMP6_TYPES" ] && \ + export FW_ICMP6_TYPES=$( + ip6tables -p icmpv6 -h 2>/dev/null | \ + sed -n -e '/^Valid ICMPv6 Types:/ { + n; :r; s/[()]/ /g; s/[[:space:]]\+/\n/g; p; n; b r + }' | sort -u + ) + + local _check + for _check in $FW_ICMP6_TYPES; do + if [ "$_check" = "${_type#!}" ]; then + [ "${_type#!}" != "$_type" ] && \ + export -n -- "$_var=! --icmpv6-type ${_type#!}" || \ + export -n -- "$_var=--icmpv6-type $_type" + return 0 + fi + done + + export -n -- "$_var=" + return 1 +} diff --git a/package/firewall/files/lib/uci_firewall.sh b/package/firewall/files/lib/uci_firewall.sh new file mode 100644 index 000000000..7c95a7a93 --- /dev/null +++ b/package/firewall/files/lib/uci_firewall.sh @@ -0,0 +1,5 @@ +# This file is here for backwards compatibility and to override the +# uci_firewall.sh from an earlier version. +type fw_is_loaded >/dev/null || { + . /lib/firewall/core.sh +} diff --git a/package/firewall/files/reflection.hotplug b/package/firewall/files/reflection.hotplug new file mode 100644 index 000000000..843c615bc --- /dev/null +++ b/package/firewall/files/reflection.hotplug @@ -0,0 +1,132 @@ +#!/bin/sh + +. /lib/functions.sh +. /lib/functions/network.sh + +if [ "$ACTION" = "add" ] && [ "$INTERFACE" = "wan" ]; then + local wanip + network_get_ipaddr wanip wan || return + + iptables -t nat -F nat_reflection_in 2>/dev/null || { + iptables -t nat -N nat_reflection_in + iptables -t nat -A prerouting_rule -j nat_reflection_in + } + + iptables -t nat -F nat_reflection_out 2>/dev/null || { + iptables -t nat -N nat_reflection_out + iptables -t nat -A postrouting_rule -j nat_reflection_out + } + + iptables -t filter -F nat_reflection_fwd 2>/dev/null || { + iptables -t filter -N nat_reflection_fwd + iptables -t filter -A forwarding_rule -j nat_reflection_fwd + } + + find_networks() { + find_networks_cb() { + local cfg="$1" + local zone="$2" + + local name + config_get name "$cfg" name + + [ "$name" = "$zone" ] && { + local network + config_get network "$cfg" network + + echo ${network:-$zone} + return 1 + } + } + + config_foreach find_networks_cb zone "$1" + } + + setup_fwd() { + local cfg="$1" + + local reflection + config_get_bool reflection "$cfg" reflection 1 + [ "$reflection" == 1 ] || return + + local src + config_get src "$cfg" src + + local target + config_get target "$cfg" target DNAT + + [ "$src" = wan ] && [ "$target" = DNAT ] && { + local dest + config_get dest "$cfg" dest "lan" + [ "$dest" != "*" ] || return + + local net + for net in $(find_networks "$dest"); do + local lannet + network_get_subnet lannet "$net" || return + + local proto + config_get proto "$cfg" proto + + local epmin epmax extport + config_get extport "$cfg" src_dport "1-65535" + [ -n "$extport" ] || return + + epmin="${extport%[-:]*}"; epmax="${extport#*[-:]}" + [ "${epmin#!}" != "$epmax" ] || epmax="" + + local ipmin ipmax intport + config_get intport "$cfg" dest_port "$extport" + + ipmin="${intport%[-:]*}"; ipmax="${intport#*[-:]}" + [ "${ipmin#!}" != "$ipmax" ] || ipmax="" + + local exthost + config_get exthost "$cfg" src_dip "$wanip" + + local inthost + config_get inthost "$cfg" dest_ip + [ -n "$inthost" ] || return + + [ "$proto" = all ] && proto="tcp udp" + [ "$proto" = tcpudp ] && proto="tcp udp" + + [ "${inthost#!}" = "$inthost" ] || return 0 + [ "${exthost#!}" = "$exthost" ] || return 0 + + [ "${epmin#!}" != "$epmin" ] && \ + extport="! --dport ${epmin#!}${epmax:+:$epmax}" || \ + extport="--dport $epmin${epmax:+:$epmax}" + + [ "${ipmin#!}" != "$ipmin" ] && \ + intport="! --dport ${ipmin#!}${ipmax:+:$ipmax}" || \ + intport="--dport $ipmin${ipmax:+:$ipmax}" + + local p + for p in ${proto:-tcp udp}; do + case "$p" in + tcp|udp|6|17) + iptables -t nat -A nat_reflection_in \ + -s $lannet -d $exthost \ + -p $p $extport \ + -j DNAT --to $inthost:${ipmin#!}${ipmax:+-$ipmax} + + iptables -t nat -A nat_reflection_out \ + -s $lannet -d $inthost \ + -p $p $intport \ + -j SNAT --to-source ${lannet%%/*} + + iptables -t filter -A nat_reflection_fwd \ + -s $lannet -d $inthost \ + -p $p $intport \ + -j ACCEPT + ;; + esac + done + done + } + } + + config_load firewall + config_foreach setup_fwd redirect +fi |