[backfire] merge dual stack firewall
authorJo-Philipp Wich <jow@openwrt.org>
Thu, 3 Feb 2011 22:02:59 +0000 (22:02 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Thu, 3 Feb 2011 22:02:59 +0000 (22:02 +0000)
SVN-Revision: 25353

16 files changed:
package/firewall/Makefile [new file with mode: 0644]
package/firewall/files/bin/fw [new file with mode: 0644]
package/firewall/files/firewall.config [new file with mode: 0644]
package/firewall/files/firewall.hotplug [new file with mode: 0644]
package/firewall/files/firewall.init [new file with mode: 0755]
package/firewall/files/firewall.user [new file with mode: 0644]
package/firewall/files/lib/config.sh [new file with mode: 0644]
package/firewall/files/lib/core.sh [new file with mode: 0644]
package/firewall/files/lib/core_forwarding.sh [new file with mode: 0644]
package/firewall/files/lib/core_init.sh [new file with mode: 0644]
package/firewall/files/lib/core_interface.sh [new file with mode: 0644]
package/firewall/files/lib/core_redirect.sh [new file with mode: 0644]
package/firewall/files/lib/core_rule.sh [new file with mode: 0644]
package/firewall/files/lib/fw.sh [new file with mode: 0644]
package/firewall/files/lib/uci_firewall.sh [new file with mode: 0644]
package/firewall/files/reflection.hotplug [new file with mode: 0644]

diff --git a/package/firewall/Makefile b/package/firewall/Makefile
new file mode 100644 (file)
index 0000000..4a8a5d3
--- /dev/null
@@ -0,0 +1,56 @@
+#
+# Copyright (C) 2008-2010 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:=21
+
+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 +iptables-mod-conntrack +iptables-mod-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
+endef
+
+$(eval $(call BuildPackage,firewall))
diff --git a/package/firewall/files/bin/fw b/package/firewall/files/bin/fw
new file mode 100644 (file)
index 0000000..0f83b8e
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+FW_LIBDIR=/lib/firewall
+
+. /etc/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 (file)
index 0000000..7904ced
--- /dev/null
@@ -0,0 +1,99 @@
+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 input    ACCEPT 
+       option output   ACCEPT 
+       option forward  REJECT
+
+config zone
+       option name             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 src              wan
+       option proto            udp
+       option dest_port        68
+       option target           ACCEPT
+       option family   ipv4
+
+#Allow ping
+config rule
+       option src wan
+       option proto icmp
+       option icmp_type echo-request
+       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
+
+
+### 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 (file)
index 0000000..720b34c
--- /dev/null
@@ -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
+
+. /etc/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 (executable)
index 0000000..a2fd0a0
--- /dev/null
@@ -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.user b/package/firewall/files/firewall.user
new file mode 100644 (file)
index 0000000..1ccbd01
--- /dev/null
@@ -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 (file)
index 0000000..996cef8
--- /dev/null
@@ -0,0 +1,97 @@
+# 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
+       }
+       
+       [ "$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 (file)
index 0000000..c383597
--- /dev/null
@@ -0,0 +1,153 @@
+# 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 redirects"
+       config_foreach fw_load_redirect redirect
+
+       echo "Loading rules"
+       config_foreach fw_load_rule rule
+
+       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
+       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 (file)
index 0000000..c4a9681
--- /dev/null
@@ -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 (file)
index 0000000..c7e41e7
--- /dev/null
@@ -0,0 +1,316 @@
+# Copyright (C) 2009-2010 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
+
+       [ $defaults_drop_invalid == 1 ] && {
+               fw add i f INPUT   DROP { -m state --state INVALID }
+               fw add i f OUTPUT  DROP { -m state --state INVALID }
+               fw add i f FORWARD DROP { -m state --state INVALID }
+               FW_NOTRACK_DISABLED=1
+       }
+
+       fw add i f INPUT   ACCEPT { -m state --state RELATED,ESTABLISHED }
+       fw add i f OUTPUT  ACCEPT { -m state --state RELATED,ESTABLISHED }
+       fw add i f FORWARD ACCEPT { -m state --state RELATED,ESTABLISHED }
+
+       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
+       fw add $mode f ${chain}_MSSFIX
+
+       # 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 f FORWARD ${chain}_MSSFIX ^
+
+       [ $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 MSSFIX; do
+                       fw add $mode f ${chain}_${t} LOG ^ \
+                               { -m limit --limit $zone_log_limit --log-prefix "$t($zone_name): "  }
+               done
+       }
+
+       # 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
+                       fw_get_negation msrc '-s' "$msrc"
+                       for mdst in ${zone_masq_dest:-0.0.0.0/0}; do
+                               fw_get_negation mdst '-d' "$mdst"
+                               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 ] && . $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 (file)
index 0000000..f089759
--- /dev/null
@@ -0,0 +1,188 @@
+# Copyright (C) 2009-2010 OpenWrt.org
+
+fw__uci_state_add() {
+       local var="$1"
+       local item="$2"
+
+       local val="$(uci_get_state firewall core $var)"
+       uci_set_state firewall core $var "${val:+$val }$item"
+}
+
+fw__uci_state_del() {
+       local var="$1"
+       local item="$2"
+
+       local val=" $(uci_get_state firewall core $var) "
+       val="${val// $item / }"
+       val="${val# }"
+       val="${val% }"
+       uci_set_state firewall core $var "$val"
+}
+
+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 ;;
+               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 }
+
+               fw $action $mode f ${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_set_state firewall core "${iface}_aliases" "$aliases"
+       } || {
+               local subnets=
+               config_get subnets core "${iface}_subnets"
+               append subnets "$aliasnet"
+
+               config_set core "${iface}_subnets" "$subnets"
+               uci_set_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_set_state firewall core "${iface}_zone" "$new_zones"
+       uci_set_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 (file)
index 0000000..87941a2
--- /dev/null
@@ -0,0 +1,114 @@
+# 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_mac "" \
+               string dest_port "" \
+               string proto "tcpudp" \
+               string family "" \
+               string target "DNAT" \
+       } || 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
+               }
+
+               fwdchain="zone_${redirect_src}_forward"
+
+               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" ":"
+
+               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" ":"
+
+               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" ":"
+
+       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}" ":"
+
+       [ "$redirect_proto" == "tcpudp" ] && redirect_proto="tcp udp"
+       for redirect_proto in $redirect_proto; do
+               local pos
+               eval 'pos=$((++FW__REDIR_COUNT_'${mode#G}'_'$natchain'))'
+
+               fw add $mode n $natchain $redirect_target $pos { $redirect_src_ip $redirect_dest_ip } { \
+                       $srcaddr $srcdaddr \
+                       ${redirect_proto:+-p $redirect_proto} \
+                       ${srcports:+--sport $srcports} \
+                       ${srcdports:+--dport $srcdports} \
+                       ${redirect_src_mac:+-m mac --mac-source $redirect_src_mac} \
+                       $natopt $nataddr${natports:+:$natports} \
+               }
+
+               [ -n "$destaddr" ] && \
+               fw add $mode f ${fwdchain:-forward} ACCEPT ^ { $redirect_src_ip $redirect_dest_ip } { \
+                       $srcaddr $destaddr \
+                       ${redirect_proto:+-p $redirect_proto} \
+                       ${srcports:+--sport $srcports} \
+                       ${destports:+--dport $destports} \
+                       ${redirect_src_mac:+-m mac --mac-source $redirect_src_mac} \
+               }
+       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 (file)
index 0000000..8c234a3
--- /dev/null
@@ -0,0 +1,71 @@
+# 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 "" \
+       } || return
+       [ -n "$rule_name" ] || rule_name=$rule__name
+       [ "$rule_proto" == "icmp" ] || rule_icmp_type=
+}
+
+fw_load_rule() {
+       fw_config_get_rule "$1"
+
+       [ "$rule_target" != "NOTRACK" ] || [ -n "$rule_src" ] || {
+               fw_log error "NOTRACK rule ${rule_name}: needs src, skipping"
+               return 0
+       }
+
+       fw_callback pre rule
+
+       fw_get_port_range rule_src_port $rule_src_port
+       fw_get_port_range rule_dest_port $rule_dest_port
+
+       local table=f
+       local chain=input
+       local target="${rule_target:-REJECT}"
+       if [ "$target" == "NOTRACK" ]; then
+               table=r
+               chain="zone_${rule_src}_notrack"
+       else
+               [ -n "$rule_src" ] && chain="zone_${rule_src}${rule_dest:+_forward}"
+               [ -n "$rule_dest" ] && target="zone_${rule_dest}_${target}"
+       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"
+       for rule_proto in $rule_proto; do
+               local rule_pos
+               eval 'rule_pos=$((++FW__RULE_COUNT_'${mode#G}'_'$chain'))'
+
+               fw add $mode $table $chain $target $rule_pos { $rule_src_ip $rule_dest_ip } { \
+                       $src_spec $dest_spec \
+                       ${rule_proto:+-p $rule_proto} \
+                       ${rule_src_port:+--sport $rule_src_port} \
+                       ${rule_src_mac:+-m mac --mac-source $rule_src_mac} \
+                       ${rule_dest_port:+--dport $rule_dest_port} \
+                       ${rule_icmp_type:+--icmp-type $rule_icmp_type} \
+               }
+       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 (file)
index 0000000..16a39b6
--- /dev/null
@@ -0,0 +1,229 @@
+# 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
+               local mod
+               eval "mod=\$FW_${fam#G}_${tab}"
+               if [ "$mod" ]; then
+                       fw__rc $mod
+                       return
+               fi
+               case "$fam" in
+                       *4) mod=iptable_${tab} ;;
+                       *6) mod=ip6table_${tab} ;;
+                       *) mod=. ;;
+               esac
+               grep -q "^${mod} " /proc/modules
+               mod=$?
+               export FW_${fam}_${tab}=$mod
+               fw__rc $mod
+       }
+
+       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
+       case "$pos" in
+               ^) pos=1 ;;
+               $) pos= ;;
+               -) pos= ;;
+       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} ${pos} ${tgt:+--jump "$tgt"}"
+       while [ $# -gt 1 ]; do
+               case "$app:$1" in
+                       ip6tables:--icmp-type) cmdline="$cmdline --icmpv6-type" ;;
+                       ip6tables:icmp|ip6tables:ICMP) cmdline="$cmdline icmpv6" ;;
+                       iptables:--icmpv6-type) cmdline="$cmdline --icmp-type" ;;
+                       iptables:icmpv6) cmdline="$cmdline icmp" ;;
+                       *) cmdline="$cmdline $1" ;;
+               esac
+               shift
+       done
+
+       [ -n "$FW_TRACE" ] && echo $cmdline >&2
+
+       $cmdline
+
+       fw__rc $?
+}
+
+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
+       [ -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)
+       }
+
+       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 _ipaddr="$3"
+
+       [ "${_ipaddr#!}" != "$_ipaddr" ] && \
+               export -n -- "$_var=! $_flag ${_ipaddr#!}" || \
+               export -n -- "$_var=${_ipaddr:+$_flag $_ipaddr}"
+}
diff --git a/package/firewall/files/lib/uci_firewall.sh b/package/firewall/files/lib/uci_firewall.sh
new file mode 100644 (file)
index 0000000..7c95a7a
--- /dev/null
@@ -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 (file)
index 0000000..33d121c
--- /dev/null
@@ -0,0 +1,120 @@
+#!/bin/sh
+
+. /etc/functions.sh
+
+if [ "$ACTION" = "add" ] && [ "$INTERFACE" = "wan" ]; then
+       local wanip=$(uci -P/var/state get network.wan.ipaddr)
+
+       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"
+
+                       local net
+                       for net in $(find_networks "$dest"); do
+                               local lanip=$(uci -P/var/state get network.$net.ipaddr)
+                               local lanmk=$(uci -P/var/state get network.$net.netmask)
+
+                               local proto
+                               config_get proto "$cfg" proto
+
+                               local epmin epmax extport
+                               config_get extport "$cfg" src_dport
+                               [ -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" = tcpudp ] && proto="tcp udp"
+
+                               [ "${inthost#!}" = "$inthost" ] || return 0
+                               [ "${exthost#!}" = "$exthost" ] || return 0
+
+                               local p
+                               for p in ${proto:-tcp udp}; do
+                                       case "$p" in
+                                               tcp|udp)
+                                                       iptables -t nat -A nat_reflection_in \
+                                                               -s $lanip/$lanmk -d $exthost \
+                                                               -p $p --dport $epmin${epmax:+:$epmax} \
+                                                               -j DNAT --to $inthost:$ipmin${ipmax:+-$ipmax}
+
+                                                       iptables -t nat -A nat_reflection_out \
+                                                               -s $lanip/$lanmk -d $inthost \
+                                                               -p $p --dport $ipmin${ipmax:+:$ipmax} \
+                                                               -j SNAT --to-source $lanip
+
+                                                       iptables -t filter -A nat_reflection_fwd \
+                                                               -s $lanip/$lanmk -d $inthost \
+                                                               -p $p --dport $ipmin${ipmax:+:$ipmax} \
+                                                               -j ACCEPT
+                                               ;;
+                                       esac
+                               done
+                       done
+               }
+       }
+
+       config_load firewall
+       config_foreach setup_fwd redirect
+fi