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 | ||
d052f343 MW |
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 | ||
a4d8cae3 | 396 | m4_divert(20)m4_dnl |
bfdc045d MW |
397 | ###-------------------------------------------------------------------------- |
398 | ### Packet classification. | |
beb4f0ee MW |
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. | |
17a45245 | 410 | ### net_via_NET List of other networks that this one forwards via. |
beb4f0ee MW |
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. | |
bfdc045d MW |
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. | |
16838f59 | 465 | trace "netclass $name = $netclassindex" |
bfdc045d MW |
466 | eval from_$name=$(( $netclassindex << $BIT_FROM )) |
467 | eval to_$name=$(( $netclassindex << $BIT_TO )) | |
3b250fe6 | 468 | eval fwd_$name=$(( 1 << ($netclassindex + $BIT_MASK) )) |
bfdc045d MW |
469 | nets="$nets $name" |
470 | ;; | |
471 | 2) | |
472 | ||
1850991d MW |
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 )) | |
bfdc045d | 477 | for net; do |
3b250fe6 | 478 | eval bit=\$fwd_$net |
bfdc045d MW |
479 | from=$(( $from + $bit )) |
480 | done | |
1850991d | 481 | to=$(( ($netclassindex << $BIT_TO) )) |
3b0f3dd8 | 482 | tomask=$(( $MASK_TO | $MASK_MASK ^ (1 << ($netclassindex + $BIT_MASK)) )) |
1850991d | 483 | trace "from $name --> set $(printf %08x/%08x $from $frommask)" |
3b0f3dd8 | 484 | trace " to $name --> set $(printf %08x/%08x $to $tomask)" |
bfdc045d MW |
485 | |
486 | ## Now establish the mark-from-NAME and mark-to-NAME chains. | |
487 | clearchain mangle:mark-from-$name mangle:mark-to-$name | |
1850991d MW |
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 | |
bfdc045d MW |
492 | ;; |
493 | esac | |
494 | netclassindex=$(( $netclassindex + 1 )) | |
495 | } | |
496 | ||
beb4f0ee MW |
497 | ## defnet NET CLASS |
498 | ## | |
17a45245 | 499 | ## Define a network. Follow by calls to `addr', `via', etc. to define |
beb4f0ee MW |
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 ;; | |
bfdc045d MW |
518 | esac |
519 | done | |
520 | } | |
521 | ||
17a45245 | 522 | ## via NET ... |
bfdc045d | 523 | ## |
beb4f0ee | 524 | ## Declare that packets from this network are forwarded to the other NETs. |
17a45245 MW |
525 | via () { |
526 | eval "net_via_$net=\"$*\"" | |
beb4f0ee MW |
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 ;; | |
bfdc045d | 555 | esac |
beb4f0ee MW |
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%::*} | |
1710944b | 573 | case $a6 in ::*) aa=$base$a6 ;; *) aa=$a6 ;; esac |
beb4f0ee MW |
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 | |
4eb9f4df | 588 | eval host_type_$host=server |
beb4f0ee MW |
589 | } |
590 | ||
4eb9f4df | 591 | ## hosttype TYPE |
beb4f0ee | 592 | ## |
4eb9f4df MW |
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 | |
beb4f0ee MW |
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 | ||
11732033 MW |
614 | ## matchnets OPT WIN FLAGS PREPARE BASE SUFFIX NEXT NET [NET ...] |
615 | ## | |
1264e917 | 616 | ## Build rules which match a particular collection of networks. |
11732033 | 617 | ## |
1264e917 | 618 | ## Specifically, use the address-comparison operator OPT (typically `-s' or |
11732033 MW |
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. | |
1264e917 MW |
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 | ||
beb4f0ee MW |
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 | |
17a45245 MW |
776 | eval via=\$net_via_$net |
777 | for n in $via; do | |
beb4f0ee MW |
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 | |
bfdc045d MW |
794 | } |
795 | ||
796 | m4_divert(-1) | |
797 | ###----- That's all, folks -------------------------------------------------- |