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_VERSION:=2
PKG_RELEASE:=26
PKG_RELEASE:=27
include $(INCLUDE_DIR)/package.mk

View 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 ping
# Allow IPv4 ping
config rule
option src wan
option proto icmp
option icmp_type echo-request
option target ACCEPT
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
# 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
config include

View 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_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 \
}
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} \
}
[ -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

View 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

View 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
}

View 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