Commit | Line | Data |
---|---|---|
775bd287 | 1 | ### -*-sh-*- |
bfdc045d MW |
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 | ||
ce79e94a MW |
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 | ||
beb4f0ee MW |
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 | ||
c70bfbbb MW |
75 | m4_divert(38)m4_dnl |
76 | ###-------------------------------------------------------------------------- | |
77 | ### Utility chains (used by function definitions). | |
78 | ||
a4d8cae3 | 79 | m4_divert(20)m4_dnl |
bfdc045d MW |
80 | ###-------------------------------------------------------------------------- |
81 | ### Basic chain constructions. | |
82 | ||
0291d6d5 MW |
83 | ## ip46tables ARGS ... |
84 | ## | |
85 | ## Do the same thing for `iptables' and `ip6tables'. | |
86 | ip46tables () { | |
87 | set -e | |
88 | iptables "$@" | |
89 | ip6tables "$@" | |
90 | } | |
91 | ||
bfdc045d MW |
92 | ## clearchain CHAIN CHAIN ... |
93 | ## | |
94 | ## Ensure that the named chains exist and are empty. | |
95 | clearchain () { | |
96 | set -e | |
7dde20fa MW |
97 | for _chain; do |
98 | case $_chain in | |
99 | *:*) table=${_chain%:*} _chain=${_chain#*:} ;; | |
bfdc045d MW |
100 | *) table=filter ;; |
101 | esac | |
7dde20fa | 102 | run ip46tables -t $table -N $_chain 2>/dev/null || : |
bfdc045d MW |
103 | done |
104 | } | |
105 | ||
9f3cffaa MW |
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 | |
45da078e MW |
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 | |
9f3cffaa MW |
125 | } |
126 | ||
bfdc045d MW |
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 | |
0291d6d5 | 139 | run ip46tables -t $table -A $chain -j LOG \ |
bfdc045d | 140 | -m limit --limit 3/minute --limit-burst 10 \ |
6fd217ae | 141 | --log-prefix "fw: $chain " --log-level notice || : |
a188f549 MW |
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 | |
bfdc045d MW |
145 | } |
146 | ||
a4d8cae3 | 147 | m4_divert(20)m4_dnl |
bfdc045d MW |
148 | ###-------------------------------------------------------------------------- |
149 | ### Basic option setting. | |
150 | ||
151 | ## setopt OPTION VALUE | |
152 | ## | |
153 | ## Set an IP sysctl. | |
154 | setopt () { | |
155 | set -e | |
0f6364ac MW |
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 | |
bfdc045d MW |
167 | } |
168 | ||
0f6364ac | 169 | ## setdevopt OPTION VALUE [INTERFACES ...] |
bfdc045d MW |
170 | ## |
171 | ## Set an IP interface-level sysctl. | |
172 | setdevopt () { | |
173 | set -e | |
0f6364ac MW |
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 | |
4224a17b | 194 | run sysctl -q net/$ver/conf/$i/$opt="$val" |
0f6364ac MW |
195 | fi |
196 | done | |
197 | case $any in | |
198 | nil) echo >&2 "$0: unknown device option $opt"; exit 1 ;; | |
199 | esac | |
bfdc045d MW |
200 | done |
201 | } | |
202 | ||
a4d8cae3 | 203 | m4_divert(20)m4_dnl |
bfdc045d MW |
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 | |
0291d6d5 | 213 | run ip46tables -A $chain -p tcp -m state \ |
bfdc045d | 214 | --state ESTABLISHED,RELATED -j ACCEPT |
0291d6d5 | 215 | run ip46tables -A $chain -p tcp ! --syn -g bad-tcp |
bfdc045d MW |
216 | } |
217 | ||
ecdca131 MW |
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 | |
c70bfbbb MW |
227 | ## fragmented. This is an extra pain for ip6tables, which doesn't provide |
228 | ## a pleasant way to detect non-initial fragments. | |
ecdca131 MW |
229 | run iptables -A $chain -p tcp -f -g tcp-fragment |
230 | run iptables -A $chain -f -j ACCEPT | |
0291d6d5 MW |
231 | run ip6tables -A $chain -p tcp -g tcp-fragment \ |
232 | -m ipv6header --soft --header frag | |
c70bfbbb | 233 | run ip6tables -A $chain -j accept-non-init-frag |
ecdca131 MW |
234 | } |
235 | ||
c70bfbbb MW |
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 | ||
a4d8cae3 | 244 | m4_divert(20)m4_dnl |
bfdc045d MW |
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 | *:*) | |
12ac65a1 | 256 | n=2 |
bfdc045d MW |
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 | *) | |
12ac65a1 | 263 | n=1 |
bfdc045d MW |
264 | case $svc in *[!0-9]*) eval svc=\$port_$svc ;; esac |
265 | ;; | |
266 | esac | |
267 | case $svc in | |
268 | *: | :* | "" | *[!0-9:]*) | |
12ac65a1 | 269 | echo >&2 "Bad service name" |
bfdc045d MW |
270 | exit 1 |
271 | ;; | |
272 | esac | |
273 | count=$(( $count + $n )) | |
274 | if [ $count -gt 15 ]; then | |
0291d6d5 | 275 | run ip46tables -A $chain -p $proto -m multiport -j ACCEPT \ |
bfdc045d MW |
276 | --destination-ports ${list#,} |
277 | list= count=$n | |
278 | fi | |
279 | list=$list,$svc | |
280 | done | |
281 | case $list in | |
282 | "") | |
283 | ;; | |
284 | ,*,*) | |
0291d6d5 | 285 | run ip46tables -A $chain -p $proto -m multiport -j ACCEPT \ |
bfdc045d MW |
286 | --destination-ports ${list#,} |
287 | ;; | |
12ac65a1 | 288 | *) |
0291d6d5 | 289 | run ip46tables -A $chain -p $proto -j ACCEPT \ |
bfdc045d MW |
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 | |
ace5a2fb MW |
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 | |
bfdc045d MW |
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 | |
0291d6d5 | 315 | run ip46tables -A $chain -j ACCEPT \ |
bfdc045d MW |
316 | -m state --state ESTABLISHED \ |
317 | -p $p --source-port 53 | |
318 | done | |
319 | } | |
320 | ||
7dde20fa MW |
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 | ||
bfdc045d MW |
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 | |
0291d6d5 MW |
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 | |
bfdc045d MW |
350 | } |
351 | ||
a4d8cae3 | 352 | m4_divert(20)m4_dnl |
bfdc045d MW |
353 | ###-------------------------------------------------------------------------- |
354 | ### Packet classification. | |
beb4f0ee MW |
355 | ### |
356 | ### See `classify.m4' for an explanation of how the firewall machinery for | |
357 | ### packet classification works. | |
358 | ### | |
359 | ### A list of all network names is kept in `allnets'. For each network NET, | |
360 | ### shell variables are defined describing their properties. | |
361 | ### | |
362 | ### net_class_NET The class of the network, as defined by | |
363 | ### `defnetclass'. | |
364 | ### net_inet_NET List of IPv4 address ranges in the network. | |
365 | ### net_inet6_NET List of IPv6 address ranges in the network. | |
17a45245 | 366 | ### net_via_NET List of other networks that this one forwards via. |
beb4f0ee MW |
367 | ### net_hosts_NET List of hosts known to be in the network. |
368 | ### host_inet_HOST IPv4 address of the named HOST. | |
369 | ### host_inet6_HOST IPv6 address of the named HOST. | |
370 | ### | |
371 | ### Similarly, a list of hosts is kept in `allhosts', and for each host HOST, | |
372 | ### a shell variables are defined: | |
373 | ### | |
374 | ### host_ifaces_HOST List of interfaces for this host and the networks | |
375 | ### they attach to, in the form IFACE=NET. | |
bfdc045d MW |
376 | |
377 | ## defbitfield NAME WIDTH | |
378 | ## | |
379 | ## Defines MASK_NAME and BIT_NAME symbolic constants for dealing with | |
380 | ## bitfields: x << BIT_NAME yields the value x in the correct position, and | |
381 | ## ff & MASK_NAME extracts the corresponding value. | |
382 | defbitfield () { | |
383 | set -e | |
384 | name=$1 width=$2 | |
385 | eval MASK_$name=$(( (1 << $width) - 1 << $bitindex )) | |
386 | eval BIT_$name=$bitindex | |
387 | bitindex=$(( $bitindex + $width )) | |
388 | } | |
389 | ||
390 | ## Define the layout of the bitfield. | |
391 | bitindex=0 | |
392 | defbitfield MASK 16 | |
393 | defbitfield FROM 4 | |
394 | defbitfield TO 4 | |
395 | ||
396 | ## defnetclass NAME FORWARD-TO... | |
397 | ## | |
398 | ## Defines a netclass called NAME, which is allowed to forward to the | |
399 | ## FORWARD-TO netclasses. | |
400 | ## | |
401 | ## For each netclass, constants from_NAME and to_NAME are defined as the | |
402 | ## appropriate values in the FROM and TO fields (i.e., not including any mask | |
403 | ## bits). | |
404 | ## | |
405 | ## This function also establishes mangle chains mark-from-NAME and | |
406 | ## mark-to-NAME for applying the appropriate mark bits to the packet. | |
407 | ## | |
408 | ## Because it needs to resolve forward references, netclasses must be defined | |
409 | ## in a two-pass manner, using a loop of the form | |
410 | ## | |
411 | ## for pass in 1 2; do netclassindex=0; ...; done | |
412 | netclassess= | |
413 | defnetclass () { | |
414 | set -e | |
415 | name=$1; shift | |
416 | case $pass in | |
417 | 1) | |
418 | ||
419 | ## Pass 1. Establish the from_NAME and to_NAME constants, and the | |
420 | ## netclass's mask bit. | |
16838f59 | 421 | trace "netclass $name = $netclassindex" |
bfdc045d MW |
422 | eval from_$name=$(( $netclassindex << $BIT_FROM )) |
423 | eval to_$name=$(( $netclassindex << $BIT_TO )) | |
3b250fe6 | 424 | eval fwd_$name=$(( 1 << ($netclassindex + $BIT_MASK) )) |
bfdc045d MW |
425 | nets="$nets $name" |
426 | ;; | |
427 | 2) | |
428 | ||
1850991d MW |
429 | ## Pass 2. Compute the actual from and to values. This is fiddly: |
430 | ## we want to preserve the other flags. | |
431 | from=$(( ($netclassindex << $BIT_FROM) )) | |
432 | frommask=$(( $MASK_FROM | $MASK_MASK )) | |
bfdc045d | 433 | for net; do |
3b250fe6 | 434 | eval bit=\$fwd_$net |
bfdc045d MW |
435 | from=$(( $from + $bit )) |
436 | done | |
1850991d | 437 | to=$(( ($netclassindex << $BIT_TO) )) |
3b0f3dd8 | 438 | tomask=$(( $MASK_TO | $MASK_MASK ^ (1 << ($netclassindex + $BIT_MASK)) )) |
1850991d | 439 | trace "from $name --> set $(printf %08x/%08x $from $frommask)" |
3b0f3dd8 | 440 | trace " to $name --> set $(printf %08x/%08x $to $tomask)" |
bfdc045d MW |
441 | |
442 | ## Now establish the mark-from-NAME and mark-to-NAME chains. | |
443 | clearchain mangle:mark-from-$name mangle:mark-to-$name | |
1850991d MW |
444 | run ip46tables -t mangle -A mark-from-$name -j MARK \ |
445 | --set-xmark $from/$frommask | |
446 | run ip46tables -t mangle -A mark-to-$name -j MARK \ | |
447 | --set-xmark $to/$tomask | |
bfdc045d MW |
448 | ;; |
449 | esac | |
450 | netclassindex=$(( $netclassindex + 1 )) | |
451 | } | |
452 | ||
beb4f0ee MW |
453 | ## defnet NET CLASS |
454 | ## | |
17a45245 | 455 | ## Define a network. Follow by calls to `addr', `via', etc. to define |
beb4f0ee MW |
456 | ## properties of the network. Networks are processed in order, so if their |
457 | ## addresses overlap then the more specific addresses should be defined | |
458 | ## earlier. | |
459 | defnet () { | |
460 | net=$1 class=$2 | |
461 | addword allnets $net | |
462 | eval net_class_$1=\$class | |
463 | } | |
464 | ||
465 | ## addr ADDRESS/LEN ... | |
466 | ## | |
467 | ## Define addresses for the network being defined. ADDRESSes are in | |
468 | ## colon-separated IPv6 or dotted-quad IPv4 form. | |
469 | addr () { | |
470 | for i in "$@"; do | |
471 | case "$i" in | |
472 | *:*) addword net_inet6_$net $i ;; | |
473 | *) addword net_inet_$net $i ;; | |
bfdc045d MW |
474 | esac |
475 | done | |
476 | } | |
477 | ||
17a45245 | 478 | ## via NET ... |
bfdc045d | 479 | ## |
beb4f0ee | 480 | ## Declare that packets from this network are forwarded to the other NETs. |
17a45245 MW |
481 | via () { |
482 | eval "net_via_$net=\"$*\"" | |
beb4f0ee MW |
483 | } |
484 | ||
485 | ## noxit NET ... | |
486 | ## | |
487 | ## Declare that packets from this network must not be forwarded to the other | |
488 | ## NETs. | |
489 | noxit () { | |
490 | eval "net_noxit_$net=\"$*\"" | |
491 | } | |
492 | ||
493 | ## host HOST ADDR ... | |
494 | ## | |
495 | ## Define the address of an individual host on the current network. The | |
496 | ## ADDRs may be full IPv4 or IPv6 addresses, or offsets from the containing | |
497 | ## network address, which is a simple number for IPv4, or a suffix beginning | |
498 | ## with `::' for IPv6. If an IPv6 base address is provided for the network | |
499 | ## but not for the host then the host's IPv4 address is used as a suffix. | |
500 | host () { | |
501 | name=$1; shift | |
502 | ||
503 | ## Work out which addresses we've actually been given. | |
504 | unset a6 | |
505 | for i in "$@"; do | |
506 | case "$i" in ::*) a6=$i ;; *) a=$i ;; esac | |
507 | done | |
508 | case "${a+t}" in | |
509 | t) ;; | |
510 | *) echo >&2 "$0: no address for $name"; exit 1 ;; | |
bfdc045d | 511 | esac |
beb4f0ee MW |
512 | case "${a6+t}" in t) ;; *) a6=::$a ;; esac |
513 | ||
514 | ## Work out the IPv4 address. | |
515 | eval nn=\$net_inet_$net | |
516 | for n in $nn; do | |
517 | addr=${n%/*} | |
518 | base=${addr%.*} | |
519 | offset=${addr##*.} | |
520 | case $a in *.*) aa=$a ;; *) aa=$base.$(( $offset + $a )) ;; esac | |
521 | eval host_inet_$name=$aa | |
522 | done | |
523 | ||
524 | ## Work out the IPv6 address. | |
525 | eval nn=\$net_inet6_$net | |
526 | for n in $nn; do | |
527 | addr=${n%/*} | |
528 | base=${addr%::*} | |
1710944b | 529 | case $a6 in ::*) aa=$base$a6 ;; *) aa=$a6 ;; esac |
beb4f0ee MW |
530 | eval host_inet6_$name=$aa |
531 | done | |
532 | ||
533 | ## Remember the host in the list. | |
534 | addword net_hosts_$net $name | |
535 | } | |
536 | ||
537 | ## defhost NAME | |
538 | ## | |
539 | ## Define a new host. Follow by calls to `iface' to define the host's | |
540 | ## interfaces. | |
541 | defhost () { | |
542 | host=$1 | |
543 | addword allhosts $host | |
4eb9f4df | 544 | eval host_type_$host=server |
beb4f0ee MW |
545 | } |
546 | ||
4eb9f4df | 547 | ## hosttype TYPE |
beb4f0ee | 548 | ## |
4eb9f4df MW |
549 | ## Declare the host to have the given type. |
550 | hosttype () { | |
551 | type=$1 | |
552 | case $type in | |
553 | router | server | client) ;; | |
554 | *) echo >&2 "$0: bad host type \`$type'"; exit 1 ;; | |
555 | esac | |
556 | eval host_type_$host=$type | |
beb4f0ee MW |
557 | } |
558 | ||
559 | ## iface IFACE NET ... | |
560 | ## | |
561 | ## Define a host's interfaces. Specifically, declares that the host has an | |
562 | ## interface IFACE attached to the listed NETs. | |
563 | iface () { | |
564 | name=$1; shift | |
565 | for net in "$@"; do | |
566 | addword host_ifaces_$host $name=$net | |
567 | done | |
568 | } | |
569 | ||
11732033 MW |
570 | ## matchnets OPT WIN FLAGS PREPARE BASE SUFFIX NEXT NET [NET ...] |
571 | ## | |
1264e917 | 572 | ## Build rules which match a particular collection of networks. |
11732033 | 573 | ## |
1264e917 | 574 | ## Specifically, use the address-comparison operator OPT (typically `-s' or |
11732033 MW |
575 | ## `-d') to match the addresses of each NET, writing the rules to the chain |
576 | ## BASESUFFIX. If we find a match, dispatch to WIN-CLASS, where CLASS is the | |
577 | ## class of the matching network. In order to deal with networks containing | |
578 | ## negative address ranges, more chains may need to be constructed; they will | |
579 | ## be named BASE#Q for sequence numbers Q starting with NEXT. All of this | |
580 | ## happens on the `mangle' table, and there isn't (currently) a way to tweak | |
581 | ## this. | |
1264e917 MW |
582 | ## |
583 | ## The FLAGS gather additional interesting information about the job, | |
584 | ## separated by colons. The only flag currently is :default: which means | |
585 | ## that the default network was listed. | |
586 | ## | |
587 | ## Finally, there is a hook PREPARE which is called just in advance of | |
588 | ## processing the final network, passing it the argument FLAGS. (The PREPARE | |
589 | ## string will be subjected to shell word-splitting, so it can provide some | |
590 | ## arguments of its own if it wants.) It should set `mode' to indicate how | |
591 | ## the chain should be finished. | |
592 | ## | |
593 | ## goto If no networks matched, then issue a final `goto' to the | |
594 | ## chain named by the variable `fail'. | |
595 | ## | |
596 | ## call Run `$finish CHAIN' to write final rules to the named CHAIN | |
597 | ## (which may be suffixed from the original BASE argument if | |
598 | ## this was necessary). This function will arrange to call | |
599 | ## these rules if no networks match. | |
600 | ## | |
601 | ## ret If no network matches then return (maybe by falling off the | |
602 | ## end of the chain). | |
603 | matchnets () { | |
604 | local opt win flags prepare base suffix next net lose splitp | |
605 | opt=$1 win=$2 flags=$3 prepare=$4 base=$5 suffix=$6 next=$7 net=$8 | |
606 | shift 8 | |
607 | ||
608 | ## If this is the default network, then set the flag. | |
609 | case "$net" in default) flags=${flags}default: ;; esac | |
610 | ||
611 | ## Do an initial pass over the addresses to see whether there are any | |
612 | ## negative ranges. If so, we'll need to split. See also the standard | |
613 | ## joke about soup. | |
614 | splitp=nil | |
615 | eval "addrs=\"\$net_inet_$net \$net_inet6_$net\"" | |
616 | for a in $addrs; do case $a in !*) splitp=t; break ;; esac; done | |
617 | ||
618 | trace "MATCHNETS [splitp $splitp] $opt $win $flags [$prepare] $base $suffix $next : $net $*" | |
619 | ||
620 | ## Work out how to handle matches against negative address ranges. If this | |
621 | ## is the last network, invoke the PREPARE hook to find out. Otherwise, if | |
622 | ## we have to split the chain, recursively build the target here. | |
623 | case $splitp,$# in | |
624 | t,0 | nil,0) | |
625 | $prepare $flags | |
626 | case $splitp,$mode in | |
627 | *,goto) | |
628 | lose="-g $fail" | |
629 | ;; | |
630 | *,ret) | |
631 | lose="-j RETURN" | |
632 | ;; | |
633 | t,call) | |
634 | clearchain mangle:$base#$next | |
635 | lose="-g $base#$next" | |
636 | ;; | |
637 | nil,call) | |
638 | ;; | |
639 | esac | |
640 | ;; | |
641 | t,*) | |
642 | clearchain mangle:$base#$next | |
643 | matchnets $opt $win $flags "$prepare" \ | |
644 | $base \#$next $(( $next + 1 )) "$@" | |
645 | lose="-g $base#$next" mode=goto | |
646 | ;; | |
647 | *) | |
648 | mode=continue | |
649 | ;; | |
650 | esac | |
651 | ||
652 | ## Populate the chain with rules to match the necessary networks. | |
653 | eval addr=\$net_inet_$net addr6=\$net_inet6_$net class=\$net_class_$net | |
654 | for a in $addr; do | |
655 | case $a in | |
656 | !*) run iptables -t mangle -A $base$suffix $lose $opt ${a#!} ;; | |
657 | *) run iptables -t mangle -A $base$suffix -g $win-$class $opt $a ;; | |
658 | esac | |
659 | done | |
660 | for a in $addr6; do | |
661 | case $a in | |
662 | !*) run ip6tables -t mangle -A $base$suffix $lose $opt ${a#!} ;; | |
663 | *) run ip6tables -t mangle -A $base$suffix -g $win-$class $opt $a ;; | |
664 | esac | |
665 | done | |
666 | ||
667 | ## Wrap up the chain appropriately. If we didn't split and there are more | |
668 | ## networks to handle then append the necessary rules now. (If we did | |
669 | ## split, then we already wrote the rules for them above.) If there are no | |
670 | ## more networks then consult the `mode' setting to find out what to do. | |
671 | case $splitp,$#,$mode in | |
672 | *,0,ret) ;; | |
673 | *,*,goto) run ip46tables -t mangle -A $base$suffix $lose ;; | |
674 | t,0,call) $finish $base#$next ;; | |
675 | nil,0,call) $finish $base$suffix ;; | |
676 | nil,*,*) | |
677 | matchnets $opt $win $flags "$prepare" $base "$suffix" $next "$@" | |
678 | ;; | |
679 | esac | |
680 | } | |
681 | ||
beb4f0ee MW |
682 | ## net_interfaces HOST NET |
683 | ## | |
684 | ## Determine the interfaces on which packets may plausibly arrive from the | |
685 | ## named NET. Returns `-' if no such interface exists. | |
686 | ## | |
687 | ## This algorithm is not very clever. It's just about barely good enough to | |
688 | ## deduce transitivity through a simple routed network; with complicated | |
689 | ## networks, it will undoubtedly give wrong answers. Check the results | |
690 | ## carefully, and, if necessary, list the connectivity explicitly; use the | |
691 | ## special interface `-' for networks you know shouldn't send packets to a | |
692 | ## host. | |
693 | net_interfaces () { | |
694 | host=$1 startnet=$2 | |
695 | ||
696 | ## Determine the locally attached networks. | |
697 | targets=: | |
698 | eval ii=\$host_ifaces_$host | |
699 | for i in $ii; do targets=$targets$i:; done | |
700 | ||
701 | ## Determine the transitivity. | |
702 | seen=: | |
703 | nets=$startnet | |
704 | while :; do | |
705 | ||
706 | ## First pass. Determine whether any of the networks we're considering | |
707 | ## are in the target set. If they are, then return the corresponding | |
708 | ## interfaces. | |
709 | found="" | |
710 | for net in $nets; do | |
711 | tg=$targets | |
712 | while :; do | |
713 | any=nil | |
714 | case $tg in | |
715 | *"=$net:"*) | |
716 | n=${tg%=$net:*}; tg=${n%:*}:; n=${n##*:} | |
717 | addword found $n | |
718 | any=t | |
719 | ;; | |
720 | esac | |
721 | case $any in nil) break ;; esac | |
722 | done | |
723 | done | |
724 | case "$found" in ?*) echo $found; return ;; esac | |
725 | ||
726 | ## No joy. Determine the set of networks which (a) these ones can | |
727 | ## forward to, and (b) that we've not considered already. These are the | |
728 | ## nets we'll consider next time around. | |
729 | nextnets="" | |
730 | any=nil | |
731 | for net in $nets; do | |
17a45245 MW |
732 | eval via=\$net_via_$net |
733 | for n in $via; do | |
beb4f0ee MW |
734 | case $seen in *":$n:"*) continue ;; esac |
735 | seen=$seen$n: | |
736 | eval noxit=\$net_noxit_$n | |
737 | case " $noxit " in *" $startnet "*) continue ;; esac | |
738 | case " $nextnets " in | |
739 | *" $n "*) ;; | |
740 | *) addword nextnets $n; any=t ;; | |
741 | esac | |
742 | done | |
743 | done | |
744 | ||
745 | ## If we've run out of networks then there's no reachability. Return a | |
746 | ## failure. | |
747 | case $any in nil) echo -; return ;; esac | |
748 | nets=$nextnets | |
749 | done | |
bfdc045d MW |
750 | } |
751 | ||
752 | m4_divert(-1) | |
753 | ###----- That's all, folks -------------------------------------------------- |