[package] firewall: don't filter IPv4 ICMP types (#10928)
[openwrt/svn-archive/archive.git] / package / firewall / files / lib / fw.sh
index 819aa48eae87f8efdc4100fc2aebe8714554f2eb..76e294f5683ce226304cf376dad852a16aacbaf1 100644 (file)
@@ -74,21 +74,8 @@ fw__exec() { # <action> <family> <table> <chain> <target> <position> { <rules> }
                        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
+               [ "$app" != ip6tables ] || [ "$tab" != nat ]
+               fw__rc $?
        }
 
        fw__err() {
@@ -137,10 +124,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
@@ -149,7 +139,7 @@ fw__exec() { # <action> <family> <table> <chain> <target> <position> { <rules> }
        fi
 
        case "$fam" in
-               G*) shift; while [ "$1" != "{" ]; do shift; done ;;
+               G*) shift; while [ $# -gt 0 ] && [ "$1" != "{" ]; do shift; done ;;
        esac
 
        if [ $# -gt 0 ]; then
@@ -159,56 +149,176 @@ fw__exec() { # <action> <family> <table> <chain> <target> <position> { <rules> }
                fi
        fi
 
+       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) echo -n "--icmpv6-type" ;;
-                       ip6tables:icmp|ip6tables:ICMP) echo -n "icmpv6" ;;
-                       iptables:--icmpv6-type) echo -n "--icmp-type" ;;
-                       iptables:icmpv6) echo -n "icmp" ;;
-                       *) echo -n "$1" ;;
+               # 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
-               echo -ne "\0"
                shift
-       done | xargs -0 ${FW_TRACE:+-t} \
-               $app --table ${tab} --${cmd} ${chn} ${pol} ${pos} ${tgt:+--jump "$tgt"}
-       fw__rc $?
+       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 ports=$1
-       local delim=${2:-:}
-       if [ "$3" ]; then
-               fw_get_port_range "${ports}-${3}" $delim
+       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
-               echo "$first$delim$last"
+       local _first=${_ports%-*}
+       local _last=${_ports#*-}
+       if [ "${_first#!}" != "${_last#!}" ]; then
+               export -- "$_var=$_first$_delim${_last#!}"
        else
-               echo "$first"
+               export -- "$_var=$_first"
        fi
 }
 
 fw_get_family_mode() {
-       local hint="$1"
-       local zone="$2"
-       local mode="$3"
-
-       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
+       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=$(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) echo G4 ;;
-               *6:*:1|*:0:1) echo G6 ;;
-               *) echo $mode ;;
+       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
+}