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
This commit is contained in:
Jo-Philipp Wich 2011-06-30 01:31:23 +00:00
parent 8976fa2622
commit 68a1c8e1e3
6 changed files with 219 additions and 73 deletions

View file

@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=firewall PKG_NAME:=firewall
PKG_VERSION:=2 PKG_VERSION:=2
PKG_RELEASE:=26 PKG_RELEASE:=27
include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/package.mk

View file

@ -8,23 +8,23 @@ config defaults
config zone config zone
option name lan option name lan
option network 'lan' option network 'lan'
option input ACCEPT option input ACCEPT
option output ACCEPT option output ACCEPT
option forward REJECT option forward REJECT
config zone config zone
option name wan option name wan
option network 'wan' option network 'wan'
option input REJECT option input REJECT
option output ACCEPT option output ACCEPT
option forward REJECT option forward REJECT
option masq 1 option masq 1
option mtu_fix 1 option mtu_fix 1
config forwarding config forwarding
option src lan option src lan
option dest wan option dest wan
# We need to accept udp packets on port 68, # We need to accept udp packets on port 68,
# see https://dev.openwrt.org/ticket/4108 # see https://dev.openwrt.org/ticket/4108
@ -33,14 +33,41 @@ config rule
option proto udp option proto udp
option dest_port 68 option dest_port 68
option target ACCEPT option target ACCEPT
option family ipv4 option family ipv4
#Allow ping # Allow IPv4 ping
config rule config rule
option src wan option src wan
option proto icmp option proto icmp
option icmp_type echo-request option icmp_type echo-request
option target ACCEPT 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
# Drop leaking router advertisements on WAN
config rule
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 # include a file with users custom iptables rules
config include config include

View file

@ -13,11 +13,11 @@ fw_config_get_redirect() {
string src_dport "" \ string src_dport "" \
string dest "" \ string dest "" \
ipaddr dest_ip "" \ ipaddr dest_ip "" \
string dest_mac "" \
string dest_port "" \ string dest_port "" \
string proto "tcpudp" \ string proto "tcpudp" \
string family "" \ string family "" \
string target "DNAT" \ string target "DNAT" \
string extra "" \
} || return } || return
[ -n "$redirect_name" ] || redirect_name=$redirect__name [ -n "$redirect_name" ] || redirect_name=$redirect__name
} }
@ -29,26 +29,27 @@ fw_load_redirect() {
local fwdchain natchain natopt nataddr natports srcdaddr srcdports local fwdchain natchain natopt nataddr natports srcdaddr srcdports
if [ "$redirect_target" == "DNAT" ]; then 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" fw_log error "DNAT redirect ${redirect_name}: needs src and dest_ip or dest_port, skipping"
return 0 return 0
} }
fwdchain="zone_${redirect_src}${redirect_dest_ip:+_forward}" fwdchain="zone_${redirect_src}_forward"
natopt="--to-destination" natopt="--to-destination"
natchain="zone_${redirect_src}_prerouting" natchain="zone_${redirect_src}_prerouting"
nataddr="$redirect_dest_ip" 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_negation srcdaddr '-d' "${redirect_src_dip:+$redirect_src_dip/$redirect_src_dip_prefixlen}"
fw_get_port_range srcdports "$redirect_src_dport" ":" fw_get_port_range srcdports "$redirect_src_dport" ":"
fw_get_negation srcdports '--dport' "$srcdports"
list_contains FW_CONNTRACK_ZONES $redirect_src || \ list_contains FW_CONNTRACK_ZONES $redirect_src || \
append FW_CONNTRACK_ZONES $redirect_src append FW_CONNTRACK_ZONES $redirect_src
elif [ "$redirect_target" == "SNAT" ]; then 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" fw_log error "SNAT redirect ${redirect_name}: needs dest and src_dip, skipping"
return 0 return 0
} }
@ -58,10 +59,11 @@ fw_load_redirect() {
natopt="--to-source" natopt="--to-source"
natchain="zone_${redirect_dest}_nat" natchain="zone_${redirect_dest}_nat"
nataddr="$redirect_src_dip" 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_negation srcdaddr '-d' "${redirect_dest_ip:+$redirect_dest_ip/$redirect_dest_ip_prefixlen}"
fw_get_port_range srcdports "$redirect_dest_port" ":" fw_get_port_range srcdports "$redirect_dest_port" ":"
fw_get_negation srcdports '--dport' "$srcdports"
list_contains FW_CONNTRACK_ZONES $redirect_dest || \ list_contains FW_CONNTRACK_ZONES $redirect_dest || \
append FW_CONNTRACK_ZONES $redirect_dest append FW_CONNTRACK_ZONES $redirect_dest
@ -79,34 +81,38 @@ fw_load_redirect() {
local srcports local srcports
fw_get_port_range srcports "$redirect_src_port" ":" fw_get_port_range srcports "$redirect_src_port" ":"
fw_get_negation srcports '--sport' "$srcports"
local destaddr local destaddr
fw_get_negation destaddr '-d' "${redirect_dest_ip:+$redirect_dest_ip/$redirect_dest_ip_prefixlen}" fw_get_negation destaddr '-d' "${redirect_dest_ip:+$redirect_dest_ip/$redirect_dest_ip_prefixlen}"
local destports local destports
fw_get_port_range destports "${redirect_dest_port:-$redirect_src_dport}" ":" fw_get_port_range destports "${redirect_dest_port:-$redirect_src_dport}" ":"
fw_get_negation destports '--dport' "$destports"
[ "$redirect_proto" == "tcpudp" ] && redirect_proto="tcp udp" [ "$redirect_proto" == "tcpudp" ] && redirect_proto="tcp udp"
for redirect_proto in $redirect_proto; do for redirect_proto in $redirect_proto; do
local pos fw_get_negation redirect_proto '-p' "$redirect_proto"
eval 'pos=$((++FW__REDIR_COUNT_'${mode#G}'_'$natchain'))' 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 \
}
fw add $mode n $natchain $redirect_target $pos { $redirect_src_ip $redirect_dest_ip } { \ [ -n "$destaddr" ] && \
$srcaddr $srcdaddr \ fw add $mode f ${fwdchain:-forward} ACCEPT + \
${redirect_proto:+-p $redirect_proto} \ { $redirect_src_ip $redirect_dest_ip } { \
${srcports:+--sport $srcports} \ $srcaddr $destaddr $redirect_proto \
${srcdports:+--dport $srcdports} \ $srcports $destports \
${redirect_src_mac:+-m mac --mac-source $redirect_src_mac} \ $redirect_src_mac \
$natopt $nataddr${natports:+:$natports} \ $redirect_extra \
} }
done
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} \
}
done done
fw_callback post redirect fw_callback post redirect

