[package] firewall:
authorJo-Philipp Wich <jow@openwrt.org>
Thu, 30 Jun 2011 01:31:23 +0000 (01:31 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Thu, 30 Jun 2011 01:31:23 +0000 (01:31 +0000)
- allow multiple ports, protocols, macs, icmp types per rule
- implement "limit" and "limit_burst" options for rules
- implement "extra" option to rules and redirects for passing arbritary flags to iptables
- implement negations for "src_port", "dest_port", "src_dport", "src_mac", "proto" and "icmp_type" options
- allow wildcard (*) "src" and "dest" options in rules to allow specifying "any" source or destination
- validate symbolic icmp-type names against the selected iptables binary
- properly handle forwarded ICMPv6 traffic in the default configuration

SVN-Revision: 27317

package/firewall/Makefile
package/firewall/files/firewall.config
package/firewall/files/lib/core_redirect.sh
package/firewall/files/lib/core_rule.sh
package/firewall/files/lib/fw.sh
package/firewall/files/reflection.hotplug

index f8510f1825a6a04b04c1967ea30ed3a7b61acadd..ff62309d3a1fa2b8a01a1b2acd8a6c0406f4c7f1 100644 (file)
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
 PKG_NAME:=firewall
 
 PKG_VERSION:=2
-PKG_RELEASE:=26
+PKG_RELEASE:=27
 
 include $(INCLUDE_DIR)/package.mk
 
index c852f4b0000170662556d4661601450edc29f5d5..c7bc798250850898f218d22619566e0ffa3d4db7 100644 (file)
@@ -8,23 +8,23 @@ config defaults
 
 config zone
        option name             lan
-       option network  'lan'
-       option input    ACCEPT 
-       option output   ACCEPT 
-       option forward  REJECT
+       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 network          'wan'
+       option input            REJECT
+       option output           ACCEPT 
+       option forward          REJECT
        option masq             1 
-       option mtu_fix  1
+       option mtu_fix          1
 
 config forwarding 
-       option src      lan
-       option dest     wan
+       option src              lan
+       option dest             wan
 
 # We need to accept udp packets on port 68,
 # see https://dev.openwrt.org/ticket/4108
@@ -33,14 +33,41 @@ config rule
        option proto            udp
        option dest_port        68
        option target           ACCEPT
-       option family   ipv4
+       option family           ipv4
+
+# Allow IPv4 ping
+config rule
+       option src              wan
+       option proto            icmp
+       option icmp_type        echo-request
+       option family           ipv4
+       option target           ACCEPT
+
+# Allow essential incoming IPv6 ICMP traffic
+config rule                                   
+       option src              wan
+       option dest             *
+       option proto            icmp
+       list icmp_type          router-solicitation
+       list icmp_type          router-advertisement
+       list icmp_type          neighbour-solicitation
+       list icmp_type          neighbour-advertisement
+       list icmp_type          echo-request
+       list icmp_type          destination-unreachable
+       list icmp_type          packet-too-big
+       list icmp_type          time-exceeded
+       option limit            1000/sec
+       option family           ipv6
+       option target           ACCEPT
 
-#Allow ping
+# Drop leaking router advertisements on WAN
 config rule
-       option src wan
-       option proto icmp
-       option icmp_type echo-request
-       option target ACCEPT
+       option src              *
+       option dest             wan
+       option proto            icmp
+       option icmp_type        router-advertisement
+       option family           ipv6
+       option target           DROP
 
 # include a file with users custom iptables rules
 config include
index 64c619e434fe0b8203875ccbb8af2d3c11c4ba46..f511d2915e18d878f2dfd43d17083984eb142cf6 100644 (file)
@@ -13,11 +13,11 @@ fw_config_get_redirect() {
                string src_dport "" \
                string dest "" \
                ipaddr dest_ip "" \
-               string dest_mac "" \
                string dest_port "" \
                string proto "tcpudp" \
                string family "" \
                string target "DNAT" \
+               string extra "" \
        } || return
        [ -n "$redirect_name" ] || redirect_name=$redirect__name
 }
@@ -29,26 +29,27 @@ fw_load_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" ] || {
+               [ -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}${redirect_dest_ip:+_forward}"
+               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_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" ] || {
+               [ -n "${redirect_dest#*}" -a -n "$redirect_src_dip" ] || {
                        fw_log error "SNAT redirect ${redirect_name}: needs dest and src_dip, skipping"
                        return 0
                }
@@ -58,10 +59,11 @@ fw_load_redirect() {
                natopt="--to-source"
                natchain="zone_${redirect_dest}_nat"
                nataddr="$redirect_src_dip"
-               fw_get_port_range natports "$redirect_src_dport" "-"
+               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
@@ -79,34 +81,38 @@ fw_load_redirect() {
 
        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"
        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} \
-               }
-
-               fw add $mode f ${fwdchain:-forward} ACCEPT ^ { $redirect_src_ip $redirect_dest_ip } { \
-                       $srcaddr ${destaddr:--m conntrack --ctstate DNAT} \
-                       ${redirect_proto:+-p $redirect_proto} \
-                       ${srcports:+--sport $srcports} \
-                       ${destports:+--dport $destports} \
-                       ${redirect_src_mac:+-m mac --mac-source $redirect_src_mac} \
-               }
+               fw_get_negation redirect_proto '-p' "$redirect_proto"
+               for redirect_src_mac in ${redirect_src_mac:-""}; do
+                       fw_get_negation redirect_src_mac '--mac-source' "$redirect_src_mac"
+                       fw add $mode n $natchain $redirect_target + \
+                               { $redirect_src_ip $redirect_dest_ip } { \
+                               $srcaddr $srcdaddr $redirect_proto \
+                               $srcports $srcdports \
+                               ${redirect_src_mac:+-m mac $redirect_src_mac} \
+                               $natopt $nataddr${natports:+:$natports} \
+                               $redirect_options \
+                       }
+
+                       [ -n "$destaddr" ] && \
+                       fw add $mode f ${fwdchain:-forward} ACCEPT + \
+                               { $redirect_src_ip $redirect_dest_ip } { \
+                               $srcaddr $destaddr $redirect_proto \
+                               $srcports $destports \
+                               $redirect_src_mac \
+                               $redirect_extra \
+                       }
+               done
        done
 
        fw_callback post redirect
