From 6abd1605ce99accc13ff68b19a31ed23fb3fa4a6 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Thu, 30 Jun 2011 01:31:23 +0000 Subject: [PATCH] [package] firewall: - 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 | 2 +- package/firewall/files/firewall.config | 61 ++++++++--- package/firewall/files/lib/core_redirect.sh | 56 +++++----- package/firewall/files/lib/core_rule.sh | 63 +++++++---- package/firewall/files/lib/fw.sh | 111 +++++++++++++++++--- package/firewall/files/reflection.hotplug | 1 + 6 files changed, 220 insertions(+), 74 deletions(-) diff --git a/package/firewall/Makefile b/package/firewall/Makefile index f8510f1825..ff62309d3a 100644 --- a/package/firewall/Makefile +++ b/package/firewall/Makefile @@ -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 diff --git a/package/firewall/files/firewall.config b/package/firewall/files/firewall.config index c852f4b000..c7bc798250 100644 --- a/package/firewall/files/firewall.config +++ b/package/firewall/files/firewall.config @@ -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 diff --git a/package/firewall/files/lib/core_redirect.sh b/package/firewall/files/lib/core_redirect.sh index 64c619e434..f511d2915e 100644 --- a/package/firewall/files/lib/core_redirect.sh +++ b/package/firewall/files/lib/core_redirect.sh @@ -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 diff --git a/package/firewall/files/lib/core_rule.sh b/package/firewall/files/lib/core_rule.sh index 8c234a33a1..64a510df92 100644 --- a/package/firewall/files/lib/core_rule.sh +++ b/package/firewall/files/lib/core_rule.sh @@ -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 diff --git a/package/firewall/files/lib/fw.sh b/package/firewall/files/lib/fw.sh index 896947241a..647bcd6a54 100644 --- a/package/firewall/files/lib/fw.sh +++ b/package/firewall/files/lib/fw.sh @@ -137,10 +137,13 @@ fw__exec() { # { } 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() { #
{ } 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() { #
{ } $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 +} diff --git a/package/firewall/files/reflection.hotplug b/package/firewall/files/reflection.hotplug index 33d121cec4..4fd8f296de 100644 --- a/package/firewall/files/reflection.hotplug +++ b/package/firewall/files/reflection.hotplug @@ -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 -- 2.30.2