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 | |
3596231a MW |
242 | run ip6tables -A accept-non-init-frag -j ACCEPT \ |
243 | -m ipv6header --header frag | |
c70bfbbb | 244 | |
a4d8cae3 | 245 | m4_divert(20)m4_dnl |
bfdc045d MW |
246 | ## allowservices CHAIN PROTO SERVICE ... |
247 | ## | |
248 | ## Add rules to allow the SERVICES on the CHAIN. | |
249 | allowservices () { | |
250 | set -e | |
251 | chain=$1 proto=$2; shift 2 | |
252 | count=0 | |
253 | list= | |
254 | for svc; do | |
255 | case $svc in | |
256 | *:*) | |
12ac65a1 | 257 | n=2 |
bfdc045d MW |
258 | left=${svc%:*} right=${svc#*:} |
259 | case $left in *[!0-9]*) eval left=\$port_$left ;; esac | |
260 | case $right in *[!0-9]*) eval right=\$port_$right ;; esac | |
261 | svc=$left:$right | |
262 | ;; | |
263 | *) | |
12ac65a1 | 264 | n=1 |
bfdc045d MW |
265 | case $svc in *[!0-9]*) eval svc=\$port_$svc ;; esac |
266 | ;; | |
267 | esac | |
268 | case $svc in | |
269 | *: | :* | "" | *[!0-9:]*) | |
12ac65a1 | 270 | echo >&2 "Bad service name" |
bfdc045d MW |
271 | exit 1 |
272 | ;; | |
273 | esac | |
274 | count=$(( $count + $n )) | |
275 | if [ $count -gt 15 ]; then | |
0291d6d5 | 276 | run ip46tables -A $chain -p $proto -m multiport -j ACCEPT \ |
bfdc045d MW |
277 | --destination-ports ${list#,} |
278 | list= count=$n | |
279 | fi | |
280 | list=$list,$svc | |
281 | done | |
282 | case $list in | |
283 | "") | |
284 | ;; | |
285 | ,*,*) | |
0291d6d5 | 286 | run ip46tables -A $chain -p $proto -m multiport -j ACCEPT \ |
bfdc045d MW |
287 | --destination-ports ${list#,} |
288 | ;; | |
12ac65a1 | 289 | *) |
0291d6d5 | 290 | run ip46tables -A $chain -p $proto -j ACCEPT \ |
bfdc045d MW |
291 | --destination-port ${list#,} |
292 | ;; | |
293 | esac | |
294 | } | |
295 | ||
296 | ## ntpclient CHAIN NTPSERVER ... | |
297 | ## | |
298 | ## Add rules to CHAIN to allow NTP with NTPSERVERs. | |
299 | ntpclient () { | |
300 | set -e | |
ace5a2fb MW |
301 | ntpchain=$1; shift |
302 | ||
303 | clearchain ntp-servers | |
2b5ca003 MW |
304 | for ntp; do |
305 | case $ntp in *:*) ipt=ip6tables ;; *) ipt=iptables ;; esac | |
306 | run $ipt -A ntp-servers -j ACCEPT -s $ntp; | |
307 | done | |
308 | run ip46tables -A $ntpchain -j ntp-servers \ | |
ace5a2fb | 309 | -p udp --source-port 123 --destination-port 123 |
bfdc045d MW |
310 | } |
311 | ||
312 | ## dnsresolver CHAIN | |
313 | ## | |
314 | ## Add rules to allow CHAIN to be a DNS resolver. | |
315 | dnsresolver () { | |
316 | set -e | |
317 | chain=$1 | |
318 | for p in tcp udp; do | |
0291d6d5 | 319 | run ip46tables -A $chain -j ACCEPT \ |
bfdc045d MW |
320 | -m state --state ESTABLISHED \ |
321 | -p $p --source-port 53 | |
322 | done | |
323 | } | |
324 | ||
7dde20fa MW |
325 | ## dnsserver CHAIN |
326 | ## | |
327 | ## Add rules to allow CHAIN to be a DNS server. | |
328 | dnsserver () { | |
329 | set -e | |
330 | chain=$1 | |
331 | ||
332 | ## Allow TCP access. Hitting us with SYNs will make us deploy SYN cookies, | |
333 | ## but that's tolerable. | |
334 | run ip46tables -A $chain -j ACCEPT -p tcp --destination-port 53 | |
335 | ||
336 | ## Avoid being a DDoS amplifier by rate-limiting incoming DNS queries. | |
337 | clearchain $chain-udp-dns | |
338 | run ip46tables -A $chain-udp-dns -j ACCEPT \ | |
339 | -m limit --limit 20/second --limit-burst 300 | |
340 | run ip46tables -A $chain-udp-dns -g dns-rate-limit | |
341 | run ip46tables -A $chain -j $chain-udp-dns \ | |
342 | -p udp --destination-port 53 | |
343 | } | |
344 | ||
bfdc045d MW |
345 | ## openports CHAIN [MIN MAX] |
346 | ## | |
347 | ## Add rules to CHAIN to allow the open ports. | |
348 | openports () { | |
349 | set -e | |
350 | chain=$1; shift | |
351 | [ $# -eq 0 ] && set -- $open_port_min $open_port_max | |
0291d6d5 MW |
352 | run ip46tables -A $chain -p tcp -g interesting --destination-port $1:$2 |
353 | run ip46tables -A $chain -p udp -g interesting --destination-port $1:$2 | |
bfdc045d MW |
354 | } |
355 | ||
d052f343 MW |
356 | bcp38_setup=: |
357 | bcp38 () { | |
358 | ipv=$1 ifname=$2; shift 2 | |
359 | ## Add rules for BCP38 egress filtering for IP version IPV (either 4 or 6). | |
360 | ## IFNAME is the outgoing interface; the remaining arguments are network | |
361 | ## prefixes. | |
362 | ||
363 | ## Sort out which command we're using | |
364 | case $ipv in | |
365 | 4) ipt=iptables ;; | |
366 | 6) ipt=ip6tables ;; | |
367 | *) echo >&2 "Unknown IP version $ipv"; exit 1 ;; | |
368 | esac | |
369 | ||
370 | ## If we've not set up the error chain then do that. | |
371 | case $bcp38_setup in | |
372 | :) | |
373 | errorchain bcp38 DROP | |
374 | clearchain bcp38-check | |
375 | ip46tables -A bcp38-check -g bcp38 | |
376 | ;; | |
377 | esac | |
378 | ||
379 | ## Stitch our egress filter into the outbound chains if we haven't done | |
380 | ## that yet. Do this for both IP versions: if we're only ever given | |
381 | ## IPv6 addresses for a particular interface then we assume that IPv4 | |
382 | ## packets aren't allowed on it at all. | |
383 | case $bcp38_setup in | |
384 | *:$ifname:*) ;; | |
385 | *) | |
386 | run ip46tables -A OUTPUT -j bcp38-check -o $ifname | |
387 | case $forward in | |
388 | 1) run ip46tables -A FORWARD -j bcp38-check -o $ifname ;; | |
389 | esac | |
390 | bcp38_setup=$bcp38_setup$ifname: | |
391 | ;; | |
392 | esac | |
393 | ||
394 | ## Finally, add in our allowed networks. | |
395 | for i in "$@"; do | |
396 | run $ipt -I bcp38-check -j RETURN -s $i | |
397 | done | |
398 | } | |
399 | ||
a4d8cae3 | 400 | m4_divert(20)m4_dnl |
bfdc045d MW |
401 | ###-------------------------------------------------------------------------- |
402 | ### Packet classification. | |
beb4f0ee MW |
403 | ### |
404 | ### See `classify.m4' for an explanation of how the firewall machinery for | |
405 | ### packet classification works. | |
406 | ### | |
407 | ### A list of all network names is kept in `allnets'. For each network NET, | |
408 | ### shell variables are defined describing their properties. | |
409 | ### | |
410 | ### net_class_NET The class of the network, as defined by | |
411 | ### `defnetclass'. | |
412 | ### net_inet_NET List of IPv4 address ranges in the network. | |
413 | ### net_inet6_NET List of IPv6 address ranges in the network. | |
17a45245 | 414 | ### net_via_NET List of other networks that this one forwards via. |
beb4f0ee MW |
415 | ### net_hosts_NET List of hosts known to be in the network. |
416 | ### host_inet_HOST IPv4 address of the named HOST. | |
417 | ### host_inet6_HOST IPv6 address of the named HOST. | |
418 | ### | |
419 | ### Similarly, a list of hosts is kept in `allhosts', and for each host HOST, | |
420 | ### a shell variables are defined: | |
421 | ### | |
422 | ### host_ifaces_HOST List of interfaces for this host and the networks | |
423 | ### they attach to, in the form IFACE=NET. | |
bfdc045d MW |
424 | |
425 | ## defbitfield NAME WIDTH | |
426 | ## | |
427 | ## Defines MASK_NAME and BIT_NAME symbolic constants for dealing with | |
428 | ## bitfields: x << BIT_NAME yields the value x in the correct position, and | |
429 | ## ff & MASK_NAME extracts the corresponding value. | |
430 | defbitfield () { | |
431 | set -e | |
432 | name=$1 width=$2 | |
433 | eval MASK_$name=$(( (1 << $width) - 1 << $bitindex )) | |
434 | eval BIT_$name=$bitindex | |
435 | bitindex=$(( $bitindex + $width )) | |
436 | } | |
437 | ||
438 | ## Define the layout of the bitfield. | |
439 | bitindex=0 | |
440 | defbitfield MASK 16 | |
441 | defbitfield FROM 4 | |
442 | defbitfield TO 4 | |
443 | ||
444 | ## defnetclass NAME FORWARD-TO... | |
445 | ## | |
446 | ## Defines a netclass called NAME, which is allowed to forward to the | |
447 | ## FORWARD-TO netclasses. | |
448 | ## | |
449 | ## For each netclass, constants from_NAME and to_NAME are defined as the | |
450 | ## appropriate values in the FROM and TO fields (i.e., not including any mask | |
451 | ## bits). | |
452 | ## | |
453 | ## This function also establishes mangle chains mark-from-NAME and | |
454 | ## mark-to-NAME for applying the appropriate mark bits to the packet. | |
455 | ## | |
456 | ## Because it needs to resolve forward references, netclasses must be defined | |
457 | ## in a two-pass manner, using a loop of the form | |
458 | ## | |
459 | ## for pass in 1 2; do netclassindex=0; ...; done | |
460 | netclassess= | |
461 | defnetclass () { | |
462 | set -e | |
463 | name=$1; shift | |
464 | case $pass in | |
465 | 1) | |
466 | ||
467 | ## Pass 1. Establish the from_NAME and to_NAME constants, and the | |
468 | ## netclass's mask bit. | |
16838f59 | 469 | trace "netclass $name = $netclassindex" |
bfdc045d MW |
470 | eval from_$name=$(( $netclassindex << $BIT_FROM )) |
471 | eval to_$name=$(( $netclassindex << $BIT_TO )) | |
3b250fe6 | 472 | eval fwd_$name=$(( 1 << ($netclassindex + $BIT_MASK) )) |
bfdc045d MW |
473 | nets="$nets $name" |
474 | ;; | |
475 | 2) | |
476 | ||
1850991d MW |
477 | ## Pass 2. Compute the actual from and to values. This is fiddly: |
478 | ## we want to preserve the other flags. | |
479 | from=$(( ($netclassindex << $BIT_FROM) )) | |
480 | frommask=$(( $MASK_FROM | $MASK_MASK )) | |
bfdc045d | 481 | for net; do |
3b250fe6 | 482 | eval bit=\$fwd_$net |
bfdc045d MW |
483 | from=$(( $from + $bit )) |
484 | done | |
1850991d | 485 | to=$(( ($netclassindex << $BIT_TO) )) |
3b0f3dd8 | 486 | tomask=$(( $MASK_TO | $MASK_MASK ^ (1 << ($netclassindex + $BIT_MASK)) )) |
1850991d | 487 | trace "from $name --> set $(printf %08x/%08x $from $frommask)" |
3b0f3dd8 | 488 | trace " to $name --> set $(printf %08x/%08x $to $tomask)" |
bfdc045d MW |
489 | |
490 | ## Now establish the mark-from-NAME and mark-to-NAME chains. | |
491 | clearchain mangle:mark-from-$name mangle:mark-to-$name | |
1850991d MW |
492 | run ip46tables -t mangle -A mark-from-$name -j MARK \ |
493 | --set-xmark $from/$frommask | |
494 | run ip46tables -t mangle -A mark-to-$name -j MARK \ | |
495 | --set-xmark $to/$tomask | |
bfdc045d MW |
496 | ;; |
497 | esac | |
498 | netclassindex=$(( $netclassindex + 1 )) | |
499 | } | |
500 | ||
beb4f0ee MW |
501 | ## defnet NET CLASS |
502 | ## | |
17a45245 | 503 | ## Define a network. Follow by calls to `addr', `via', etc. to define |
beb4f0ee MW |
504 | ## properties of the network. Networks are processed in order, so if their |
505 | ## addresses overlap then the more specific addresses should be defined | |
506 | ## earlier. | |
507 | defnet () { | |
508 | net=$1 class=$2 | |
509 | addword allnets $net | |
510 | eval net_class_$1=\$class | |
511 | } | |
512 | ||
513 | ## addr ADDRESS/LEN ... | |
514 | ## | |
515 | ## Define addresses for the network being defined. ADDRESSes are in | |
516 | ## colon-separated IPv6 or dotted-quad IPv4 form. | |
517 | addr () { | |
518 | for i in "$@"; do | |
519 | case "$i" in | |
520 | *:*) addword net_inet6_$net $i ;; | |
521 | *) addword net_inet_$net $i ;; | |
bfdc045d MW |
522 | esac |
523 | done | |
524 | } | |
525 | ||
17a45245 | 526 | ## via NET ... |
bfdc045d | 527 | ## |
beb4f0ee | 528 | ## Declare that packets from this network are forwarded to the other NETs. |
17a45245 MW |
529 | via () { |
530 | eval "net_via_$net=\"$*\"" | |
beb4f0ee MW |
531 | } |
532 | ||
533 | ## noxit NET ... | |
534 | ## | |
535 | ## Declare that packets from this network must not be forwarded to the other | |
536 | ## NETs. | |
537 | noxit () { | |
538 | eval "net_noxit_$net=\"$*\"" | |
539 | } | |
540 | ||
541 | ## host HOST ADDR ... | |
542 | ## | |
543 | ## Define the address of an individual host on the current network. The | |
544 | ## ADDRs may be full IPv4 or IPv6 addresses, or offsets from the containing | |
545 | ## network address, which is a simple number for IPv4, or a suffix beginning | |
546 | ## with `::' for IPv6. If an IPv6 base address is provided for the network | |
547 | ## but not for the host then the host's IPv4 address is used as a suffix. | |
548 | host () { | |
549 | name=$1; shift | |
550 | ||
551 | ## Work out which addresses we've actually been given. | |
552 | unset a6 | |
553 | for i in "$@"; do | |
554 | case "$i" in ::*) a6=$i ;; *) a=$i ;; esac | |
555 | done | |
556 | case "${a+t}" in | |
557 | t) ;; | |
558 | *) echo >&2 "$0: no address for $name"; exit 1 ;; | |
bfdc045d | 559 | esac |
beb4f0ee MW |
560 | case "${a6+t}" in t) ;; *) a6=::$a ;; esac |
561 | ||
562 | ## Work out the IPv4 address. | |
563 | eval nn=\$net_inet_$net | |
564 | for n in $nn; do | |
565 | addr=${n%/*} | |
566 | base=${addr%.*} | |
567 | offset=${addr##*.} | |
568 | case $a in *.*) aa=$a ;; *) aa=$base.$(( $offset + $a )) ;; esac | |
569 | eval host_inet_$name=$aa | |
570 | done | |
571 | ||
572 | ## Work out the IPv6 address. | |
573 | eval nn=\$net_inet6_$net | |
574 | for n in $nn; do | |
575 | addr=${n%/*} | |
576 | base=${addr%::*} | |
1710944b | 577 | case $a6 in ::*) aa=$base$a6 ;; *) aa=$a6 ;; esac |
beb4f0ee MW |
578 | eval host_inet6_$name=$aa |
579 | done | |
580 | ||
581 | ## Remember the host in the list. | |
582 | addword net_hosts_$net $name | |
583 | } | |
584 | ||
585 | ## defhost NAME | |
586 | ## | |
587 | ## Define a new host. Follow by calls to `iface' to define the host's | |
588 | ## interfaces. | |
589 | defhost () { | |
590 | host=$1 | |
591 | addword allhosts $host | |
4eb9f4df | 592 | eval host_type_$host=server |
beb4f0ee MW |
593 | } |
594 | ||
4eb9f4df | 595 | ## hosttype TYPE |
beb4f0ee | 596 | ## |
4eb9f4df MW |
597 | ## Declare the host to have the given type. |
598 | hosttype () { | |
599 | type=$1 | |
600 | case $type in | |
601 | router | server | client) ;; | |
602 | *) echo >&2 "$0: bad host type \`$type'"; exit 1 ;; | |
603 | esac | |
604 | eval host_type_$host=$type | |
beb4f0ee MW |
605 | } |
606 | ||
607 | ## iface IFACE NET ... | |
608 | ## | |
609 | ## Define a host's interfaces. Specifically, declares that the host has an | |
610 | ## interface IFACE attached to the listed NETs. | |
611 | iface () { | |
612 | name=$1; shift | |
613 | for net in "$@"; do | |
614 | addword host_ifaces_$host $name=$net | |
615 | done | |
616 | } | |
617 | ||
11732033 MW |
618 | ## matchnets OPT WIN FLAGS PREPARE BASE SUFFIX NEXT NET [NET ...] |
619 | ## | |
1264e917 | 620 | ## Build rules which match a particular collection of networks. |
11732033 | 621 | ## |
1264e917 | 622 | ## Specifically, use the address-comparison operator OPT (typically `-s' or |
11732033 MW |
623 | ## `-d') to match the addresses of each NET, writing the rules to the chain |
624 | ## BASESUFFIX. If we find a match, dispatch to WIN-CLASS, where CLASS is the | |
625 | ## class of the matching network. In order to deal with networks containing | |
626 | ## negative address ranges, more chains may need to be constructed; they will | |
627 | ## be named BASE#Q for sequence numbers Q starting with NEXT. All of this | |
628 | ## happens on the `mangle' table, and there isn't (currently) a way to tweak | |
629 | ## this. | |
1264e917 MW |
630 | ## |
631 | ## The FLAGS gather additional interesting information about the job, | |
632 | ## separated by colons. The only flag currently is :default: which means | |
633 | ## that the default network was listed. | |
634 | ## | |
635 | ## Finally, there is a hook PREPARE which is called just in advance of | |
636 | ## processing the final network, passing it the argument FLAGS. (The PREPARE | |
637 | ## string will be subjected to shell word-splitting, so it can provide some | |
638 | ## arguments of its own if it wants.) It should set `mode' to indicate how | |
639 | ## the chain should be finished. | |
640 | ## | |
641 | ## goto If no networks matched, then issue a final `goto' to the | |
642 | ## chain named by the variable `fail'. | |
643 | ## | |
644 | ## call Run `$finish CHAIN' to write final rules to the named CHAIN | |
645 | ## (which may be suffixed from the original BASE argument if | |
646 | ## this was necessary). This function will arrange to call | |
647 | ## these rules if no networks match. | |
648 | ## | |
649 | ## ret If no network matches then return (maybe by falling off the | |
650 | ## end of the chain). | |
651 | matchnets () { | |
652 | local opt win flags prepare base suffix next net lose splitp | |
653 | opt=$1 win=$2 flags=$3 prepare=$4 base=$5 suffix=$6 next=$7 net=$8 | |
654 | shift 8 | |
655 | ||
656 | ## If this is the default network, then set the flag. | |
657 | case "$net" in default) flags=${flags}default: ;; esac | |
658 | ||
659 | ## Do an initial pass over the addresses to see whether there are any | |
660 | ## negative ranges. If so, we'll need to split. See also the standard | |
661 | ## joke about soup. | |
662 | splitp=nil | |
663 | eval "addrs=\"\$net_inet_$net \$net_inet6_$net\"" | |
664 | for a in $addrs; do case $a in !*) splitp=t; break ;; esac; done | |
665 | ||
666 | trace "MATCHNETS [splitp $splitp] $opt $win $flags [$prepare] $base $suffix $next : $net $*" | |
667 | ||
668 | ## Work out how to handle matches against negative address ranges. If this | |
669 | ## is the last network, invoke the PREPARE hook to find out. Otherwise, if | |
670 | ## we have to split the chain, recursively build the target here. | |
671 | case $splitp,$# in | |
672 | t,0 | nil,0) | |
673 | $prepare $flags | |
674 | case $splitp,$mode in | |
675 | *,goto) | |
676 | lose="-g $fail" | |
677 | ;; | |
678 | *,ret) | |
679 | lose="-j RETURN" | |
680 | ;; | |
681 | t,call) | |
682 | clearchain mangle:$base#$next | |
683 | lose="-g $base#$next" | |
684 | ;; | |
685 | nil,call) | |
686 | ;; | |
687 | esac | |
688 | ;; | |
689 | t,*) | |
690 | clearchain mangle:$base#$next | |
691 | matchnets $opt $win $flags "$prepare" \ | |
692 | $base \#$next $(( $next + 1 )) "$@" | |
693 | lose="-g $base#$next" mode=goto | |
694 | ;; | |
695 | *) | |
696 | mode=continue | |
697 | ;; | |
698 | esac | |
699 | ||
700 | ## Populate the chain with rules to match the necessary networks. | |
701 | eval addr=\$net_inet_$net addr6=\$net_inet6_$net class=\$net_class_$net | |
702 | for a in $addr; do | |
703 | case $a in | |
704 | !*) run iptables -t mangle -A $base$suffix $lose $opt ${a#!} ;; | |
705 | *) run iptables -t mangle -A $base$suffix -g $win-$class $opt $a ;; | |
706 | esac | |
707 | done | |
708 | for a in $addr6; do | |
709 | case $a in | |
710 | !*) run ip6tables -t mangle -A $base$suffix $lose $opt ${a#!} ;; | |
711 | *) run ip6tables -t mangle -A $base$suffix -g $win-$class $opt $a ;; | |
712 | esac | |
713 | done | |
714 | ||
715 | ## Wrap up the chain appropriately. If we didn't split and there are more | |
716 | ## networks to handle then append the necessary rules now. (If we did | |
717 | ## split, then we already wrote the rules for them above.) If there are no | |
718 | ## more networks then consult the `mode' setting to find out what to do. | |
719 | case $splitp,$#,$mode in | |
720 | *,0,ret) ;; | |
721 | *,*,goto) run ip46tables -t mangle -A $base$suffix $lose ;; | |
722 | t,0,call) $finish $base#$next ;; | |
723 | nil,0,call) $finish $base$suffix ;; | |
724 | nil,*,*) | |
725 | matchnets $opt $win $flags "$prepare" $base "$suffix" $next "$@" | |
726 | ;; | |
727 | esac | |
728 | } | |
729 | ||
beb4f0ee MW |
730 | ## net_interfaces HOST NET |
731 | ## | |
732 | ## Determine the interfaces on which packets may plausibly arrive from the | |
733 | ## named NET. Returns `-' if no such interface exists. | |
734 | ## | |
735 | ## This algorithm is not very clever. It's just about barely good enough to | |
736 | ## deduce transitivity through a simple routed network; with complicated | |
737 | ## networks, it will undoubtedly give wrong answers. Check the results | |
738 | ## carefully, and, if necessary, list the connectivity explicitly; use the | |
739 | ## special interface `-' for networks you know shouldn't send packets to a | |
740 | ## host. | |
741 | net_interfaces () { | |
742 | host=$1 startnet=$2 | |
743 | ||
744 | ## Determine the locally attached networks. | |
745 | targets=: | |
746 | eval ii=\$host_ifaces_$host | |
747 | for i in $ii; do targets=$targets$i:; done | |
748 | ||
749 | ## Determine the transitivity. | |
750 | seen=: | |
751 | nets=$startnet | |
752 | while :; do | |
753 | ||
754 | ## First pass. Determine whether any of the networks we're considering | |
755 | ## are in the target set. If they are, then return the corresponding | |
756 | ## interfaces. | |
757 | found="" | |
758 | for net in $nets; do | |
759 | tg=$targets | |
760 | while :; do | |
761 | any=nil | |
762 | case $tg in | |
763 | *"=$net:"*) | |
764 | n=${tg%=$net:*}; tg=${n%:*}:; n=${n##*:} | |
765 | addword found $n | |
766 | any=t | |
767 | ;; | |
768 | esac | |
769 | case $any in nil) break ;; esac | |
770 | done | |
771 | done | |
772 | case "$found" in ?*) echo $found; return ;; esac | |
773 | ||
774 | ## No joy. Determine the set of networks which (a) these ones can | |
775 | ## forward to, and (b) that we've not considered already. These are the | |
776 | ## nets we'll consider next time around. | |
777 | nextnets="" | |
778 | any=nil | |
779 | for net in $nets; do | |
17a45245 MW |
780 | eval via=\$net_via_$net |
781 | for n in $via; do | |
beb4f0ee MW |
782 | case $seen in *":$n:"*) continue ;; esac |
783 | seen=$seen$n: | |
784 | eval noxit=\$net_noxit_$n | |
785 | case " $noxit " in *" $startnet "*) continue ;; esac | |
786 | case " $nextnets " in | |
787 | *" $n "*) ;; | |
788 | *) addword nextnets $n; any=t ;; | |
789 | esac | |
790 | done | |
791 | done | |
792 | ||
793 | ## If we've run out of networks then there's no reachability. Return a | |
794 | ## failure. | |
795 | case $any in nil) echo -; return ;; esac | |
796 | nets=$nextnets | |
797 | done | |
bfdc045d MW |
798 | } |
799 | ||
800 | m4_divert(-1) | |
801 | ###----- That's all, folks -------------------------------------------------- |