+## Define a new host. Follow by calls to `iface' to define the host's
+## interfaces.
+defhost () {
+ host=$1
+ addword allhosts $host
+ eval host_type_$host=endsys
+}
+
+## router
+##
+## Declare the host to be a router, so it should forward packets and so on.
+router () {
+ eval host_type_$host=router
+}
+
+## iface IFACE NET ...
+##
+## Define a host's interfaces. Specifically, declares that the host has an
+## interface IFACE attached to the listed NETs.
+iface () {
+ name=$1; shift
+ for net in "$@"; do
+ addword host_ifaces_$host $name=$net
+ done
+}
+
+## 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 NOT, 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