classify.m4: Clean up interface map tracing.
[firewall] / functions.m4
index 0ebba30..d7b42b2 100644 (file)
@@ -94,12 +94,12 @@ ip46tables () {
 ## Ensure that the named chains exist and are empty.
 clearchain () {
   set -e
-  for chain; do
-    case $chain in
-      *:*) table=${chain%:*} chain=${chain#*:} ;;
+  for _chain; do
+    case $_chain in
+      *:*) table=${_chain%:*} _chain=${_chain#*:} ;;
       *) table=filter ;;
     esac
-    run ip46tables -t $table -N $chain 2>/dev/null || :
+    run ip46tables -t $table -N $_chain 2>/dev/null || :
   done
 }
 
@@ -183,7 +183,7 @@ setdevopt () {
     for ver in ipv4 ipv6; do
       if [ -f /proc/sys/net/$ver/conf/$i/$opt ]; then
        any=t
-       run sysctl -q net/ipv4/conf/$i/$opt="$val"
+       run sysctl -q net/$ver/conf/$i/$opt="$val"
       fi
     done
     case $any in
@@ -310,6 +310,26 @@ dnsresolver () {
   done
 }
 
+## dnsserver CHAIN
+##
+## Add rules to allow CHAIN to be a DNS server.
+dnsserver () {
+  set -e
+  chain=$1
+
+  ## Allow TCP access.  Hitting us with SYNs will make us deploy SYN cookies,
+  ## but that's tolerable.
+  run ip46tables -A $chain -j ACCEPT -p tcp --destination-port 53
+
+  ## Avoid being a DDoS amplifier by rate-limiting incoming DNS queries.
+  clearchain $chain-udp-dns
+  run ip46tables -A $chain-udp-dns -j ACCEPT \
+         -m limit --limit 20/second --limit-burst 300
+  run ip46tables -A $chain-udp-dns -g dns-rate-limit
+  run ip46tables -A $chain -j $chain-udp-dns \
+         -p udp --destination-port 53
+}
+
 ## openports CHAIN [MIN MAX]
 ##
 ## Add rules to CHAIN to allow the open ports.
@@ -513,14 +533,19 @@ host () {
 defhost () {
   host=$1
   addword allhosts $host
-  eval host_type_$host=endsys
+  eval host_type_$host=server
 }
 
-## router
+## hosttype TYPE
 ##
-## Declare the host to be a router, so it should forward packets and so on.
-router () {
-  eval host_type_$host=router
+## Declare the host to have the given type.
+hosttype () {
+  type=$1
+  case $type in
+    router | server | client) ;;
+     *) echo >&2 "$0: bad host type \`$type'"; exit 1 ;;
+  esac
+  eval host_type_$host=$type
 }
 
 ## iface IFACE NET ...
@@ -534,6 +559,118 @@ iface () {
   done
 }
 
+## matchnets OPT WIN FLAGS PREPARE BASE SUFFIX NEXT NET [NET ...]
+##
+## Build rules which match a particular collection of networks.
+##
+## Specifically, use the address-comparison operator OPT (typically `-s' or
+## `-d') to match the addresses of each NET, writing the rules to the chain
+## BASESUFFIX.  If we find a match, dispatch to WIN-CLASS, where CLASS is the
+## class of the matching network.  In order to deal with networks containing
+## negative address ranges, more chains may need to be constructed; they will
+## be named BASE#Q for sequence numbers Q starting with NEXT.  All of this
+## happens on the `mangle' table, and there isn't (currently) a way to tweak
+## this.
+##
+## The FLAGS gather additional interesting information about the job,
+## separated by colons.  The only flag currently is :default: which means
+## that the default network was listed.
+##
+## Finally, there is a hook PREPARE which is called just in advance of
+## processing the final network, passing it the argument FLAGS.  (The PREPARE
+## string will be subjected to shell word-splitting, so it can provide some
+## arguments of its own if it wants.)  It should set `mode' to indicate how
+## the chain should be finished.
+##
+## goto                If no networks matched, then issue a final `goto' to the
+##             chain named by the variable `fail'.
+##
+## call                Run `$finish CHAIN' to write final rules to the named CHAIN
+##             (which may be suffixed from the original BASE argument if
+##             this was necessary).  This function will arrange to call
+##             these rules if no networks match.
+##
+## ret         If no network matches then return (maybe by falling off the
+##             end of the chain).
+matchnets () {
+  local opt win flags prepare base suffix next net lose splitp
+  opt=$1 win=$2 flags=$3 prepare=$4 base=$5 suffix=$6 next=$7 net=$8
+  shift 8
+
+  ## If this is the default network, then set the flag.
+  case "$net" in default) flags=${flags}default: ;; esac
+
+  ## Do an initial pass over the addresses to see whether there are any
+  ## negative ranges.  If so, we'll need to split.  See also the standard
+  ## joke about soup.
+  splitp=nil
+  eval "addrs=\"\$net_inet_$net \$net_inet6_$net\""
+  for a in $addrs; do case $a in !*) splitp=t; break ;; esac; done
+
+  trace "MATCHNETS [splitp $splitp] $opt $win $flags [$prepare] $base $suffix $next : $net $*"
+
+  ## Work out how to handle matches against negative address ranges.  If this
+  ## is the last network, invoke the PREPARE hook to find out.  Otherwise, if
+  ## we have to split the chain, recursively build the target here.
+  case $splitp,$# in
+    t,0 | nil,0)
+      $prepare $flags
+      case $splitp,$mode in
+       *,goto)
+         lose="-g $fail"
+         ;;
+       *,ret)
+         lose="-j RETURN"
+         ;;
+       t,call)
+         clearchain mangle:$base#$next
+         lose="-g $base#$next"
+         ;;
+       nil,call)
+         ;;
+      esac
+      ;;
+    t,*)
+      clearchain mangle:$base#$next
+      matchnets $opt $win $flags "$prepare" \
+       $base \#$next $(( $next + 1 )) "$@"
+      lose="-g $base#$next" mode=goto
+      ;;
+    *)
+      mode=continue
+      ;;
+  esac
+
+  ## Populate the chain with rules to match the necessary networks.
+  eval addr=\$net_inet_$net addr6=\$net_inet6_$net class=\$net_class_$net
+  for a in $addr; do
+    case $a in
+      !*) run iptables -t mangle -A $base$suffix $lose $opt ${a#!} ;;
+      *) run iptables -t mangle -A $base$suffix -g $win-$class $opt $a ;;
+    esac
+  done
+  for a in $addr6; do
+    case $a in
+      !*) run ip6tables -t mangle -A $base$suffix $lose $opt ${a#!} ;;
+      *) run ip6tables -t mangle -A $base$suffix -g $win-$class $opt $a ;;
+    esac
+  done
+
+  ## Wrap up the chain appropriately.  If we didn't split and there are more
+  ## networks to handle then append the necessary rules now.  (If we did
+  ## split, then we already wrote the rules for them above.)  If there are no
+  ## more networks then consult the `mode' setting to find out what to do.
+  case $splitp,$#,$mode in
+    *,0,ret) ;;
+    *,*,goto) run ip46tables -t mangle -A $base$suffix $lose ;;
+    t,0,call) $finish $base#$next ;;
+    nil,0,call) $finish $base$suffix ;;
+    nil,*,*)
+      matchnets $opt $win $flags "$prepare" $base "$suffix" $next "$@"
+      ;;
+  esac
+}
+
 ## net_interfaces HOST NET
 ##
 ## Determine the interfaces on which packets may plausibly arrive from the