index 8c234a33a13e9e6a9984521be34516492674a314..64a510df924698940de2d433af362e992ae487ae 100644 (file)
@@ -16,24 +16,23 @@ fw_config_get_rule() {
                string proto "tcpudp" \
                string target "" \
                string family "" \
+               string limit "" \
+               string limit_burst "" \
+               string extra "" \
        } || 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" ] || {
+       [ "$rule_target" != "NOTRACK" ] || [ -n "$rule_src" ] || [ "$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}"
@@ -41,8 +40,22 @@ fw_load_rule() {
                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}"
+               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
@@ -54,17 +67,31 @@ fw_load_rule() {
 
        [ "$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} \
-               }
+               fw_get_negation rule_proto '-p' "$rule_proto"
+               for rule_src_port in ${rule_src_port:-""}; do
+                       fw_get_port_range rule_src_port $rule_src_port
+                       fw_get_negation rule_src_port '--sport' "$rule_src_port"
+                       for rule_dest_port in ${rule_dest_port:-""}; do
+                               fw_get_port_range rule_dest_port $rule_dest_port
+                               fw_get_negation rule_dest_port '--dport' "$rule_dest_port"
+                               for rule_src_mac in ${rule_src_mac:-""}; do
+                                       fw_get_negation rule_src_mac '--mac-source' "$rule_src_mac"
+                                       for rule_icmp_type in ${rule_icmp_type:-""}; do
+                                               [ "$rule_proto" = "-p icmp" ] || rule_icmp_type=""
+                                               fw add $mode $table $chain $target + \
+                                                       { $rule_src_ip $rule_dest_ip } { \
+                                                       $src_spec $dest_spec $rule_proto \
+                                                       $rule_src_port $rule_dest_port \
+                                                       ${rule_src_mac:+-m mac $rule_src_mac} \
+                                                       ${rule_icmp_type:+--icmp-type $rule_icmp_type} \
+                                                       ${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
index 896947241a525bdf8d383dd41ce3f34610ef2c6b..647bcd6a547c3bded9a972f0d475b70f7aceb0bd 100644 (file)
@@ -137,10 +137,13 @@ fw__exec() { # <action> <family> <table> <chain> <target> <position> { <rules> }
        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
@@ -159,13 +162,29 @@ fw__exec() { # <action> <family> <table> <chain> <target> <position> { <rules> }
                fi
        fi
 
-       local cmdline="$app --table ${tab} --${cmd} ${chn} ${pol} ${pos} ${tgt:+--jump "$tgt"}"
+       local cmdline="$app --table ${tab} --${cmd} ${chn} ${pol} ${rule_offset:-${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" ;;
+               # special parameter handling
+               case "$1:$2" in
+                       -p:icmp*|--protocol:icmp*)
+                               [ "$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
@@ -175,7 +194,10 @@ fw__exec() { # <action> <family> <table> <chain> <target> <position> { <rules> }
 
        $cmdline
 
-       fw__rc $?
+       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() {
@@ -189,8 +211,8 @@ fw_get_port_range() {
 
        local _first=${_ports%-*}
        local _last=${_ports#*-}
-       if [ "$_first" != "$_last" ]; then
-               export -- "$_var=$_first$_delim$_last"
+       if [ "${_first#!}" != "${_last#!}" ]; then
+               export -- "$_var=$_first$_delim${_last#!}"
        else
                export -- "$_var=$_first"
        fi
@@ -221,11 +243,11 @@ fw_get_family_mode() {
 fw_get_negation() {
        local _var="$1"
        local _flag="$2"
-       local _ipaddr="$3"
+       local _value="$3"
 
-       [ "${_ipaddr#!}" != "$_ipaddr" ] && \
-               export -n -- "$_var=! $_flag ${_ipaddr#!}" || \
-               export -n -- "$_var=${_ipaddr:+$_flag $_ipaddr}"
+       [ "${_value#!}" != "$_value" ] && \
+               export -n -- "$_var=! $_flag ${_value#!}" || \
+               export -n -- "$_var=${_value:+$_flag $_value}"
 }
 
 fw_get_subnet4() {
@@ -245,3 +267,66 @@ fw_get_subnet4() {
                *) export -n -- "$_var=" ;;
        esac
 }
+
+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;
+                               /router-advertisement/d;
+                               /router-solicitation/d;
+                               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
+}
index 33d121cec41e8b6fc8284b6d980e279d53257cb3..4fd8f296de44e4de3e523ebf1e7eec2dfbe714bb 100644 (file)
@@ -56,6 +56,7 @@ if [ "$ACTION" = "add" ] && [ "$INTERFACE" = "wan" ]; then
                [ "$src" = wan ] && [ "$target" = DNAT ] && {
                        local dest
                        config_get dest "$cfg" dest "lan"
+                       [ "$dest" != "*" ] || return
 
                        local net
                        for net in $(find_networks "$dest"); do