| 1 | ### -*-sh-*- |
| 2 | ### |
| 3 | ### Utility functions for firewall scripts |
| 4 | ### |
| 5 | ### (c) 2008 Mark Wooding |
| 6 | ### |
| 7 | |
| 8 | ###----- Licensing notice --------------------------------------------------- |
| 9 | ### |
| 10 | ### This program is free software; you can redistribute it and/or modify |
| 11 | ### it under the terms of the GNU General Public License as published by |
| 12 | ### the Free Software Foundation; either version 2 of the License, or |
| 13 | ### (at your option) any later version. |
| 14 | ### |
| 15 | ### This program is distributed in the hope that it will be useful, |
| 16 | ### but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 | ### GNU General Public License for more details. |
| 19 | ### |
| 20 | ### You should have received a copy of the GNU General Public License |
| 21 | ### along with this program; if not, write to the Free Software Foundation, |
| 22 | ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| 23 | |
| 24 | m4_divert(20)m4_dnl |
| 25 | ###-------------------------------------------------------------------------- |
| 26 | ### Utility functions. |
| 27 | |
| 28 | ## doit COMMAND ARGS... |
| 29 | ## |
| 30 | ## If debugging, print the COMMAND and ARGS. If serious, execute them. |
| 31 | run () { |
| 32 | set -e |
| 33 | if [ "$FW_DEBUG" ]; then echo "* $*"; fi |
| 34 | if ! [ "$FW_NOACT" ]; then "$@"; fi |
| 35 | } |
| 36 | |
| 37 | ## trace MESSAGE... |
| 38 | ## |
| 39 | ## If debugging, print the MESSAGE. |
| 40 | trace () { |
| 41 | set -e |
| 42 | if [ "$FW_DEBUG" ]; then echo "$*"; fi |
| 43 | } |
| 44 | |
| 45 | ## defport NAME NUMBER |
| 46 | ## |
| 47 | ## Define $port_NAME to be NUMBER. |
| 48 | defport () { |
| 49 | name=$1 number=$2 |
| 50 | eval port_$name=$number |
| 51 | } |
| 52 | |
| 53 | ## defproto NAME NUMBER |
| 54 | ## |
| 55 | ## Define $proto_NAME to be NUMBER. |
| 56 | defproto () { |
| 57 | name=$1 number=$2 |
| 58 | eval proto_$name=$number |
| 59 | } |
| 60 | |
| 61 | ## addword VAR WORD |
| 62 | ## |
| 63 | ## Adds WORD to the value of the shell variable VAR, if it's not there |
| 64 | ## already. Words are separated by a single space; no leading or trailing |
| 65 | ## spaces are introduced. |
| 66 | addword () { |
| 67 | var=$1 word=$2 |
| 68 | eval val=\$$var |
| 69 | case " $val " in |
| 70 | *" $word "*) ;; |
| 71 | *) eval "$var=\${$var:+\$val }\$word" ;; |
| 72 | esac |
| 73 | } |
| 74 | |
| 75 | m4_divert(38)m4_dnl |
| 76 | ###-------------------------------------------------------------------------- |
| 77 | ### Utility chains (used by function definitions). |
| 78 | |
| 79 | m4_divert(20)m4_dnl |
| 80 | ###-------------------------------------------------------------------------- |
| 81 | ### Basic chain constructions. |
| 82 | |
| 83 | ## ip46tables ARGS ... |
| 84 | ## |
| 85 | ## Do the same thing for `iptables' and `ip6tables'. |
| 86 | ip46tables () { |
| 87 | set -e |
| 88 | iptables "$@" |
| 89 | ip6tables "$@" |
| 90 | } |
| 91 | |
| 92 | ## clearchain CHAIN CHAIN ... |
| 93 | ## |
| 94 | ## Ensure that the named chains exist and are empty. |
| 95 | clearchain () { |
| 96 | set -e |
| 97 | for _chain; do |
| 98 | case $_chain in |
| 99 | *:*) table=${_chain%:*} _chain=${_chain#*:} ;; |
| 100 | *) table=filter ;; |
| 101 | esac |
| 102 | run ip46tables -t $table -N $_chain 2>/dev/null || : |
| 103 | done |
| 104 | } |
| 105 | |
| 106 | ## makeset SET TYPE [PARAMS] |
| 107 | ## |
| 108 | ## Ensure that the named ipset exists. Don't clear it. |
| 109 | makeset () { |
| 110 | set -e |
| 111 | name=$1; shift |
| 112 | v=$(ipset --version) |
| 113 | createp=t |
| 114 | case "$v" in |
| 115 | "ipset v4"*) |
| 116 | if ipset -nL | grep -q "^Name: $name\$"; then createp=nil; fi |
| 117 | ;; |
| 118 | *) |
| 119 | if ipset -n -L | grep -q "^$name\$"; then createp=nil; fi |
| 120 | ;; |
| 121 | esac |
| 122 | case $createp in |
| 123 | t) ipset -N "$name" "$@" ;; |
| 124 | esac |
| 125 | } |
| 126 | |
| 127 | ## errorchain CHAIN ACTION ARGS ... |
| 128 | ## |
| 129 | ## Make a chain which logs a message and then invokes some other action, |
| 130 | ## typically REJECT. Log messages are prefixed by `fw: CHAIN'. |
| 131 | errorchain () { |
| 132 | set -e |
| 133 | chain=$1; shift |
| 134 | case $chain in |
| 135 | *:*) table=${chain%:*} chain=${chain#*:} ;; |
| 136 | *) table=filter ;; |
| 137 | esac |
| 138 | clearchain $table:$chain |
| 139 | run ip46tables -t $table -A $chain -j LOG \ |
| 140 | -m limit --limit 3/minute --limit-burst 10 \ |
| 141 | --log-prefix "fw: $chain " --log-level notice || : |
| 142 | run ip46tables -t $table -A $chain -j "$@" \ |
| 143 | -m limit --limit 20/second --limit-burst 100 |
| 144 | run ip46tables -t $table -A $chain -j DROP |
| 145 | } |
| 146 | |
| 147 | m4_divert(20)m4_dnl |
| 148 | ###-------------------------------------------------------------------------- |
| 149 | ### Basic option setting. |
| 150 | |
| 151 | ## setopt OPTION VALUE |
| 152 | ## |
| 153 | ## Set an IP sysctl. |
| 154 | setopt () { |
| 155 | set -e |
| 156 | opt=$1 val=$2 |
| 157 | any=nil |
| 158 | for ver in ipv4 ipv6; do |
| 159 | if [ -f /proc/sys/net/$ver/$opt ]; then |
| 160 | run sysctl -q net/$ver/$opt="$val" |
| 161 | any=t |
| 162 | fi |
| 163 | done |
| 164 | case $any in |
| 165 | nil) echo >&2 "$0: unknown IP option $opt"; exit 1 ;; |
| 166 | esac |
| 167 | } |
| 168 | |
| 169 | ## setdevopt OPTION VALUE [INTERFACES ...] |
| 170 | ## |
| 171 | ## Set an IP interface-level sysctl. |
| 172 | setdevopt () { |
| 173 | set -e |
| 174 | opt=$1 val=$2; shift 2 |
| 175 | case "$#,$1" in |
| 176 | 0, | 1,all) |
| 177 | set -- $( |
| 178 | seen=: |
| 179 | for ver in ipv4 ipv6; do |
| 180 | cd /proc/sys/net/$ver/conf |
| 181 | for i in *; do |
| 182 | [ -f $i/$opt ] || continue |
| 183 | case "$seen" in (*:$i:*) continue ;; esac |
| 184 | echo $i |
| 185 | done |
| 186 | done) |
| 187 | ;; |
| 188 | esac |
| 189 | for i in "$@"; do |
| 190 | any=nil |
| 191 | for ver in ipv4 ipv6; do |
| 192 | if [ -f /proc/sys/net/$ver/conf/$i/$opt ]; then |
| 193 | any=t |
| 194 | run sysctl -q net/$ver/conf/$i/$opt="$val" |
| 195 | fi |
| 196 | done |
| 197 | case $any in |
| 198 | nil) echo >&2 "$0: unknown device option $opt"; exit 1 ;; |
| 199 | esac |
| 200 | done |
| 201 | } |
| 202 | |
| 203 | m4_divert(20)m4_dnl |
| 204 | ###-------------------------------------------------------------------------- |
| 205 | ### Packet filter construction. |
| 206 | |
| 207 | ## conntrack CHAIN |
| 208 | ## |
| 209 | ## Add connection tracking to CHAIN, and allow obvious stuff. |
| 210 | conntrack () { |
| 211 | set -e |
| 212 | chain=$1 |
| 213 | run ip46tables -A $chain -p tcp -m state \ |
| 214 | --state ESTABLISHED,RELATED -j ACCEPT |
| 215 | run ip46tables -A $chain -p tcp ! --syn -g bad-tcp |
| 216 | } |
| 217 | |
| 218 | ## commonrules CHAIN |
| 219 | ## |
| 220 | ## Add standard IP filtering rules to the CHAIN. |
| 221 | commonrules () { |
| 222 | set -e |
| 223 | chain=$1 |
| 224 | |
| 225 | ## Pass fragments through, assuming that the eventual destination will sort |
| 226 | ## things out properly. Except for TCP, that is, which should never be |
| 227 | ## fragmented. This is an extra pain for ip6tables, which doesn't provide |
| 228 | ## a pleasant way to detect non-initial fragments. |
| 229 | run iptables -A $chain -p tcp -f -g tcp-fragment |
| 230 | run iptables -A $chain -f -j ACCEPT |
| 231 | run ip6tables -A $chain -p tcp -g tcp-fragment \ |
| 232 | -m ipv6header --soft --header frag |
| 233 | run ip6tables -A $chain -j accept-non-init-frag |
| 234 | } |
| 235 | |
| 236 | m4_divert(38)m4_dnl |
| 237 | ## Accept a non-initial fragment. This is only needed by IPv6, to work |
| 238 | ## around a deficiency in the option parser. |
| 239 | run ip6tables -N accept-non-init-frag |
| 240 | run ip6tables -A accept-non-init-frag -j RETURN \ |
| 241 | -m frag --fragfirst |
| 242 | run ip6tables -A accept-non-init-frag -j ACCEPT |
| 243 | |
| 244 | m4_divert(20)m4_dnl |
| 245 | ## allowservices CHAIN PROTO SERVICE ... |
| 246 | ## |
| 247 | ## Add rules to allow the SERVICES on the CHAIN. |
| 248 | allowservices () { |
| 249 | set -e |
| 250 | chain=$1 proto=$2; shift 2 |
| 251 | count=0 |
| 252 | list= |
| 253 | for svc; do |
| 254 | case $svc in |
| 255 | *:*) |
| 256 | n=2 |
| 257 | left=${svc%:*} right=${svc#*:} |
| 258 | case $left in *[!0-9]*) eval left=\$port_$left ;; esac |
| 259 | case $right in *[!0-9]*) eval right=\$port_$right ;; esac |
| 260 | svc=$left:$right |
| 261 | ;; |
| 262 | *) |
| 263 | n=1 |
| 264 | case $svc in *[!0-9]*) eval svc=\$port_$svc ;; esac |
| 265 | ;; |
| 266 | esac |
| 267 | case $svc in |
| 268 | *: | :* | "" | *[!0-9:]*) |
| 269 | echo >&2 "Bad service name" |
| 270 | exit 1 |
| 271 | ;; |
| 272 | esac |
| 273 | count=$(( $count + $n )) |
| 274 | if [ $count -gt 15 ]; then |
| 275 | run ip46tables -A $chain -p $proto -m multiport -j ACCEPT \ |
| 276 | --destination-ports ${list#,} |
| 277 | list= count=$n |
| 278 | fi |
| 279 | list=$list,$svc |
| 280 | done |
| 281 | case $list in |
| 282 | "") |
| 283 | ;; |
| 284 | ,*,*) |
| 285 | run ip46tables -A $chain -p $proto -m multiport -j ACCEPT \ |
| 286 | --destination-ports ${list#,} |
| 287 | ;; |
| 288 | *) |
| 289 | run ip46tables -A $chain -p $proto -j ACCEPT \ |
| 290 | --destination-port ${list#,} |
| 291 | ;; |
| 292 | esac |
| 293 | } |
| 294 | |
| 295 | ## ntpclient CHAIN NTPSERVER ... |
| 296 | ## |
| 297 | ## Add rules to CHAIN to allow NTP with NTPSERVERs. |
| 298 | ntpclient () { |
| 299 | set -e |
| 300 | ntpchain=$1; shift |
| 301 | |
| 302 | clearchain ntp-servers |
| 303 | for ntp; do run iptables -A ntp-servers -j ACCEPT -s $ntp; done |
| 304 | run iptables -A $ntpchain -j ntp-servers \ |
| 305 | -p udp --source-port 123 --destination-port 123 |
| 306 | } |
| 307 | |
| 308 | ## dnsresolver CHAIN |
| 309 | ## |
| 310 | ## Add rules to allow CHAIN to be a DNS resolver. |
| 311 | dnsresolver () { |
| 312 | set -e |
| 313 | chain=$1 |
| 314 | for p in tcp udp; do |
| 315 | run ip46tables -A $chain -j ACCEPT \ |
| 316 | -m state --state ESTABLISHED \ |
| 317 | -p $p --source-port 53 |
| 318 | done |
| 319 | } |
| 320 | |
| 321 | ## dnsserver CHAIN |
| 322 | ## |
| 323 | ## Add rules to allow CHAIN to be a DNS server. |
| 324 | dnsserver () { |
| 325 | set -e |
| 326 | chain=$1 |
| 327 | |
| 328 | ## Allow TCP access. Hitting us with SYNs will make us deploy SYN cookies, |
| 329 | ## but that's tolerable. |
| 330 | run ip46tables -A $chain -j ACCEPT -p tcp --destination-port 53 |
| 331 | |
| 332 | ## Avoid being a DDoS amplifier by rate-limiting incoming DNS queries. |
| 333 | clearchain $chain-udp-dns |
| 334 | run ip46tables -A $chain-udp-dns -j ACCEPT \ |
| 335 | -m limit --limit 20/second --limit-burst 300 |
| 336 | run ip46tables -A $chain-udp-dns -g dns-rate-limit |
| 337 | run ip46tables -A $chain -j $chain-udp-dns \ |
| 338 | -p udp --destination-port 53 |
| 339 | } |
| 340 | |
| 341 | ## openports CHAIN [MIN MAX] |
| 342 | ## |
| 343 | ## Add rules to CHAIN to allow the open ports. |
| 344 | openports () { |
| 345 | set -e |
| 346 | chain=$1; shift |
| 347 | [ $# -eq 0 ] && set -- $open_port_min $open_port_max |
| 348 | run ip46tables -A $chain -p tcp -g interesting --destination-port $1:$2 |
| 349 | run ip46tables -A $chain -p udp -g interesting --destination-port $1:$2 |
| 350 | } |
| 351 | |
| 352 | bcp38_setup=: |
| 353 | bcp38 () { |
| 354 | ipv=$1 ifname=$2; shift 2 |
| 355 | ## Add rules for BCP38 egress filtering for IP version IPV (either 4 or 6). |
| 356 | ## IFNAME is the outgoing interface; the remaining arguments are network |
| 357 | ## prefixes. |
| 358 | |
| 359 | ## Sort out which command we're using |
| 360 | case $ipv in |
| 361 | 4) ipt=iptables ;; |
| 362 | 6) ipt=ip6tables ;; |
| 363 | *) echo >&2 "Unknown IP version $ipv"; exit 1 ;; |
| 364 | esac |
| 365 | |
| 366 | ## If we've not set up the error chain then do that. |
| 367 | case $bcp38_setup in |
| 368 | :) |
| 369 | errorchain bcp38 DROP |
| 370 | clearchain bcp38-check |
| 371 | ip46tables -A bcp38-check -g bcp38 |
| 372 | ;; |
| 373 | esac |
| 374 | |
| 375 | ## Stitch our egress filter into the outbound chains if we haven't done |
| 376 | ## that yet. Do this for both IP versions: if we're only ever given |
| 377 | ## IPv6 addresses for a particular interface then we assume that IPv4 |
| 378 | ## packets aren't allowed on it at all. |
| 379 | case $bcp38_setup in |
| 380 | *:$ifname:*) ;; |
| 381 | *) |
| 382 | run ip46tables -A OUTPUT -j bcp38-check -o $ifname |
| 383 | case $forward in |
| 384 | 1) run ip46tables -A FORWARD -j bcp38-check -o $ifname ;; |
| 385 | esac |
| 386 | bcp38_setup=$bcp38_setup$ifname: |
| 387 | ;; |
| 388 | esac |
| 389 | |
| 390 | ## Finally, add in our allowed networks. |
| 391 | for i in "$@"; do |
| 392 | run $ipt -I bcp38-check -j RETURN -s $i |
| 393 | done |
| 394 | } |
| 395 | |
| 396 | m4_divert(20)m4_dnl |
| 397 | ###-------------------------------------------------------------------------- |
| 398 | ### Packet classification. |
| 399 | ### |
| 400 | ### See `classify.m4' for an explanation of how the firewall machinery for |
| 401 | ### packet classification works. |
| 402 | ### |
| 403 | ### A list of all network names is kept in `allnets'. For each network NET, |
| 404 | ### shell variables are defined describing their properties. |
| 405 | ### |
| 406 | ### net_class_NET The class of the network, as defined by |
| 407 | ### `defnetclass'. |
| 408 | ### net_inet_NET List of IPv4 address ranges in the network. |
| 409 | ### net_inet6_NET List of IPv6 address ranges in the network. |
| 410 | ### net_via_NET List of other networks that this one forwards via. |
| 411 | ### net_hosts_NET List of hosts known to be in the network. |
| 412 | ### host_inet_HOST IPv4 address of the named HOST. |
| 413 | ### host_inet6_HOST IPv6 address of the named HOST. |
| 414 | ### |
| 415 | ### Similarly, a list of hosts is kept in `allhosts', and for each host HOST, |
| 416 | ### a shell variables are defined: |
| 417 | ### |
| 418 | ### host_ifaces_HOST List of interfaces for this host and the networks |
| 419 | ### they attach to, in the form IFACE=NET. |
| 420 | |
| 421 | ## defbitfield NAME WIDTH |
| 422 | ## |
| 423 | ## Defines MASK_NAME and BIT_NAME symbolic constants for dealing with |
| 424 | ## bitfields: x << BIT_NAME yields the value x in the correct position, and |
| 425 | ## ff & MASK_NAME extracts the corresponding value. |
| 426 | defbitfield () { |
| 427 | set -e |
| 428 | name=$1 width=$2 |
| 429 | eval MASK_$name=$(( (1 << $width) - 1 << $bitindex )) |
| 430 | eval BIT_$name=$bitindex |
| 431 | bitindex=$(( $bitindex + $width )) |
| 432 | } |
| 433 | |
| 434 | ## Define the layout of the bitfield. |
| 435 | bitindex=0 |
| 436 | defbitfield MASK 16 |
| 437 | defbitfield FROM 4 |
| 438 | defbitfield TO 4 |
| 439 | |
| 440 | ## defnetclass NAME FORWARD-TO... |
| 441 | ## |
| 442 | ## Defines a netclass called NAME, which is allowed to forward to the |
| 443 | ## FORWARD-TO netclasses. |
| 444 | ## |
| 445 | ## For each netclass, constants from_NAME and to_NAME are defined as the |
| 446 | ## appropriate values in the FROM and TO fields (i.e., not including any mask |
| 447 | ## bits). |
| 448 | ## |
| 449 | ## This function also establishes mangle chains mark-from-NAME and |
| 450 | ## mark-to-NAME for applying the appropriate mark bits to the packet. |
| 451 | ## |
| 452 | ## Because it needs to resolve forward references, netclasses must be defined |
| 453 | ## in a two-pass manner, using a loop of the form |
| 454 | ## |
| 455 | ## for pass in 1 2; do netclassindex=0; ...; done |
| 456 | netclassess= |
| 457 | defnetclass () { |
| 458 | set -e |
| 459 | name=$1; shift |
| 460 | case $pass in |
| 461 | 1) |
| 462 | |
| 463 | ## Pass 1. Establish the from_NAME and to_NAME constants, and the |
| 464 | ## netclass's mask bit. |
| 465 | trace "netclass $name = $netclassindex" |
| 466 | eval from_$name=$(( $netclassindex << $BIT_FROM )) |
| 467 | eval to_$name=$(( $netclassindex << $BIT_TO )) |
| 468 | eval fwd_$name=$(( 1 << ($netclassindex + $BIT_MASK) )) |
| 469 | nets="$nets $name" |
| 470 | ;; |
| 471 | 2) |
| 472 | |
| 473 | ## Pass 2. Compute the actual from and to values. This is fiddly: |
| 474 | ## we want to preserve the other flags. |
| 475 | from=$(( ($netclassindex << $BIT_FROM) )) |
| 476 | frommask=$(( $MASK_FROM | $MASK_MASK )) |
| 477 | for net; do |
| 478 | eval bit=\$fwd_$net |
| 479 | from=$(( $from + $bit )) |
| 480 | done |
| 481 | to=$(( ($netclassindex << $BIT_TO) )) |
| 482 | tomask=$(( $MASK_TO | $MASK_MASK ^ (1 << ($netclassindex + $BIT_MASK)) )) |
| 483 | trace "from $name --> set $(printf %08x/%08x $from $frommask)" |
| 484 | trace " to $name --> set $(printf %08x/%08x $to $tomask)" |
| 485 | |
| 486 | ## Now establish the mark-from-NAME and mark-to-NAME chains. |
| 487 | clearchain mangle:mark-from-$name mangle:mark-to-$name |
| 488 | run ip46tables -t mangle -A mark-from-$name -j MARK \ |
| 489 | --set-xmark $from/$frommask |
| 490 | run ip46tables -t mangle -A mark-to-$name -j MARK \ |
| 491 | --set-xmark $to/$tomask |
| 492 | ;; |
| 493 | esac |
| 494 | netclassindex=$(( $netclassindex + 1 )) |
| 495 | } |
| 496 | |
| 497 | ## defnet NET CLASS |
| 498 | ## |
| 499 | ## Define a network. Follow by calls to `addr', `via', etc. to define |
| 500 | ## properties of the network. Networks are processed in order, so if their |
| 501 | ## addresses overlap then the more specific addresses should be defined |
| 502 | ## earlier. |
| 503 | defnet () { |
| 504 | net=$1 class=$2 |
| 505 | addword allnets $net |
| 506 | eval net_class_$1=\$class |
| 507 | } |
| 508 | |
| 509 | ## addr ADDRESS/LEN ... |
| 510 | ## |
| 511 | ## Define addresses for the network being defined. ADDRESSes are in |
| 512 | ## colon-separated IPv6 or dotted-quad IPv4 form. |
| 513 | addr () { |
| 514 | for i in "$@"; do |
| 515 | case "$i" in |
| 516 | *:*) addword net_inet6_$net $i ;; |
| 517 | *) addword net_inet_$net $i ;; |
| 518 | esac |
| 519 | done |
| 520 | } |
| 521 | |
| 522 | ## via NET ... |
| 523 | ## |
| 524 | ## Declare that packets from this network are forwarded to the other NETs. |
| 525 | via () { |
| 526 | eval "net_via_$net=\"$*\"" |
| 527 | } |
| 528 | |
| 529 | ## noxit NET ... |
| 530 | ## |
| 531 | ## Declare that packets from this network must not be forwarded to the other |
| 532 | ## NETs. |
| 533 | noxit () { |
| 534 | eval "net_noxit_$net=\"$*\"" |
| 535 | } |
| 536 | |
| 537 | ## host HOST ADDR ... |
| 538 | ## |
| 539 | ## Define the address of an individual host on the current network. The |
| 540 | ## ADDRs may be full IPv4 or IPv6 addresses, or offsets from the containing |
| 541 | ## network address, which is a simple number for IPv4, or a suffix beginning |
| 542 | ## with `::' for IPv6. If an IPv6 base address is provided for the network |
| 543 | ## but not for the host then the host's IPv4 address is used as a suffix. |
| 544 | host () { |
| 545 | name=$1; shift |
| 546 | |
| 547 | ## Work out which addresses we've actually been given. |
| 548 | unset a6 |
| 549 | for i in "$@"; do |
| 550 | case "$i" in ::*) a6=$i ;; *) a=$i ;; esac |
| 551 | done |
| 552 | case "${a+t}" in |
| 553 | t) ;; |
| 554 | *) echo >&2 "$0: no address for $name"; exit 1 ;; |
| 555 | esac |
| 556 | case "${a6+t}" in t) ;; *) a6=::$a ;; esac |
| 557 | |
| 558 | ## Work out the IPv4 address. |
| 559 | eval nn=\$net_inet_$net |
| 560 | for n in $nn; do |
| 561 | addr=${n%/*} |
| 562 | base=${addr%.*} |
| 563 | offset=${addr##*.} |
| 564 | case $a in *.*) aa=$a ;; *) aa=$base.$(( $offset + $a )) ;; esac |
| 565 | eval host_inet_$name=$aa |
| 566 | done |
| 567 | |
| 568 | ## Work out the IPv6 address. |
| 569 | eval nn=\$net_inet6_$net |
| 570 | for n in $nn; do |
| 571 | addr=${n%/*} |
| 572 | base=${addr%::*} |
| 573 | case $a6 in ::*) aa=$base$a6 ;; *) aa=$a6 ;; esac |
| 574 | eval host_inet6_$name=$aa |
| 575 | done |
| 576 | |
| 577 | ## Remember the host in the list. |
| 578 | addword net_hosts_$net $name |
| 579 | } |
| 580 | |
| 581 | ## defhost NAME |
| 582 | ## |
| 583 | ## Define a new host. Follow by calls to `iface' to define the host's |
| 584 | ## interfaces. |
| 585 | defhost () { |
| 586 | host=$1 |
| 587 | addword allhosts $host |
| 588 | eval host_type_$host=server |
| 589 | } |
| 590 | |
| 591 | ## hosttype TYPE |
| 592 | ## |
| 593 | ## Declare the host to have the given type. |
| 594 | hosttype () { |
| 595 | type=$1 |
| 596 | case $type in |
| 597 | router | server | client) ;; |
| 598 | *) echo >&2 "$0: bad host type \`$type'"; exit 1 ;; |
| 599 | esac |
| 600 | eval host_type_$host=$type |
| 601 | } |
| 602 | |
| 603 | ## iface IFACE NET ... |
| 604 | ## |
| 605 | ## Define a host's interfaces. Specifically, declares that the host has an |
| 606 | ## interface IFACE attached to the listed NETs. |
| 607 | iface () { |
| 608 | name=$1; shift |
| 609 | for net in "$@"; do |
| 610 | addword host_ifaces_$host $name=$net |
| 611 | done |
| 612 | } |
| 613 | |
| 614 | ## matchnets OPT WIN FLAGS PREPARE BASE SUFFIX NEXT NET [NET ...] |
| 615 | ## |
| 616 | ## Build rules which match a particular collection of networks. |
| 617 | ## |
| 618 | ## Specifically, use the address-comparison operator OPT (typically `-s' or |
| 619 | ## `-d') to match the addresses of each NET, writing the rules to the chain |
| 620 | ## BASESUFFIX. If we find a match, dispatch to WIN-CLASS, where CLASS is the |
| 621 | ## class of the matching network. In order to deal with networks containing |
| 622 | ## negative address ranges, more chains may need to be constructed; they will |
| 623 | ## be named BASE#Q for sequence numbers Q starting with NEXT. All of this |
| 624 | ## happens on the `mangle' table, and there isn't (currently) a way to tweak |
| 625 | ## this. |
| 626 | ## |
| 627 | ## The FLAGS gather additional interesting information about the job, |
| 628 | ## separated by colons. The only flag currently is :default: which means |
| 629 | ## that the default network was listed. |
| 630 | ## |
| 631 | ## Finally, there is a hook PREPARE which is called just in advance of |
| 632 | ## processing the final network, passing it the argument FLAGS. (The PREPARE |
| 633 | ## string will be subjected to shell word-splitting, so it can provide some |
| 634 | ## arguments of its own if it wants.) It should set `mode' to indicate how |
| 635 | ## the chain should be finished. |
| 636 | ## |
| 637 | ## goto If no networks matched, then issue a final `goto' to the |
| 638 | ## chain named by the variable `fail'. |
| 639 | ## |
| 640 | ## call Run `$finish CHAIN' to write final rules to the named CHAIN |
| 641 | ## (which may be suffixed from the original BASE argument if |
| 642 | ## this was necessary). This function will arrange to call |
| 643 | ## these rules if no networks match. |
| 644 | ## |
| 645 | ## ret If no network matches then return (maybe by falling off the |
| 646 | ## end of the chain). |
| 647 | matchnets () { |
| 648 | local opt win flags prepare base suffix next net lose splitp |
| 649 | opt=$1 win=$2 flags=$3 prepare=$4 base=$5 suffix=$6 next=$7 net=$8 |
| 650 | shift 8 |
| 651 | |
| 652 | ## If this is the default network, then set the flag. |
| 653 | case "$net" in default) flags=${flags}default: ;; esac |
| 654 | |
| 655 | ## Do an initial pass over the addresses to see whether there are any |
| 656 | ## negative ranges. If so, we'll need to split. See also the standard |
| 657 | ## joke about soup. |
| 658 | splitp=nil |
| 659 | eval "addrs=\"\$net_inet_$net \$net_inet6_$net\"" |
| 660 | for a in $addrs; do case $a in !*) splitp=t; break ;; esac; done |
| 661 | |
| 662 | trace "MATCHNETS [splitp $splitp] $opt $win $flags [$prepare] $base $suffix $next : $net $*" |
| 663 | |
| 664 | ## Work out how to handle matches against negative address ranges. If this |
| 665 | ## is the last network, invoke the PREPARE hook to find out. Otherwise, if |
| 666 | ## we have to split the chain, recursively build the target here. |
| 667 | case $splitp,$# in |
| 668 | t,0 | nil,0) |
| 669 | $prepare $flags |
| 670 | case $splitp,$mode in |
| 671 | *,goto) |
| 672 | lose="-g $fail" |
| 673 | ;; |
| 674 | *,ret) |
| 675 | lose="-j RETURN" |
| 676 | ;; |
| 677 | t,call) |
| 678 | clearchain mangle:$base#$next |
| 679 | lose="-g $base#$next" |
| 680 | ;; |
| 681 | nil,call) |
| 682 | ;; |
| 683 | esac |
| 684 | ;; |
| 685 | t,*) |
| 686 | clearchain mangle:$base#$next |
| 687 | matchnets $opt $win $flags "$prepare" \ |
| 688 | $base \#$next $(( $next + 1 )) "$@" |
| 689 | lose="-g $base#$next" mode=goto |
| 690 | ;; |
| 691 | *) |
| 692 | mode=continue |
| 693 | ;; |
| 694 | esac |
| 695 | |
| 696 | ## Populate the chain with rules to match the necessary networks. |
| 697 | eval addr=\$net_inet_$net addr6=\$net_inet6_$net class=\$net_class_$net |
| 698 | for a in $addr; do |
| 699 | case $a in |
| 700 | !*) run iptables -t mangle -A $base$suffix $lose $opt ${a#!} ;; |
| 701 | *) run iptables -t mangle -A $base$suffix -g $win-$class $opt $a ;; |
| 702 | esac |
| 703 | done |
| 704 | for a in $addr6; do |
| 705 | case $a in |
| 706 | !*) run ip6tables -t mangle -A $base$suffix $lose $opt ${a#!} ;; |
| 707 | *) run ip6tables -t mangle -A $base$suffix -g $win-$class $opt $a ;; |
| 708 | esac |
| 709 | done |
| 710 | |
| 711 | ## Wrap up the chain appropriately. If we didn't split and there are more |
| 712 | ## networks to handle then append the necessary rules now. (If we did |
| 713 | ## split, then we already wrote the rules for them above.) If there are no |
| 714 | ## more networks then consult the `mode' setting to find out what to do. |
| 715 | case $splitp,$#,$mode in |
| 716 | *,0,ret) ;; |
| 717 | *,*,goto) run ip46tables -t mangle -A $base$suffix $lose ;; |
| 718 | t,0,call) $finish $base#$next ;; |
| 719 | nil,0,call) $finish $base$suffix ;; |
| 720 | nil,*,*) |
| 721 | matchnets $opt $win $flags "$prepare" $base "$suffix" $next "$@" |
| 722 | ;; |
| 723 | esac |
| 724 | } |
| 725 | |
| 726 | ## net_interfaces HOST NET |
| 727 | ## |
| 728 | ## Determine the interfaces on which packets may plausibly arrive from the |
| 729 | ## named NET. Returns `-' if no such interface exists. |
| 730 | ## |
| 731 | ## This algorithm is not very clever. It's just about barely good enough to |
| 732 | ## deduce transitivity through a simple routed network; with complicated |
| 733 | ## networks, it will undoubtedly give wrong answers. Check the results |
| 734 | ## carefully, and, if necessary, list the connectivity explicitly; use the |
| 735 | ## special interface `-' for networks you know shouldn't send packets to a |
| 736 | ## host. |
| 737 | net_interfaces () { |
| 738 | host=$1 startnet=$2 |
| 739 | |
| 740 | ## Determine the locally attached networks. |
| 741 | targets=: |
| 742 | eval ii=\$host_ifaces_$host |
| 743 | for i in $ii; do targets=$targets$i:; done |
| 744 | |
| 745 | ## Determine the transitivity. |
| 746 | seen=: |
| 747 | nets=$startnet |
| 748 | while :; do |
| 749 | |
| 750 | ## First pass. Determine whether any of the networks we're considering |
| 751 | ## are in the target set. If they are, then return the corresponding |
| 752 | ## interfaces. |
| 753 | found="" |
| 754 | for net in $nets; do |
| 755 | tg=$targets |
| 756 | while :; do |
| 757 | any=nil |
| 758 | case $tg in |
| 759 | *"=$net:"*) |
| 760 | n=${tg%=$net:*}; tg=${n%:*}:; n=${n##*:} |
| 761 | addword found $n |
| 762 | any=t |
| 763 | ;; |
| 764 | esac |
| 765 | case $any in nil) break ;; esac |
| 766 | done |
| 767 | done |
| 768 | case "$found" in ?*) echo $found; return ;; esac |
| 769 | |
| 770 | ## No joy. Determine the set of networks which (a) these ones can |
| 771 | ## forward to, and (b) that we've not considered already. These are the |
| 772 | ## nets we'll consider next time around. |
| 773 | nextnets="" |
| 774 | any=nil |
| 775 | for net in $nets; do |
| 776 | eval via=\$net_via_$net |
| 777 | for n in $via; do |
| 778 | case $seen in *":$n:"*) continue ;; esac |
| 779 | seen=$seen$n: |
| 780 | eval noxit=\$net_noxit_$n |
| 781 | case " $noxit " in *" $startnet "*) continue ;; esac |
| 782 | case " $nextnets " in |
| 783 | *" $n "*) ;; |
| 784 | *) addword nextnets $n; any=t ;; |
| 785 | esac |
| 786 | done |
| 787 | done |
| 788 | |
| 789 | ## If we've run out of networks then there's no reachability. Return a |
| 790 | ## failure. |
| 791 | case $any in nil) echo -; return ;; esac |
| 792 | nets=$nextnets |
| 793 | done |
| 794 | } |
| 795 | |
| 796 | m4_divert(-1) |
| 797 | ###----- That's all, folks -------------------------------------------------- |