ipturntables.sh 38.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
#!/bin/bash

# trying to simplify iptables usage

printAbout()
{
	printf "Usage: $0
 -4=IPv4|-6=IPv6
 [KERNEL_PARAMS param1,param2,...,paramN]
 [PROBE_KERNEL_MODS mod1,mod2,...,modN]
 [RESET [default_policy]]
 [BASE_RULE_SET]
13 14 15 16 17
 [ALLOW_DHCP_CLIENT on_link]
 [ALLOW_SUBNETS on_link]
 [ALLOW_LINK_LOCAL on_link]
 [ALLOW_SERVICE_DISCOVERY on_link]
 [ALLOW_TUNNEL mode on_link from_address]
18
 [ALLOW_PORT|ALLOW_SERVICE port[,port,port,...] ip|link [tcp|udp]]
19 20
 [FORWARD_SUBNET subnet/mask to_link]
 [FORWARD_SUBNET_PROTECTIVE subnet/mask to_link]
21
 [FORWARD_PORT|FORWARD_ROUTING ip|link port(s) to_address[:to_port] [tcp|udp]]
22
 [MAC_FILTER chain mac_address]
23
 [POSTROUTING_MASQUERADE subnet/mask on_link]
24 25
 [DEBUG_CHAIN chain_name]
 [REMOVE_RULES pattern]
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

Written by Branko Mikic in Nov 2ol4
All copyrights reserved. Free use of this software is granted under the terms of the GNU General Public License (GPLv3).

See man page (ipturntables.8) for a detailed documentation.

"
	exit 5
}

msg()
{
	echo ""
	echo "${0##*/}: $1"
}

error()
{
	msg "(ERROR $1) >> $2"
	kill $$
    exit $1
}

49
# some helpers
50