View file

@ -16,24 +16,23 @@ fw_config_get_rule() {
string proto "tcpudp" \ string proto "tcpudp" \
string target "" \ string target "" \
string family "" \ string family "" \
string limit "" \
string limit_burst "" \
string extra "" \
} || return } || return
[ -n "$rule_name" ] || rule_name=$rule__name [ -n "$rule_name" ] || rule_name=$rule__name
[ "$rule_proto" == "icmp" ] || rule_icmp_type=
} }
fw_load_rule() { fw_load_rule() {
fw_config_get_rule "$1" 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" fw_log error "NOTRACK rule ${rule_name}: needs src, skipping"
return 0 return 0
} }
fw_callback pre rule 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 table=f
local chain=input local chain=input
local target="${rule_target:-REJECT}" local target="${rule_target:-REJECT}"
@ -41,8 +40,22 @@ fw_load_rule() {
table=r table=r
chain="zone_${rule_src}_notrack" chain="zone_${rule_src}_notrack"
else else
[ -n "$rule_src" ] && chain="zone_${rule_src}${rule_dest:+_forward}" if [ -n "$rule_src" ]; then
[ -n "$rule_dest" ] && target="zone_${rule_dest}_${target}" 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 fi
local mode local mode
@ -54,17 +67,31 @@ fw_load_rule() {
[ "$rule_proto" == "tcpudp" ] && rule_proto="tcp udp" [ "$rule_proto" == "tcpudp" ] && rule_proto="tcp udp"
for rule_proto in $rule_proto; do for rule_proto in $rule_proto; do
local rule_pos fw_get_negation rule_proto '-p' "$rule_proto"
eval 'rule_pos=$((++FW__RULE_COUNT_'${mode#G}'_'$chain'))' for rule_src_port in ${rule_src_port:-""}; do
fw_get_port_range rule_src_port $rule_src_port
fw add $mode $table $chain $target $rule_pos { $rule_src_ip $rule_dest_ip } { \ fw_get_negation rule_src_port '--sport' "$rule_src_port"
$src_spec $dest_spec \ for rule_dest_port in ${rule_dest_port:-""}; do
${rule_proto:+-p $rule_proto} \ fw_get_port_range rule_dest_port $rule_dest_port
${rule_src_port:+--sport $rule_src_port} \ fw_get_negation rule_dest_port '--dport' "$rule_dest_port"
${rule_src_mac:+-m mac --mac-source $rule_src_mac} \ for rule_src_mac in ${rule_src_mac:-""}; do
${rule_dest_port:+--dport $rule_dest_port} \ fw_get_negation rule_src_mac '--mac-source' "$rule_src_mac"
${rule_icmp_type:+--icmp-type $rule_icmp_type} \ 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 done
fw_callback post rule fw_callback post rule

View file

@ -137,10 +137,13 @@ fw__exec() { # <action> <family> <table> <chain> <target> <position> { <rules> }
case "$tgt" in case "$tgt" in
-) tgt= ;; -) tgt= ;;
esac esac
local rule_offset
case "$pos" in case "$pos" in
^) pos=1 ;; ^) pos=1 ;;
$) pos= ;; $) pos= ;;
-) pos= ;; -) pos= ;;
+) eval "rule_offset=\${FW__RULE_OFS_${app}_${tab}_${chn}:-1}" ;;
esac esac
if ! fw__has - family || ! fw__has $tab ; then if ! fw__has - family || ! fw__has $tab ; then
@ -159,13 +162,29 @@ fw__exec() { # <action> <family> <table> <chain> <target> <position> { <rules> }
fi fi
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 while [ $# -gt 1 ]; do
case "$app:$1" in # special parameter handling
ip6tables:--icmp-type) cmdline="$cmdline --icmpv6-type" ;; case "$1:$2" in
ip6tables:icmp|ip6tables:ICMP) cmdline="$cmdline icmpv6" ;; -p:icmp*|--protocol:icmp*)
iptables:--icmpv6-type) cmdline="$cmdline --icmp-type" ;; [ "$app" = ip6tables ] && \
iptables:icmpv6) cmdline="$cmdline icmp" ;; 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" ;; *) cmdline="$cmdline $1" ;;
esac esac
shift shift
@ -175,7 +194,10 @@ fw__exec() { # <action> <family> <table> <chain> <target> <position> { <rules> }
$cmdline $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() { fw_get_port_range() {
@ -189,8 +211,8 @@ fw_get_port_range() {
local _first=${_ports%-*} local _first=${_ports%-*}
local _last=${_ports#*-} local _last=${_ports#*-}
if [ "$_first" != "$_last" ]; then if [ "${_first#!}" != "${_last#!}" ]; then
export -- "$_var=$_first$_delim$_last" export -- "$_var=$_first$_delim${_last#!}"
else else
export -- "$_var=$_first" export -- "$_var=$_first"
fi fi
@ -221,11 +243,11 @@ fw_get_family_mode() {
fw_get_negation() { fw_get_negation() {
local _var="$1" local _var="$1"
local _flag="$2" local _flag="$2"
local _ipaddr="$3" local _value="$3"
[ "${_ipaddr#!}" != "$_ipaddr" ] && \ [ "${_value#!}" != "$_value" ] && \
export -n -- "$_var=! $_flag ${_ipaddr#!}" || \ export -n -- "$_var=! $_flag ${_value#!}" || \
export -n -- "$_var=${_ipaddr:+$_flag $_ipaddr}" export -n -- "$_var=${_value:+$_flag $_value}"
} }
fw_get_subnet4() { fw_get_subnet4() {
@ -245,3 +267,66 @@ fw_get_subnet4() {
*) export -n -- "$_var=" ;; *) export -n -- "$_var=" ;;
esac 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
}

View file

@ -56,6 +56,7 @@ if [ "$ACTION" = "add" ] && [ "$INTERFACE" = "wan" ]; then
[ "$src" = wan ] && [ "$target" = DNAT ] && { [ "$src" = wan ] && [ "$target" = DNAT ] && {
local dest local dest
config_get dest "$cfg" dest "lan" config_get dest "$cfg" dest "lan"
[ "$dest" != "*" ] || return
local net local net
for net in $(find_networks "$dest"); do for net in $(find_networks "$dest"); do