eval port_$name=$number
}
+## defproto NAME NUMBER
+##
+## Define $proto_NAME to be NUMBER.
+defproto () {
+ name=$1 number=$2
+ eval proto_$name=$number
+}
+
+## addword VAR WORD
+##
+## Adds WORD to the value of the shell variable VAR, if it's not there
+## already. Words are separated by a single space; no leading or trailing
+## spaces are introduced.
+addword () {
+ var=$1 word=$2
+ eval val=\$$var
+ case " $val " in
+ *" $word "*) ;;
+ *) eval "$var=\${$var:+\$val }\$word" ;;
+ esac
+}
+
m4_divert(38)m4_dnl
###--------------------------------------------------------------------------
### Utility chains (used by function definitions).
-m4_divert(22)m4_dnl
+m4_divert(20)m4_dnl
###--------------------------------------------------------------------------
### Basic chain constructions.
*:*) table=${chain%:*} chain=${chain#*:} ;;
*) table=filter ;;
esac
- run ip46tables -t $table -N $chain
+ run ip46tables -t $table -N $chain 2>/dev/null || :
done
}
+## makeset SET TYPE [PARAMS]
+##
+## Ensure that the named ipset exists. Don't clear it.
+makeset () {
+ set -e
+ name=$1; shift
+ if ipset -nL | grep -q "^Name: $name$"; then
+ :
+ else
+ ipset -N "$name" "$@"
+ fi
+}
+
## errorchain CHAIN ACTION ARGS ...
##
## Make a chain which logs a message and then invokes some other action,
run ip46tables -t $table -A $chain -j LOG \
-m limit --limit 3/minute --limit-burst 10 \
--log-prefix "fw: $chain " --log-level notice
- run ip46tables -t $table -A $chain -j "$@"
+ run ip46tables -t $table -A $chain -j "$@" \
+ -m limit --limit 20/second --limit-burst 100
+ run ip46tables -t $table -A $chain -j DROP
}
-m4_divert(24)m4_dnl
+m4_divert(20)m4_dnl
###--------------------------------------------------------------------------
### Basic option setting.
done
}
-m4_divert(26)m4_dnl
+m4_divert(20)m4_dnl
###--------------------------------------------------------------------------
### Packet filter construction.
-m frag --fragfirst
run ip6tables -A accept-non-init-frag -j ACCEPT
-m4_divert(26)m4_dnl
+m4_divert(20)m4_dnl
## allowservices CHAIN PROTO SERVICE ...
##
## Add rules to allow the SERVICES on the CHAIN.
## Add rules to CHAIN to allow NTP with NTPSERVERs.
ntpclient () {
set -e
- chain=$1; shift
- for ntp; do
- run iptables -A $chain -s $ntp -j ACCEPT \
- -p udp --source-port 123 --destination-port 123
- done
+ ntpchain=$1; shift
+
+ clearchain ntp-servers
+ for ntp; do run iptables -A ntp-servers -j ACCEPT -s $ntp; done
+ run iptables -A $ntpchain -j ntp-servers \
+ -p udp --source-port 123 --destination-port 123
}
## dnsresolver CHAIN
run ip46tables -A $chain -p udp -g interesting --destination-port $1:$2
}
-m4_divert(28)m4_dnl
+m4_divert(20)m4_dnl
###--------------------------------------------------------------------------
### Packet classification.
+###
+### See `classify.m4' for an explanation of how the firewall machinery for
+### packet classification works.
+###
+### A list of all network names is kept in `allnets'. For each network NET,
+### shell variables are defined describing their properties.
+###
+### net_class_NET The class of the network, as defined by
+### `defnetclass'.
+### net_inet_NET List of IPv4 address ranges in the network.
+### net_inet6_NET List of IPv6 address ranges in the network.
+### net_fwd_NET List of other networks that this one forwards to.
+### net_hosts_NET List of hosts known to be in the network.
+### host_inet_HOST IPv4 address of the named HOST.
+### host_inet6_HOST IPv6 address of the named HOST.
+###
+### Similarly, a list of hosts is kept in `allhosts', and for each host HOST,
+### a shell variables are defined:
+###
+### host_ifaces_HOST List of interfaces for this host and the networks
+### they attach to, in the form IFACE=NET.
## defbitfield NAME WIDTH
##
## Pass 1. Establish the from_NAME and to_NAME constants, and the
## netclass's mask bit.
+ trace "netclass $name = $netclassindex"
eval from_$name=$(( $netclassindex << $BIT_FROM ))
eval to_$name=$(( $netclassindex << $BIT_TO ))
eval _mask_$name=$(( 1 << ($netclassindex + $BIT_MASK) ))
netclassindex=$(( $netclassindex + 1 ))
}
-## defiface NAME[,NAME,...] NETCLASS:NETWORK/MASK...
-##
-## Declares network interfaces with the given NAMEs and associates with them
-## a number of reachable networks. During source classification, a packet
-## arriving on interface NAME from an address in NETWORK/MASK is classified
-## as coming from to NETCLASS. During destination classification, all
-## packets going to NETWORK/MASK are classified as going to NETCLASS,
-## regardless of interface (which is good, because the outgoing interface
-## hasn't been determined yet).
-##
-## As a special case, the NETWORK/MASK can be the string `default', which
-## indicates that all addresses not matched elsewhere should be considered.
-ifaces=:
-defaultifaces=""
-allnets= allnets6=
-defiface () {
- set -e
- names=$1; shift
- seen=:
- for name in $(echo $names | sed 'y/,/ /'); do
- case $seen in *:"$name":*) continue ;; esac
- seen=$seen$name:
- case $ifaces in
- *:"$name":*) ;;
- *)
- clearchain mangle:in-$name
- run ip46tables -t mangle -A in-classify -i $name -g in-$name
- ;;
+## defnet NET CLASS
+##
+## Define a network. Follow by calls to `addr', `forwards', etc. to define
+## properties of the network. Networks are processed in order, so if their
+## addresses overlap then the more specific addresses should be defined
+## earlier.
+defnet () {
+ net=$1 class=$2
+ addword allnets $net
+ eval net_class_$1=\$class
+}
+
+## addr ADDRESS/LEN ...
+##
+## Define addresses for the network being defined. ADDRESSes are in
+## colon-separated IPv6 or dotted-quad IPv4 form.
+addr () {
+ for i in "$@"; do
+ case "$i" in
+ *:*) addword net_inet6_$net $i ;;
+ *) addword net_inet_$net $i ;;
esac
- ifaces=$ifaces$name:
- for item; do
- netclass=${item%:*} addr=${item#*:}
- case $addr in
- default)
- case "$defaultifaces,$defaultclass" in
- ,* | *,$netclass)
- defaultifaces="$defaultifaces $name"
- defaultclass=$netclass
- ;;
- *)
- echo >&2 "$0: inconsistent default netclasses"
- exit 1
- ;;
- esac
- ;;
- *:*)
- run ip6tables -t mangle -A in-$name -g mark-from-$netclass \
- -s $addr
- run ip6tables -t mangle -A out-classify -g mark-to-$netclass \
- -d $addr
- allnets6="$allnets6 $name:$addr"
- ;;
- *)
- run iptables -t mangle -A in-$name -g mark-from-$netclass \
- -s $addr
- run iptables -t mangle -A out-classify -g mark-to-$netclass \
- -d $addr
- allnets="$allnets $name:$addr"
- ;;
- esac
- done
done
}
-## defvpn IFACE CLASS NET HOST:ADDR ...
+## forwards NET ...
##
-## Defines a VPN interface. If the interface has the form `ROOT+' (i.e., a
-## netfilter wildcard) then define a separate interface ROOTHOST routing to
-## ADDR; otherwise just write a blanket rule allowing the whole NET. All
-## addresses concerned are put in the named CLASS.
-defvpn () {
- set -e
- iface=$1 class=$2 net=$3; shift 3
- case $iface in
- *-+)
- root=${iface%+}
- for host; do
- name=${host%%:*} addr=${host#*:}
- defiface $root$name $class:$addr
- done
- ;;
- *)
- defiface $iface $class:$net
- ;;
+## Declare that packets from this network are forwarded to the other NETs.
+forwards () {
+ eval "net_fwd_$net=\"$*\""
+}
+
+## noxit NET ...
+##
+## Declare that packets from this network must not be forwarded to the other
+## NETs.
+noxit () {
+ eval "net_noxit_$net=\"$*\""
+}
+
+## host HOST ADDR ...
+##
+## Define the address of an individual host on the current network. The
+## ADDRs may be full IPv4 or IPv6 addresses, or offsets from the containing
+## network address, which is a simple number for IPv4, or a suffix beginning
+## with `::' for IPv6. If an IPv6 base address is provided for the network
+## but not for the host then the host's IPv4 address is used as a suffix.
+host () {
+ name=$1; shift
+
+ ## Work out which addresses we've actually been given.
+ unset a6
+ for i in "$@"; do
+ case "$i" in ::*) a6=$i ;; *) a=$i ;; esac
+ done
+ case "${a+t}" in
+ t) ;;
+ *) echo >&2 "$0: no address for $name"; exit 1 ;;
esac
+ case "${a6+t}" in t) ;; *) a6=::$a ;; esac
+
+ ## Work out the IPv4 address.
+ eval nn=\$net_inet_$net
+ for n in $nn; do
+ addr=${n%/*}
+ base=${addr%.*}
+ offset=${addr##*.}
+ case $a in *.*) aa=$a ;; *) aa=$base.$(( $offset + $a )) ;; esac
+ eval host_inet_$name=$aa
+ done
+
+ ## Work out the IPv6 address.
+ eval nn=\$net_inet6_$net
+ for n in $nn; do
+ addr=${n%/*}
+ base=${addr%::*}
+ case $a in ::*) aa=$addr$a ;; *) aa=$a ;; esac
+ eval host_inet6_$name=$aa
+ done
+
+ ## Remember the host in the list.
+ addword net_hosts_$net $name
+}
+
+## defhost NAME
+##
+## 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
+}
+
+## net_interfaces HOST NET
+##
+## Determine the interfaces on which packets may plausibly arrive from the
+## named NET. Returns `-' if no such interface exists.
+##
+## This algorithm is not very clever. It's just about barely good enough to
+## deduce transitivity through a simple routed network; with complicated
+## networks, it will undoubtedly give wrong answers. Check the results
+## carefully, and, if necessary, list the connectivity explicitly; use the
+## special interface `-' for networks you know shouldn't send packets to a
+## host.
+net_interfaces () {
+ host=$1 startnet=$2
+
+ ## Determine the locally attached networks.
+ targets=:
+ eval ii=\$host_ifaces_$host
+ for i in $ii; do targets=$targets$i:; done
+
+ ## Determine the transitivity.
+ seen=:
+ nets=$startnet
+ while :; do
+
+ ## First pass. Determine whether any of the networks we're considering
+ ## are in the target set. If they are, then return the corresponding
+ ## interfaces.
+ found=""
+ for net in $nets; do
+ tg=$targets
+ while :; do
+ any=nil
+ case $tg in
+ *"=$net:"*)
+ n=${tg%=$net:*}; tg=${n%:*}:; n=${n##*:}
+ addword found $n
+ any=t
+ ;;
+ esac
+ case $any in nil) break ;; esac
+ done
+ done
+ case "$found" in ?*) echo $found; return ;; esac
+
+ ## No joy. Determine the set of networks which (a) these ones can
+ ## forward to, and (b) that we've not considered already. These are the
+ ## nets we'll consider next time around.
+ nextnets=""
+ any=nil
+ for net in $nets; do
+ eval fwd=\$net_fwd_$net
+ for n in $fwd; do
+ case $seen in *":$n:"*) continue ;; esac
+ seen=$seen$n:
+ eval noxit=\$net_noxit_$n
+ case " $noxit " in *" $startnet "*) continue ;; esac
+ case " $nextnets " in
+ *" $n "*) ;;
+ *) addword nextnets $n; any=t ;;
+ esac
+ done
+ done
+
+ ## If we've run out of networks then there's no reachability. Return a
+ ## failure.
+ case $any in nil) echo -; return ;; esac
+ nets=$nextnets
+ done
}
m4_divert(-1)