51 52 53
function isNaturalNumber() { [[ ${1} =~ ^[0-9]+$ ]]; }
function isInteger() { [[ ${1} == ?(-)+([0-9]) ]]; }
function isFloat() { [[ ${1} == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]; }
54
function isNumber() { isNaturalNumber $1 || isInteger $1 || isFloat $1; }
55

56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
###
### Command environment setup for either IPv(4|6)
###
### $1: Desired protocol either 4=IPv4 or 6=IPv6
###
setupEnv()
{
	[ $1 -ne 4 ] && [ $1 -ne 6 ] &&	error 1001 "setupEnv(): Mode must be an integer of 4 or 6."

	DEPMOD=$(which depmod)
	[ -z "$DEPMOD" ] && error 4 "Can't use depmod command. Please install missing command."

	MODPROBE=$(which modprobe)
	[ -z "$MODPROBE" ] && error 2 "Can't use modprobe command. Please install missing command."

	IP=$(which ip)
	(( $? != 0 )) && error 3 "Can't use ip command. Please install missing command."

74

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
	if [ $1 -eq 4 ]; then
		IPTABLES=$(which iptables)
		(( $? != 0 )) && error 1 "Can't use iptables command. Please install missing command."
	fi

	if [ $1 -eq 6 ]; then
		IPTABLES=$(which ip6tables)
		(( $? != 0 )) && error 1 "Can't use ip6tables command. Please install missing command."

		ICMPTAG+="v6"
		IP+=" -6"
	fi

	ENVID=$1
	ICMPTAG="icmp"
}

###
### $1: A list of kernel module names to probe for.
###
probeKernelModules()
{
 local sz;

	printf "# Kernel modules probed:\n#   "

	$DEPMOD -a
	for sz in $1; do
		printf " %s" $sz
		$MODPROBE $sz
		(( $? != 0 )) && error 100 "Kernel module '$sz' no available."
	done
	echo ""
}

###
### $1: Mode must be an integer value of 4 or 6 (4=inet|6=inet6)
### $2: Desired kernel param below /proc/sys/net/ipv$(MODE)/conf
###     eg: autoconf or eth2/redirect
###
printKernelParams()
{
 local i; local sz; local sy;

	[ $1 -ne 4 ] && [ $1 -ne 6 ] &&	error 1001 "printKernelParams(): Mode must be an integer of 4 or 6."

	(( i=0 ));
	sz="/proc/sys/net/ipv$1"

	[ -f "$sz/conf/default/$2" ] && sz="$sz/conf" && sy="*/$2"
	[ -f "$sz/conf/$2" ] && sz="$sz/conf" && sy="$2"
Branko Mikić's avatar
Branko Mikić committed
126
	[ -d "$sz/conf/$2" ] && sz="$sz/conf/$2" && sy="*" && printf "#\n# $2:\n"
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
	[ -f "$sz/$2" ] && sy="$2"
	[ -z "$sy" ] && error 27 "Kernel param: '$2' not found!"

	pushd $sz 1>/dev/nul 2>/dev/nul
	for sz in $sy ; do
		(( i==0 )) && printf "#"
		printf "%36s=%-12s" $sz $(cat $sz);
		(( i++ ))
		if (( i == 2 )); then
			printf "\n"
			(( i=0 ))
		else
			printf "\t"
		fi
	done;
	popd 1>/dev/nul 2>/dev/nul
	(( i == 1 )) && printf "\n"
}

###
### Checks network devices for existence
###
### $1: A list of network devices
###
### Return code is (0: Given devices exist | 1: One of the devices not existing)
###
checkLink()
{
 local sz; local i;

157
	(( ${#@} == 0 )) && error 1202 "checkDevice(): device or device list argument is mandatory."
158 159 160 161 162 163 164 165 166 167

	(( i=0 ))
	for sz in $@; do
		$IP link show $sz 1>/dev/nul 2>/dev/nul
		(( i+=$? ))
	done
	return $i
}

###
168
### $1: an interface node (eg: eth0 or eth1, ...)
169
###
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
### Returns an unique hex ID derived from the MAC address.
### Virtual network devices return a special hash value since
### they usally don't have a MAC address.
### 
getLinkID()
{
 local i;

	[ -z "$1" ] && error 1203 "getLinkID(): device argument is mandatory."

	if [ $(cat /sys/class/net/$1/addr_len) -gt 0 ]; then
		printf "0x%s" $(cat /sys/class/net/$1/address | sed "s/://g")
	else
		printf "0x"
		for (( i=0; i<${#1}; i++ )); do
			printf '%02X' "'${1:$i:1}"
		done
	fi
}

###
191
### $1: an interface node (eg: eth0 or eth1, ...)
192 193 194
###
### Returns the MAC address of the desired network interface
###
195 196
getLinkMAC()
{
197 198 199 200
	[ -z "$1" ] && error 1204 "getLinkMAC(): device argument is mandatory."

#	printf "%s" $($IP link show $1 | grep -o -P "(?<=link/ether ).*(?=\sbrd)")
	printf "%s" $(cat /sys/class/net/$1/address)
201

202 203
		# tricky! regardless wether it's true or false >> just return the return code
	[ $(cat /sys/class/net/eth2/addr_len) -gt 0 ] ; return $?
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
}

###
### $1: Mode must be an integer value of 4 or 6 (4=inet|6=inet6)
### $2: Scope to obtain addresses from (eg: host|link|global|all)
### $3: list of devices (separated by spaces) to obtain ip's for.
###
### Prints a list of corresponding ip addresses
###
obtainIPs()
{
 local sz;

	[ $1 -ne 4 ] && [ $1 -ne 6 ] &&	error 1001 "obtainIPs(): Mode must be an integer of 4 or 6."
	[[ $2 =~ host|link|global|all ]] || error 1002 "obtainIPs(): Scope argument must be either host, link, global or all"
	[ -z "$3" ] && error 1002 "obtainIPs(): No list of devices given."

	for sz in $3; do
		sz=$(ip \-$1 addr show dev $sz scope $2 | grep -P -o "[\w|\.|\:]+(?=\/\d+)")
		printf " $sz"
	done
}

###
228
### $1: Some IP of a local net
229
###
230
obtainRouteToIP()
231
{
232
	printf "%s" $($IP -o route get $1 | grep -o -P "(?<=src\s)(\d+\.){3}\d+")
233 234 235
}

###
236 237 238
### $1: Desired IP address to obtain corresponding net prefix
### $2: A link the IP address is configured on
###     (Optional for IPv4 | Mandatory for IPv6 !!!)
239
###
240
### Returns a subnet prefix
241
###
242
obtainNetPrefix()
243
{
244
 local i;
245

246 247 248 249 250 251 252 253 254 255
	if [ $ENVID -eq 4 ]; then
			# obtain ip of this host from given ip
		if ! $IP addr show $1 &> /dev/null ; then
			i=$(obtainRouteToIP $1)
		else
			i=$1
		fi
		printf "%s" $($IP -o route list | grep -oP "(\d+\.){3}+\d+\/\d+(?=.* src $i)")
	fi
	[ $ENVID -eq 6 ] && printf "%s" $($IP -o route list | grep -oP "${1%::*}::\/\d* (?=dev $2)")
256 257 258 259 260 261 262 263 264 265 266 267
}

###
### $1: a net prefix (eg: 192.168.0.0/16)
###
### Prints the related device from the route table
### Return code != 0 indicating an error.
###
obtainLinkFromSubnetPrefix()
{
 local sz;

268
	sz=$($IP -o route list | grep -P "^$1" | grep -o -P "(?<= dev )\w*")
269 270 271 272
	(( $? == 0 )) && printf "%s" $sz && return 0
	return 1
}

273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
###
### $1: a MAC address (eg; 06:00:17:d3:97:b4)
###
### Return code == 0: Argument has MAC format
###             != 0: Argument is not in MAC format
###
checkMACArgFormat()
{
	echo "$1" | grep -q -oE "^(\w+{2}:){5}\w{2}$"
}

###
### $1: IP address with optional port assignment (eg: 192.168.1.1:80) or a device node
###
### Returns TRUE if $1 is an IPv4 or IPv6 address otherwise FALSE 
###
checkIPArgFormat()
{
 local sz;

	[ $ENVID -eq 4 ] && sz="(\d+\.){3}\d+((\:)+(\d)+)*$"
		# TODO: check for IPv6 address! Format is untested and likely to fail
295
	[ $ENVID -eq 6 ] && sz="^([\da-fA-F]+:)+(:[\da-fA-F]+)+$"
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310

	echo "$1" | grep -q -P "$sz" 1>/dev/nul 2>/dev/nul
	return $?
}

###
### $1: A subnet/mask argument
###
### Returns 0 if argument has a subnet/mask format otherwise 1
###
checkSubnetArgFormat()
{
 local sz;

	[ $ENVID -eq 4 ] && sz="(\d+\.){3}+\d+\/\d+"
311
	[ $ENVID -eq 6 ] && sz="^([\da-fA-F]+:)+(:\/\d+)+$"
312 313 314 315 316

	echo "$1" | grep -q -P "$sz" 1>/dev/nul 2>/dev/nul
	return $?
}

317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
###
### $1: Either tcp or udp
###
### Returns a string either 'tcp', 'udp'
### or 'any' if none of the other
###
checkProtocol()
{
	echo $1 | grep -iP "(tcp|udp)" 1>/dev/nul 2>/dev/nul
	if (($? == 0)); then
		printf $1
	else
		printf "any"
	fi
}

333 334 335 336 337 338 339 340 341 342 343
###
### $1: chain to search for device occurence in any rule
###
### Returns the # of rules in a chain
###
returnRuleCount()
{
 local sz;

	[ -z $1 ] && error 1 "returnRuleCount(): chain argument is mandatory."

344 345
	let sz=$($IPTABLES --line-numbers -L $1 | grep -P -c ^[0-9]+)
	return $sz
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
}

###
### Checks if iptables contains any rules
###
### Returns rule count of INPUT, OUTPUT & FORWARD or 0 if empty
###
IsEmpty()
{
 local n;

	(( n=0 ))
	returnRuleCount INPUT
	(( n=n+$? ))
	returnRuleCount OUTPUT
	(( n=n+$? ))
	returnRuleCount FORWARD
	(( n=n+$? ))
	return $n
}

###
### $1: chain to search in (eg: INPUT or -t nat POSTROUTING)
### $2: string pattern to look for
###
### Prints a list of rule indices in which the pattern occurs
###
obtainRuleIndices()
{
 local sz; local sy;

	if [[ $1 =~ -t ]]; then
		[ -z $2 ] && error 70 "obtainRuleIndices() expects a tablename after optional -t argument."
		sy="$1 $2"
		shift; shift;
	fi
	[ -z "$1" ] && error 71 "obtainRuleIndices(): chain argument is mandatory."
	[ -z "$2" ] && error 72 "obtainRuleIndices(): string pattern argument is mandatory."

385
	sz=$($IPTABLES --line-numbers $sy -vnL $1 | grep -P -o "^[0-9]*(?=.*$2)")
386 387 388 389 390 391 392
	printf "%s " $sz
}

###
### $1: chain to delete rules by pattern in (eg: INPUT or -t nat POSTROUTING)
### $2: string pattern to look for
###
393 394
### Deletes any rule in chain $1 containing the pattern $2
### Return code == Number of successfully removed rules
395 396 397 398
###
deleteRules()
{
 local i; local ia;
399
 local sz; local n;
400 401 402 403 404 405 406 407

	if [[ $1 =~ -t ]]; then
		[ -z $2 ] && error 70 "deleteRules() expects a tablename after optional -t argument."
		sz="$1 $2"
		shift; shift;
	fi
		# putting it in brackets returns it as an array
	ia=($(obtainRuleIndices $sz $1 "$2"))
408
	(( n=0 ))
409
	for (( i=${#ia[@]}-1; i>=0; i-- )); do
410
			# delete rule
411
		$IPTABLES $sz -D $1 ${ia[$i]}
412
		(( $? == 0 )) && (( n++ ))
413
	done
414
	return $n
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
}

###
### $1: One or multiple desired chains to probe for existence
###
probeChains()
{
 local sz;

	for sz in $@; do
		$IPTABLES -nvL $sz 1>/dev/nul 2>/dev/nul
		(( $? != 0 )) && return 1
	done
	return 0
}

###
### $1: Desired name of chain to alloc (only for user-defined chains!)
###     Attention!
###     if that chain already exists it will be zero'ed & flushed (!)
###
allocChain()
{
	[ -z $1 ] && error 10 "allocChain(NULL): chain argument is mandatory."
	[[ $1 =~ INPUT|FORWARD|OUTPUT ]] && error 11 "allocChain($1): Built-in chains are hardwired! Only user-defined chains can be allocated."

	$IPTABLES -nvL $1 1>/dev/nul 2>/dev/nul
	if (( $? == 0 )); then
		$IPTABLES --zero $1
		$IPTABLES --flush $1
	else
		$IPTABLES -N $1
	fi
	return 0
}

###
452
### $1: Desired name of chain to dealloc (only for user-defined chains!)
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
###
deallocChain()
{
	[ -z $1 ] && error 10 "deallocChain(NULL): chain argument is mandatory."
	[[ $1 =~ INPUT|FORWARD|OUTPUT ]] && error 11 "deallocChain($1): Built-in chains are hardwired! Only user-defined chains can be allocated."

	$IPTABLES -nvL $1 1>/dev/nul 2>/dev/nul
	(( $? != 0 )) && echo "INFO >> deallocChain($1): Given chain doesn't exist." && return 1
	$IPTABLES --zero $1
	$IPTABLES --flush $1
	$IPTABLES --delete-chain $1
}

###
### $1: A subnet (eg: 192.168.0.0/16)
###
### Prints a hexadecimal representation (eg: 0xC0A8000010)
###
471
formatAsHexID() {
472 473 474 475 476 477 478 479 480 481 482 483
 local sz; local sy;
								# sed using bash! ${1//REPLACE_ME/WITH_THIS}
	if [ $ENVID -eq 4 ]; then
		sz=${1//\./ }
		sz=${sz//\// }
		for sy in $sz; do
			printf "%02X" $sy
		done
	fi

	if [ $ENVID -eq 6 ]; then
		sz=${1%/*}
484 485
		printf ${sz//\:/}
		[[ $1 =~ '/' ]] && printf "%02X" ${1#*/}
486 487 488 489 490 491 492 493 494
	fi
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~~~ main script starts here
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	(( ${#@} == 0 )) && printAbout

495 496 497
	$(groups $USER | grep netdev) >/dev/null
	[ "$USER" != "root" ] && (( $? == 1 )) && error 46 "Superuser priviledges needed. This script makes heavy use of 'iptables' command which is only allowed to be used by root or a user being a member of 'netdev' group."
	# we are not root!    and we aren't a user of 'netdev' group either?
498

499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
	[ "$1" == "-6" ] && arg=6
	[ "$1" == "-4" ] && arg=4
	if [ ! -v arg ]; then
		# default environment is IPv4
		arg=4
	else
		shift
	fi
	setupEnv $arg

	while (( ${#@} >= 1 )); do
		arg=${1^^}
		shift
		case $arg in
			KERNEL_PARAMS)
				printf "#\n# /proc/sys/net/ipv$ENVID:\n"
				for sz in $(echo $1 | sed "s/,/ /g"); do
					printKernelParams $ENVID $sz
				done
				echo "#"
				shift
				;;

			PROBE_KERNEL_MODS)
				sz=$(echo $1 | sed "s/,/ /g")
				probeKernelModules "$sz"
				echo "#"
				shift
				;;

			RESET)
				echo "# reseting ruleset ($IPTABLES)"

				sz=${1^^}
				if [[ $sz =~ ACCEPT|DROP|REJECT ]]; then
					shift
				else
					sz="ACCEPT"
				fi

				$IPTABLES -t nat -nvL 1>/dev/nul 2>/dev/nul
				(( $? == 0 )) && $IPTABLES -t nat -F

				$IPTABLES -t mangle -nvL 1>/dev/nul 2>/dev/nul
				(( $? == 0 )) && $IPTABLES -t mangle -F

				$IPTABLES -F
					# set policy (default: accept)
				$IPTABLES -P INPUT $sz
				$IPTABLES -P OUTPUT $sz
				$IPTABLES -P FORWARD $sz
					# delete all user-specified chains
				$IPTABLES -X
					# reset all counters
				$IPTABLES -Z

				unset sz
				;;

			BASE_RULE_SET)
				IsEmpty; (( $? != 0 )) && error 32 "iptables contains rules. Prepend RESET before BASE_RULE_SET argument."

				echo "# setting up base ruleset"

					# default policy is to drop anything on builtin chains
				$IPTABLES -P INPUT DROP
				$IPTABLES -P FORWARD DROP
				$IPTABLES -P OUTPUT DROP

					# create a LOG & DROP chain
569 570 571 572
				BLOCK_INVALID=1
				BLOCK_LIMITER=2
				BLOCK_ANTI_FLOOD=4

573
				$IPTABLES -N BLOCK
574 575 576 577 578 579 580
				$IPTABLES -A BLOCK --match hashlimit --hashlimit-name DROP_SILENTLY \
									--hashlimit-above 3/min -j DROP
				$IPTABLES -A BLOCK -m state --state INVALID -j MARK --set-mark $BLOCK_INVALID
				$IPTABLES -A BLOCK -m mark --mark $BLOCK_LIMITER -j LOG --log-prefix "[BLOCK] (LIMITER) "
				$IPTABLES -A BLOCK -m mark --mark $BLOCK_INVALID -j LOG --log-prefix "[BLOCK] (INVALID) "
				$IPTABLES -A BLOCK -m mark --mark $BLOCK_ANTI_FLOOD -j LOG --log-prefix "[BLOCK] (ANTIFLOOD) "
				$IPTABLES -A BLOCK -m mark --mark 0 -j LOG --log-prefix "[BLOCK] "
581 582
				$IPTABLES -A BLOCK -j DROP

583 584 585 586 587 588 589 590 591 592 593 594 595 596
					# create an ANTI-FLOOD protection chain. Maximises the rate of incoming connections. 
					# Used for protection against SYN or PING_OF_DEATH flooding
				$IPTABLES -N ANTI-FLOOD
				$IPTABLES -A ANTI-FLOOD -m limit --limit 3/s -j RETURN
				$IPTABLES -A ANTI-FLOOD -m mark --mark $BLOCK_ANTI_FLOOD -j BLOCK

					# limits the connection attempts
				$IPTABLES -N LIMITER
				$IPTABLES -A LIMITER -m state ! --state NEW -j RETURN
				$IPTABLES -A LIMITER --match hashlimit --hashlimit-name LIMITER --hashlimit-mode srcip \
									--hashlimit-upto 3/hour --hashlimit-burst 3 -j RETURN
				$IPTABLES -A LIMITER -j MARK --set-mark $BLOCK_LIMITER
				$IPTABLES -A LIMITER -j BLOCK

597 598 599 600 601 602 603 604
					# Will be used in INPUT, OUTPUT to add allowed subnets on internal links
				$IPTABLES -N LOCAL
				$IPTABLES -A LOCAL -m addrtype --dst-type LOCAL -j RETURN
				$IPTABLES -A LOCAL -m addrtype --dst-type MULTICAST -j RETURN
				[ $ENVID == "4" ] && $IPTABLES -A LOCAL -m addrtype --dst-type BROADCAST -j RETURN
				$IPTABLES -A LOCAL -j BLOCK
					# When this chain retuns it's either a local, multi- or broadcast packet

605 606 607 608 609

					# will be used in INPUT, OUTPUT & FORWARD
					# this chain should return always! (don't place DROP rules on specific ICMPs here!)
				$IPTABLES -N ICMP

610
					# place your custom rules in these chains used in INPUT & OUTPUT
611 612 613 614 615 616 617
				$IPTABLES -N USER-IN
				$IPTABLES -N USER-OUT

					# allow local loopback in & out
				$IPTABLES -A INPUT -i lo -j ACCEPT
				$IPTABLES -A OUTPUT -o lo -j ACCEPT

618 619 620
					# stateful packets intiated by ourself coming back
				$IPTABLES -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

621 622 623 624 625 626 627 628 629 630 631 632 633 634
				# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IPv4
				if [ $ENVID == "4" ]; then

						# ICMPs allowed (nearly all as most others are depricated)
					$IPTABLES -A ICMP -p icmp -m icmp --icmp-type 3 -j ACCEPT -m comment --comment "destination-unreachable"
					$IPTABLES -A ICMP -p icmp -m icmp --icmp-type 4 -j ACCEPT -m comment --comment "source-quench"
					$IPTABLES -A ICMP -p icmp -m icmp --icmp-type 8 -j ANTI-FLOOD -m comment --comment "echo-request: Ping of death"
					$IPTABLES -A ICMP -p icmp -m icmp --icmp-type 8 -j ACCEPT -m comment --comment "echo-request"
					$IPTABLES -A ICMP -p icmp -m icmp --icmp-type 11 -j ACCEPT -m comment --comment "time-exceeded"
					$IPTABLES -A ICMP -p icmp -m icmp --icmp-type 12 -j ACCEPT -m comment --comment "parameter-problem"

						# handle ICMP protocol
					$IPTABLES -A INPUT -p icmp -j ICMP

635 636
						# intercept all the TCP handshakes and correct on-the-fly the wrong
						# MSS value requested by internal hosts in magle tables of FORWARD chain
637 638 639 640 641 642
					$IPTABLES -A FORWARD -t mangle -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
	 			fi

				# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IPv6
				if [ $ENVID == "6" ]; then

643 644 645 646 647 648 649 650 651 652
						# filter all packets that have RH0 headers:
					#$IPTABLES -A INPUT -m rt --rt-type 0 --rt-segsleft 0 -j DROP
					#$IPTABLES -A OUTPUT -m rt --rt-type 0 --rt-segsleft 0 -j DROP
					#$IPTABLES -A FORWARD -m rt --rt-type 0 --rt-segsleft 0 -j DROP
						# explicit rules for RH0 is many years obsolete and no longer necessary on modern Linux systems!
						# https://serverfault.com/questions/410321/debian-ip6tables-rules-setup-for-ipv6/527334

						# echo reply allow explicitly on link local 
					$IPTABLES -A ICMP -p ipv6-icmp -m icmp6 --icmpv6-type 129 -s fe80::/10 -j ACCEPT -m comment --comment "echo-reply allowed on link local"
						# accept harmless ICMP requests
653 654 655 656 657 658 659
					$IPTABLES -A ICMP -p ipv6-icmp -m icmp6 --icmpv6-type 1 -j ACCEPT -m comment --comment "destination unreachable"
					$IPTABLES -A ICMP -p ipv6-icmp -m icmp6 --icmpv6-type 2 -j ACCEPT -m comment --comment "packet too big"
					$IPTABLES -A ICMP -p ipv6-icmp -m icmp6 --icmpv6-type 3 -j ACCEPT -m comment --comment "time exceeded"
					$IPTABLES -A ICMP -p ipv6-icmp -m icmp6 --icmpv6-type 4 -j ACCEPT -m comment --comment "parameter problem"
					$IPTABLES -A ICMP -p ipv6-icmp -m icmp6 --icmpv6-type 128 -j ANTI-FLOOD -m comment --comment "echo-request: Ping of death"
					$IPTABLES -A ICMP -p ipv6-icmp -m icmp6 --icmpv6-type 128 -j ACCEPT  -m comment --comment "echo-request"

660 661
						# ICMP: handling of router|neighbor solicitation|advertisments
						# with special hop limits for traffic in and out
662 663 664 665
					$IPTABLES -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 133 -m hl --hl-eq 255 -j ACCEPT -m comment --comment "multicast listener done"
					$IPTABLES -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 134 -m hl --hl-eq 255 -j ACCEPT -m comment --comment "router advertisement"
					$IPTABLES -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 135 -m hl --hl-eq 255 -j ACCEPT -m comment --comment "neighbor solicitation"
					$IPTABLES -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 136 -m hl --hl-eq 255 -j ACCEPT -m comment --comment "neighbor advertisement"
666 667 668 669
					$IPTABLES -A OUTPUT -p ipv6-icmp -m icmp6 --icmpv6-type 133 -m hl --hl-eq 255 -j ACCEPT -m comment --comment "multicast listener done"
					$IPTABLES -A OUTPUT -p ipv6-icmp -m icmp6 --icmpv6-type 135 -m hl --hl-eq 255 -j ACCEPT -m comment --comment "neighbor solicitation"
					$IPTABLES -A OUTPUT -p ipv6-icmp -m icmp6 --icmpv6-type 136 -m hl --hl-eq 255 -j ACCEPT -m comment --comment "neighbor advertisement"
					$IPTABLES -A OUTPUT -p ipv6-icmp -m icmp6 --icmpv6-type 143 -m hl --hl-eq 1 -j ACCEPT -m comment --comment "V2 multicast listener report"
670 671 672

						# handle ICMP protocol
					$IPTABLES -A INPUT -p ipv6-icmp -j ICMP
673
				fi
674

675
				# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ for both IPv(4|6)
676

677 678
					# only routable subnets allowed
				$IPTABLES -A INPUT -j LOCAL
679

680
				$IPTABLES -A INPUT -m state --state INVALID -j BLOCK
681

682 683
					# syn-flood protection
				$IPTABLES -A INPUT -p tcp --syn -j ANTI-FLOOD
684

685 686
					# furtive port scanner
				$IPTABLES -A INPUT -p tcp --tcp-flags SYN,ACK,FIN,RST RST -j ANTI-FLOOD
687

688 689
					# allow some safe ports (but use a limiter especially for ssh port)
				$IPTABLES -A INPUT -p tcp -m tcp --dport 22 -j LIMITER -m comment --comment "avoid bashing on ssh port"
690
				$IPTABLES -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT -m comment --comment "ssh"
691 692 693 694 695 696 697

					# stateful packets intiated by ourself going out
				$IPTABLES -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

					# new packets are allowed to go out from anywhere
				$IPTABLES -A OUTPUT -m state --state NEW -j ACCEPT

698 699 700 701 702 703 704 705 706
					# only routable subnets allowed
				$IPTABLES -A OUTPUT -j LOCAL

				# ~~~ any of the default chains should end with this!

					# user customized rules for traffic coming in & going out
				$IPTABLES -A INPUT -j USER-IN -m comment --comment "add your custom INPUT rules in the USER-IN chain!"
				$IPTABLES -A OUTPUT -j USER-OUT -m comment --comment "add your custom OUTPUT rules in the USER-OUT chain!"

707
					# default log rules
708 709 710
				$IPTABLES -A INPUT -m limit --limit 8/min --limit-burst 16 -j LOG --log-prefix "[IN$ENVID-DROP] "
				$IPTABLES -A OUTPUT -m limit --limit 8/min --limit-burst 16 -j LOG --log-prefix "[OU$ENVID-DROP] "
				$IPTABLES -A FORWARD -m limit --limit 8/min --limit-burst 16 -j LOG --log-prefix "[FW$ENVID-DROP] "
711 712
				;;

713 714 715 716 717 718
			ALLOW_DHCP_CLIENT)
				sz=""
				checkLink $1
				(( $? != 0 )) && sz=" (WARNING! '$1' is currently not available. Maybe invoked later?)" 
				printf "# allowing DHCP (IPv%s) client requests on '%s'.%s\n" $ENVID $1 "$sz"

719
				ID="ALLOW_DHCP_CLIENT_on_$(getLinkID $1)"
720 721 722 723
				deleteRules USER-IN "$ID"

				if [ $ENVID -eq 4 ]; then
					$IPTABLES -A USER-IN -p udp -m udp -i $1 -s 0.0.0.0 --sport 68 -d 255.255.255.255 --dport 67 -j ACCEPT -m comment --comment "$ID"
724
					$IPTABLES -A USER-IN -p udp -m udp -i $1 --sport 67 --dport 68 -j ACCEPT -m comment --comment "$ID (dhcp/bootp)"
725 726 727 728 729 730 731 732 733
				fi

				if [ $ENVID -eq 6 ]; then
					$IPTABLES -A USER-IN -p udp -m udp -i $1 -s fe80::/10 --sport 547 -d fe80::/10 --dport 546 -j ACCEPT -m comment --comment "$ID"
				fi

				shift
				;;

734 735
			ALLOW_SUBNETS)
				checkLink $1
736
				(( $? != 0 )) && error 41 "ALLOW_SUBNETS expects an interface node argument (eg: eth0)"
737

738 739
				probeChains LOCAL
				(( $? != 0 )) && error 42 "The 'LOCAL' chain is missing. Setup a new base firewall with BASE_RULE_SET first."
740

741 742
				ID="ALLOW_SUBNETS_$(getLinkID $1)"
				deleteRules LOCAL "$ID"
743 744 745 746 747 748 749 750 751 752 753 754

				printf "# allowing subnets on $1 link:"
				unset p
				for addr in $(obtainIPs $ENVID global $1); do
					printf " %s" $addr

					subnet=$(obtainNetPrefix $addr $1)

						# omit a subnet when it has been processed already!
					echo "$p" | grep "$subnet" 1>/dev/nul 2>/dev/nul
					(( $? == 0 )) && continue

755 756
					$IPTABLES -I LOCAL 1 -i $1 -s $subnet -j ACCEPT -m comment --comment "$ID"
					$IPTABLES -I LOCAL 1 -o $1 -s $addr -d $subnet -j ACCEPT -m comment --comment "$ID"
757 758 759 760

					p+=" $subnet"
				done

761 762
				printf " (broadcast)"
				$IPTABLES -I LOCAL 1 -i $1 -m pkttype --pkt-type broadcast -j ACCEPT -m comment --comment "$ID (broadcast)"
763 764 765 766 767 768 769

					# for IPv6 on this link
				if [ $ENVID -eq 6 ]; then
					addr=$(obtainIPs $ENVID link $1)
					[[ $addr =~ fe80 ]] || error 43 "A link local address is not available for $1."
					printf " %s" $addr

770
					$IPTABLES -I LOCAL 1 -o $1 -s $addr -j ACCEPT -m comment --comment "$ID (outbound from link-local)"
771 772 773 774 775 776 777 778 779
				fi

				echo ""
				shift
				unset addr; unset subnet; unset p
				;;

			ALLOW_LINK_LOCAL)
				checkLink $1
780
				(( $? != 0 )) && error 41 "ALLOW_LINK_LOCAL expects an interface node argument (eg: eth0)"
781

782
				probeChains LOCAL
783 784 785
				(( $? != 0 )) && error 42 "The 'LOCAL' chain is missing. Setup a new base firewall with BASE_RULE_SET first."

#TODO: check back if interface has configured the correct subnet (169.254.0.0/16 fe80::/10)
786

787
				ID="ALLOW_LINK_LOCAL on $(getLinkID $1)"
788
				deleteRules LOCAL "$ID"
789 790 791 792 793 794

				[ $ENVID -eq 4 ] && sz="169.254.0.0/16"
				[ $ENVID -eq 6 ] && sz="fe80::/10"

				printf "# allowing link local (%s) on %s\n" $sz $1

795
				$IPTABLES -I LOCAL 1 -i $1 -d $sz -j ACCEPT -m comment --comment "$ID"
796 797 798

# This is called ALLOW_LINK_LOCAL don't know why multicast is allowed here too!
#				$IPTABLES -I LOCAL 1 -i $1 -s $sz -m pkttype --pkt-type multicast -j ACCEPT -m comment --comment "$ID (multicast)"
799 800

				$IPTABLES -I LOCAL 1 -o $1 -s $sz -j ACCEPT -m comment --comment "$ID"
801 802 803 804

				shift
				;;

805 806 807 808 809 810 811 812
#			ALLOW_MULTICAST)
#				#-A INPUT -m pkttype --pkt-type multicast -j ACCEPT
#				iptables -I LOCAL 1 -m pkttype --pkt-type multicast -i macvlan0 -s 10.1.0.0/16 -j ACCEPT
#				iptables -O LOCAL 1 -m pkttype --pkt-type multicast -o macvlan0 -d 10.1.0.0/16 -j ACCEPT

#				shift
#				;;

813 814
			ALLOW_SERVICE_DISCOVERY)
				checkLink $1
815
				(( $? != 0 )) && error 36 "ALLOW_SERVICE_DISCOVERY expects an interface node argument (eg: eth0)"
816

817
				probeChains LOCAL
818 819 820 821
				(( $? != 0 )) && error 37 "The 'USER-IN' chain is missing. Setup a new base firewall with BASE_RULE_SET first."

				printf "# allowing service discovery on %s link.\n" $1

822
				ID="ALLOW_SERVICE_DISCOVERY on $(getLinkID $1)" 
823
				deleteRules LOCAL "$ID"
824 825

				if [ $ENVID -eq 4 ]; then
826
					$IPTABLES -I LOCAL 1 -d 224.0.0.251/32 -p udp -m udp -i $1 --dport 5353 -j ACCEPT -m comment --comment "$ID (multicast mDNS)"
827 828
					$IPTABLES -I LOCAL 1 -d 224.0.0.252/32 -p udp -m udp -i $1 --dport 5355 -j ACCEPT -m comment --comment "$ID (multicast LLMNR)"
 
829
					$IPTABLES -I LOCAL 1 -d 239.255.255.250/32 -p udp -m udp -i $1 --dport 1900 -j ACCEPT -m comment --comment "$ID (multicast UPnP)"
830 831
				fi
				if [ $ENVID -eq 6 ]; then
832
					$IPTABLES -I LOCAL 1 -d ff02::fb/128 -p udp -m udp -i $1 --dport 5353 -j ACCEPT -m comment --comment "$ID (multicast mDNS)"
833 834
					$IPTABLES -I LOCAL 1 -d ff02::1:3/128 -p udp -m udp -i $1 --dport 5355 -j ACCEPT -m comment --comment "$ID (multicast LLMNR)"

835
					$IPTABLES -I LOCAL 1 -d ff02::f/128 -p udp -m udp -i $1 --dport 1900 -j ACCEPT -m comment --comment "$ID (multicast UPnP)"
836 837 838 839 840
				fi

				shift;
				;;

841
			ALLOW_TUNNEL)
842 843
				[ $ENVID -eq 6 ] && error 90 "Only useful with IPv4."

844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
				probeChains USER-IN
				(( $? != 0 )) && error 37 "The 'USER-IN' chain is missing. Setup a new base firewall with BASE_RULE_SET first."
				probeChains USER-OUT
				(( $? != 0 )) && error 37 "The 'USER-OUT' chain is missing. Setup a new base firewall with BASE_RULE_SET first."

					# convert mode to lower case
				sz=${1,,}
				[ $sz == "ipip" ] && sz=94
				[ $sz == "gre" ] && sz=47
				[ $sz == "sit" ] && sz=41
				isNaturalNumber $sz
				(( $? != 0 )) && error 37 "ALLOW_TUNNEL expects a tunnel mode (eg: ipip, gre or sit)"

				checkLink $2
				(( $? != 0 )) && error 38 "ALLOW_TUNNEL expects an incoming interface to allow tunnel traffic from (eg: eth0)"

				checkIPArgFormat $3
				(( $? != 0 )) && error 39 "ALLOW_TUNNEL expects a source IP address to allow tunnel traffic from (eg: 192.168.1.1)"

				printf "# allowing '%s' tunnel on %s link from %s.\n" ${1^^} $2 $3

				ID="ALLOW_TUNNEL_${1^^}_$(getLinkID $2)_$3"
				deleteRules USER-IN "$ID"
				deleteRules USER-OUT "$ID"

				$IPTABLES -A USER-IN -i $2 -p $sz -s $3 -j ACCEPT -m comment --comment "$ID"
				$IPTABLES -A USER-OUT -o $2 -p $sz -d $3 -j ACCEPT -m comment --comment "$ID"

				shift; shift; shift;
				;;

875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
			6TO4)
				[ $ENVID -eq 6 ] && error 90 "Only useful with IPv4."

				checkIPArgFormat "$1"
				(( $? != 0 )) && error 91 "6to4 expects a public, routable IPv4 wan address."

				checkLink $2
				(( $? == 0 )) && error 92 "Tunnel name '$2' already in use.\n"

				sz=$(printf "2002:%02x%02x:%02x%02x:e0::1" ${1//./ })
				ip tunnel add $2 mode sit local $1 remote 192.88.99.1 ttl 125 dev eth2
				ip -6 address add $sz dev $2
				ip link set $2 up
				ip -6 route add 2000::/3 dev $2 metric 1

				#ip6tables -I FORWARD 1 -s 2002::/16 -o sit2 -j ACCEPT
				shift; shift;
				;;

			ALLOW_PORT|ALLOW_SERVICE)
					# $1 == port or ports
				ay=${1//,/}
				for sz in $ay; do
					isNaturalNumber $sz
					(( $? != 0 )) && error 70 "ALLOW_PORT expects a destination port or ports argument (eg: 80,443)"
				done

902 903 904
					# $3 == protocol?
				PROTO=$(checkProtocol $3)

905 906 907 908 909 910 911 912 913
					# $2 == device node or IP address?
				checkIPArgFormat "$2"
				if (( $? != 0 )); then
					[[ $2 =~ [:alnum:]+ ]] && error 71 "ALLOW_PORT expects either an IP or interface argument."

					checkLink $2
					(( $? != 0 )) && printf "# WARNING! '$2' currently not available. Maybe invoked later?\n"

					FILTER_BY="-i $2"
914 915
					printf "# allow port(s): %s (%s) to reach %s\n  (WARNING! Ports reachable for any address configured on this device! Maybe this is unintended?)\n" $1 $PROTO $2
					sz="$2"
916 917 918 919 920
				else
					sz=$(obtainNetPrefix $2)
					[ -z $sz ] && error 73 "ALLOW_PORT has no route to '$2' or argument has malformed IP format"

					FILTER_BY="-d $2"
921 922
					printf "# allow port(s): %s (%s) to reach %s on %s\n" $1 $PROTO $2 $(obtainLinkFromSubnetPrefix $sz)
					sz="$(formatAsHexID $2)"
923 924
				fi

925
				ID="ALLOW_PORT_$1_$sz"
926 927
				deleteRules USER-IN "$ID"

928 929 930 931 932 933 934
				if [[ $PROTO =~ any ]]; then
					$IPTABLES -A USER-IN $FILTER_BY -p tcp -m multiport --ports $1 -j ACCEPT -m comment --comment "$ID"
					$IPTABLES -A USER-IN $FILTER_BY -p udp -m multiport --ports $1 -j ACCEPT -m comment --comment "$ID"
				else
					$IPTABLES -A USER-IN $FILTER_BY -p $PROTO -m multiport --ports $1 -j ACCEPT -m comment --comment "$ID"
					shift;
				fi
935 936

				shift; shift;
937
				unset FILTER_BY; unset PROTO; unset ay; unset ID;
938 939
				;;

940 941 942 943 944 945 946 947 948 949 950 951 952 953 954
			FORWARD_SUBNET)
				sz=""
				[ $ENVID -eq 4 ] && sz=" (eg: 192.168.0.0/16)"
				[ $ENVID -eq 6 ] && sz=" (eg: 2001:DB8::/32)"
				checkSubnetArgFormat $1
				(( $? != 0 )) && error 51 "FORWARD_SUBNET expects a subnet/mask argument$sz and a (internal!) LAN device."

				sz=$(obtainLinkFromSubnetPrefix $1)
				(( $? != 0 )) && error 52 " A route to '$1' doesn't exist. Forwarding without a route?"

				checkLink $2
				(( $? != 0 )) && error 53 " No '$2' device found."

				printf "# forwarding %s (%s) to %s.\n" $1 $sz $2

955
				ID="FORWARD_SUBNET $1 on $(getLinkID $sz) to $2"
956 957
				deleteRules FORWARD "$ID"

958 959 960
					# get index position for placing subchains in FORWARD chain
				returnRuleCount FORWARD
				n=$?
961

962 963 964 965 966
				$IPTABLES -I FORWARD $n -i $sz -o $2 -s $1 -j ACCEPT -m comment --comment "$ID"
				$IPTABLES -I FORWARD $n -i $2 -o $sz -d $1 -m state --state RELATED,ESTABLISHED -j ACCEPT -m comment --comment "$ID"
				$IPTABLES -I FORWARD $n -i $2 -o $sz -d $1 -m state --state NEW -j ACCEPT -m comment --comment "$ID"

				unset n
967 968 969 970 971 972 973
				shift; shift
				;;

			FORWARD_SUBNET_PROTECTIVE)
				sz=""
				[ $ENVID -eq 4 ] && sz=" (eg: 192.168.0.0/16)"
				[ $ENVID -eq 6 ] && sz=" (eg: 2001:DB8::/32)"
974

975 976 977 978 979 980 981 982 983 984 985
				checkSubnetArgFormat $1
				(( $? != 0 )) && error 51 "FORWARD_SUBNET_PROTECTIVE expects a subnet/mask argument$sz and a WAN device (eg: ppp0)"

				DEV=$(obtainLinkFromSubnetPrefix $1)
				(( $? != 0 )) && error 52 " A route to '$1' doesn't exist. Forwarding without a route?"

				probeChains ICMP
				(( $? != 0 )) && error 53 "The 'ICMP' chain is missing. Setup a new base firewall with BASE_RULE_SET first."

				sz=""
				checkLink $2
986 987
				(( $? != 0 )) && sz=" (WARNING! '$2' currently not available. Maybe invoked later?)" 
				printf "# forwarding protectively %s (%s) to %s.%s\n" $1 $DEV $2 "$sz"
988

989
				ID=$(printf "FORWARD_SUBNET_PROTECTIVE_%s_%s_%s" $DEV $(formatAsHexID "$1") $2)
990 991
				deleteRules FORWARD "$ID"

992 993
					# for IPv6 subnets the max chain name length can
					# be easily exceeded so we need a shorter name
994
				#CHAIN=$(printf "%s-%s-%s" $(formatAsHexID "$1") $2 $DEV)
995
				CHAIN=$(printf "%X-IN" $(cksum <<<"$ID" | grep -oP "^\d*"))
996 997
				allocChain $CHAIN

998 999 1000 1001
					# get index position for placing subchains in FORWARD chain
				returnRuleCount FORWARD
				n=$?

1002 1003 1004 1005 1006 1007 1008 1009 1010
					# allow ICMP requests from the inner
				[ $ENVID -eq 4 ] && sz="icmp"
				[ $ENVID -eq 6 ] && sz="ipv6-icmp"
				$IPTABLES -A $CHAIN -p $sz -j ICMP

					# drop invalid packets
				$IPTABLES -A $CHAIN -m state --state INVALID -j BLOCK

					# syn-flood protection
1011
				$IPTABLES -A $CHAIN ! -d $1 -p tcp --syn -j ANTI-FLOOD
1012 1013

					# furtive port scanner
1014
				$IPTABLES -A $CHAIN ! -d $1 -p tcp --tcp-flags SYN,ACK,FIN,RST RST -j ANTI-FLOOD
1015 1016

					# allow safe ports for inbound traffic
1017 1018 1019 1020
#				$IPTABLES -A $CHAIN -p tcp -m tcp -m multiport --sports 80,443 -j ACCEPT 

					# forward to the inside for related or established traffic only!
				$IPTABLES -A $CHAIN -m state --state RELATED,ESTABLISHED -j ACCEPT
1021

1022
				$IPTABLES -I FORWARD $n -i $2 -o $DEV -d $1 -j $CHAIN -m comment --comment "$ID"
1023

1024
				CHAIN=$(printf "%X-OUT" $(cksum <<<"$ID" | grep -oP "^\d*"))
1025 1026
				allocChain $CHAIN

1027
					# forward new, related & established traffic outside
1028 1029 1030 1031 1032 1033
				$IPTABLES -A $CHAIN -m state --state RELATED,ESTABLISHED -j ACCEPT
				$IPTABLES -A $CHAIN -m state --state NEW -j ACCEPT

					# allow safe ports for outbound traffic
				$IPTABLES -A $CHAIN -p tcp -m tcp -m multiport --dports 80,443 -j ACCEPT 

1034
				$IPTABLES -I FORWARD $n -i $DEV -o $2 -s $1 -j $CHAIN -m comment --comment "$ID"
1035

1036
				unset sz; unset DEV; unset CHAIN; unset n
1037 1038 1039
				shift; shift
				;;

1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055
			FORWARD_PORT|FORWARD_ROUTING)
				ay=${2//,/}		# check given port list
				for sz in $ay; do
					isNaturalNumber $sz
					(( $? != 0 )) && error 70 "FORWARD_ROUTING expects a destination port argument (eg: 80,443)"
				done

				checkIPArgFormat "$3"
				(( $? != 0 )) && error 71 "FORWARD_ROUTING expects a destination IP to your internal host and an optional destination port (eg: 192.168.1.1[:80])"

					# $1 == device node or IP address?
				checkIPArgFormat "$1"
				if (( $? != 0 )); then
					[[ ! $1 =~ [A-Za-z]{3}[0-9]+ ]] && error 72 "FORWARD_ROUTING expects '$1' to be a device node or an IP address."

					checkLink $1
1056
					(( $? != 0 )) && printf "# WARNING! '$1' currently not available. Maybe invoked later?\n"
1057

1058 1059 1060 1061 1062 1063 1064 1065 1066 1067
					FILTER_BY="-i $1"
				else
					sz=$(obtainNetPrefix $1)
					[ -z $sz ] && error 73 "FORWARD_ROUTING has no route to '$1' or a malformed IP format (eg: 192.168.1.1)"

					FILTER_BY="-i $(obtainLinkFromSubnetPrefix $sz) -d $1"
				fi
					# create a mark ID (cksum)
				MARK_ID=$(printf "0x%x" $(cksum <<<"$1$2$3" | grep -oP "^\d*"))

1068 1069 1070 1071 1072
					# $4 == protocol (tcp|udp) with 'tcp' as default when not specified
				PROTO=$(checkProtocol $4)
				[[ $PROTO =~ any ]] && PROTO=tcp

				printf "# prerouting port fowarding from %s:%s to %s (ID: %s)\n" $1 $2 $3 $MARK_ID
1073 1074

				ID="FORWARD_ROUTING $MARK_ID"
1075
				deleteRules FORWARD "$ID"
1076 1077
				deleteRules -t nat PREROUTING "$ID"
				deleteRules -t nat POSTROUTING "$ID"
1078

1079 1080 1081 1082 1083
						# We mark traffic originating from someone to our receiver IP|device on the
						# specified port only.
						# This may look complicated but makes absolutely sense since our POSTROUTING
						# needs to distinguish from traffic carrying our MARK_ID _and_ traffic on the 
						# corresponding port returned from the forwarded host.
1084 1085
				$IPTABLES -t nat -A PREROUTING $FILTER_BY -p $PROTO -m multiport --dports $2 -j MARK --set-mark $MARK_ID -m comment --comment "$ID"
				$IPTABLES -t nat -A PREROUTING -m mark --mark $MARK_ID -p $PROTO -j DNAT --to $3 -m comment --comment "$ID"
1086 1087 1088 1089 1090 1091 1092 1093 1094 1095

						# prerouting rewrites destination address for marked packets therefore these
						# go through the FORWARD chain instead of INPUT, to avoid having other rules
						# (eg: FORWARD_SUBNET_PROTECTIVE) blocking our marked packets accidentially
						# we allow them to be forwarded explicitly
				$IPTABLES -I FORWARD 1 -m mark --mark $MARK_ID -j ACCEPT -m comment --comment "$ID"

						# allow packets back & forth from the forwarded host
						# Attention! >> These response packets are _not_ carrying our MARK_ID
						# therefore these rules are all inserted at index 1 so order matters!
1096 1097 1098
				[[ $3 =~ : ]] && sz=${3#*:} || sz=$2
				$IPTABLES -I FORWARD 1 -p $PROTO -s ${3%:*}/32 -m multiport --sports $sz -j ACCEPT -m comment --comment "$ID"
				$IPTABLES -I FORWARD 1 -p $PROTO -d ${3%:*}/32 -m multiport --dports $sz -j ACCEPT -m comment --comment "$ID"
1099 1100 1101 1102 1103

						# Masquerade only those carrying our MARK_ID!
				$IPTABLES -t nat -I POSTROUTING 1 -m mark --mark $MARK_ID -d ${3%:*}/32 -j MASQUERADE -m comment --comment "$ID"

				shift; shift; shift;
1104 1105
				[[ $(checkProtocol $4) =~ any ]] && shift
				unset FILTER_BY; unset PROTO; unset MARK_ID;
1106
				unset ID;
1107 1108
				;;

1109 1110 1111 1112 1113 1114 1115
			MAC_FILTER)
				sz=$($IPTABLES -nvL | grep -oP "(?<=Chain\s)\S+" | grep ${1^^})
				[ -z $sz ] && error 90 "MAC_FILTER expects a chain name to place filter in (eg: INPUT, FORWARD, ...)"

				checkMACArgFormat "$2"
				(( $? != 0 )) && error 91 "MAC_FILTER expects an ether address (MAC) (eg: 06:00:17:d3:97:b4)"

1116
				printf "# filtering '%s' MAC address in %s chain.%s\n" $2 $sz $(ip n | grep "$2" >/dev/nul && printf " (WARNING! Mac address appears to be a host of the local network.)")
1117

1118 1119 1120
				sy=${2//:/}
				sy=${sy,,}
				ID=$(printf "MAC_FILTER_%s_%s" $sz $sy)
1121
				deleteRules $sz "$ID"
1122

1123 1124
				$IPTABLES -I $sz 1 -m mac --mac-source $2 -j DROP -m comment --comment "$ID"

1125
				unset sz; unset sy;
1126
				shift; shift
1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
				;;

			TRANSPARENT_PROXY)
				#$IPTABLES -t nat -A PREROUTING -i $LAN -p tcp --dport 80 -j DNAT --to $SQUID_SERVER:$SQUID_PORT
				#$IPTABLES -t nat -A PREROUTING -i $WAN -p tcp --dport 80 -j REDIRECT --to-port $SQUID_PORT
				;;

			POSTROUTING_MASQUERADE)
				[ $ENVID -ne 4 ] && printf "# Masquerading (NAT) is only useful for IPv4!"

				checkSubnetArgFormat $1
				(( $? != 0 )) && error 61 "POSTROUTING_MASQUERADE expects a subnet/mask on an outgoing device argument (eg: 192.168.0.0/16 ppp0)"

				sz=""
				checkLink $2
1142 1143
				(( $? != 0 )) && sz=" (WARNING! '$2' currently not available. Maybe invoked later?)" 
				printf "# masquerading %s when leaving through %s.%s\n" $1 $2 "$sz"
1144

1145
				ID=$(printf "POSTROUTING_MASQUERADE %s_%s_%s" $(obtainLinkFromSubnetPrefix $1) $(formatAsHexID "$1") $2)
1146 1147 1148 1149 1150 1151 1152 1153
				deleteRules -t nat POSTROUTING "$ID"

				$IPTABLES -t nat -A POSTROUTING -s $1 -o $2 -j MASQUERADE -m comment --comment "$ID"

				shift; shift;
				unset ID;
				;;

1154 1155 1156
			PORT_KNOCKING)
				# http://stackoverflow.com/questions/15451009/port-knocking-using-iptables
				;;
1157

1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168
			DEBUG_CHAIN)
				$IPTABLES -nvL $1 1>/dev/nul 2>/dev/nul
				(( $? != 0 )) && error 83 "DEBUG_CHAIN expects a chain name argument. A chain called '$1' was not found."

				ID="DEBUG_CHAIN $1" 
				deleteRules $1 "$ID"

				$IPTABLES -I $1 1 -j LOG --log-prefix "DEBUG '$1': " -m comment --comment "$ID"
				shift;
				;;

1169 1170 1171 1172 1173 1174
			REMOVE_RULES)
				printf "# Removing rules containing '$1' in:"
				for sz in $($IPTABLES -nvL | grep -o -P "(?<=Chain\s)\S+"); do
					$IPTABLES -nvL $sz | grep "$1" 1>/dev/nul 2>/dev/nul
					if (( $? == 0 )); then
						deleteRules $sz "$1"
1175
						(( $? != 0 )) && printf " $sz"
1176 1177 1178 1179 1180 1181
					fi
				done
				for sz in $($IPTABLES -t nat -nvL | grep -o -P "(?<=Chain\s)\S+"); do
					$IPTABLES -t nat -nvL $sz | grep "$1" 1>/dev/nul 2>/dev/nul
					if (( $? == 0 )); then
						deleteRules -t nat $sz "$1"
1182
						(( $? != 0 )) && printf " $sz"
1183 1184 1185
					fi
				done
				echo " ... done"
1186

1187 1188 1189 1190 1191 1192
				printf "# Deallocating orphaned chains:"
				for sz in $($IPTABLES -nvL | grep -o -P "(?<=Chain\s)\S+(?=\s\(0 ref)"); do
					printf " $sz"
					deallocChain $sz
				done
				echo " ... done"
1193

1194 1195
				unset sz;
				shift;
1196 1197
				;;

1198 1199 1200 1201
			VERBOSE|LIST_RULES|SHOW_RULES)
				$IPTABLES-save
				;;

1202 1203 1204 1205 1206 1207 1208 1209
#			MARK_PACKET)
#				# mark a packet with a value of 1
#				$IPTABLES -A INPUT -m state --state INVALID -j MARK --set-mark 1
#
#				# packet is marked with our value?
#				$IPTABLES -A INPUT -m mark --mark 0x1 -j BLOCK
#				;;

1210 1211 1212 1213 1214 1215 1216 1217
			*)
				echo "(ERROR) >> Unknown argument: '$arg'"
				printAbout
				;;
		esac

	done

1218