1#!/usr/local/bin/bash 2# 3# portblock: iptables temporary portblocking control 4# 5# Author: Sun Jiang Dong (initial version) 6# Philipp Reisner (per-IP filtering) 7# 8# License: GNU General Public License (GPL) 9# 10# Copyright: (C) 2005 International Business Machines 11# 12# OCF parameters are as below: 13# OCF_RESKEY_protocol 14# OCF_RESKEY_portno 15# OCF_RESKEY_action 16# OCF_RESKEY_ip 17# OCF_RESKEY_tickle_dir 18# OCF_RESKEY_sync_script 19####################################################################### 20# Initialization: 21 22: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat} 23. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs 24 25# Defaults 26OCF_RESKEY_protocol_default="" 27OCF_RESKEY_portno_default="" 28OCF_RESKEY_action_default="" 29OCF_RESKEY_ip_default="0.0.0.0/0" 30OCF_RESKEY_reset_local_on_unblock_stop_default="false" 31OCF_RESKEY_tickle_dir_default="" 32OCF_RESKEY_sync_script_default="" 33 34: ${OCF_RESKEY_protocol=${OCF_RESKEY_protocol_default}} 35: ${OCF_RESKEY_portno=${OCF_RESKEY_portno_default}} 36: ${OCF_RESKEY_action=${OCF_RESKEY_action_default}} 37: ${OCF_RESKEY_ip=${OCF_RESKEY_ip_default}} 38: ${OCF_RESKEY_reset_local_on_unblock_stop=${OCF_RESKEY_reset_local_on_unblock_stop_default}} 39: ${OCF_RESKEY_tickle_dir=${OCF_RESKEY_tickle_dir_default}} 40: ${OCF_RESKEY_sync_script=${OCF_RESKEY_sync_script_default}} 41####################################################################### 42CMD=`basename $0` 43TICKLETCP=$HA_BIN/tickle_tcp 44 45usage() 46{ 47 cat <<END >&2 48 usage: $CMD {start|stop|status|monitor|meta-data|validate-all} 49 50 $CMD is used to temporarily block ports using iptables. 51 52 It can be used to blackhole a port before bringing 53 up an IP address, and enable it after a service is started. 54 To do that for samba, the following can be used: 55 56 crm configure <<EOF 57 primitive portblock-samba ocf:heartbeat:portblock \\ 58 params protocol=tcp portno=137,138 action=block 59 primitive portunblock-samba ocf:heartbeat:portblock \\ 60 params protocol=tcp portno=137,138 action=unblock 61 primitive samba-vip ocf:heartbeat:IPaddr2 \\ 62 params ip=10.10.10.20 63 group g-samba \\ 64 portblock-samba samba-vip nmbd smbd portunblock-samba 65 EOF 66 67 This will do the following things: 68 69 - DROP all incoming packets for TCP ports 137 and 138 70 - Bring up the IP alias 10.10.10.20 71 - start the nmbd and smbd services 72 - Re-enable TCP ports 137 and 138 73 (enable normal firewall rules on those ports) 74 75 This prevents clients from getting TCP RST if they try to reconnect 76 to the service after the alias is enabled but before nmbd and smbd 77 are running. These packets will cause some clients to give up 78 attempting to reconnect to the server. 79 80 Attempts to connect to UDP and other non-TCP ports which have nothing 81 listening can result in ICMP port unreachable responses, which can 82 have the same undesirable affect on some clients. 83 84 NOTE: iptables is Linux-specific. 85 86 An additional feature in the portblock RA is the tickle ACK function 87 enabled by specifying the tickle_dir parameter. The tickle ACK 88 triggers the clients to faster reconnect their TCP connections to the 89 fail-overed server. 90 91 Please note that this feature is often used for the floating IP fail- 92 over scenario where the long-lived TCP connections need to be tickled. 93 It doesn't support the cluster alias IP scenario. 94 95 When using the tickle ACK function, in addition to the normal usage 96 of portblock RA, the parameter tickle_dir must be specified in the 97 action=unblock instance of the portblock resources. 98 For example, you may stack resources like below: 99 portblock action=block 100 services 101 portblock action=unblock tickle_dir=/tickle/state/dir 102 103 If you want to tickle all the TCP connections which connected to _one_ 104 floating IP but different ports, no matter how many portblock resources 105 you have defined, you should enable tickles for _one_ portblock 106 resource(action=unblock) only. 107 108 The tickle_dir is a location which stores the established TCP 109 connections. It can be a shared directory(which is cluster-visible to 110 all nodes) or a local directory. 111 If you use the shared directory, you needn't do any other things. 112 If you use the local directory, you must also specify the sync_script 113 paramater. We recommend you to use csync2 as the sync_script. 114 For example, if you use the local directory /tmp/tickle as tickle_dir, 115 you could setup the csync2 as the csync2 documentation says and 116 configure your /etc/csync2/csync2.cfg like: 117 group ticklegroup { 118 host node1; 119 host node2; 120 key /etc/csync2/ticklegroup.key; 121 include /etc/csync2/csync2.cfg; 122 include /tmp/tickle; 123 auto younger; 124 } 125 Then specify the parameter sync_script as "csync2 -xv". 126 127END 128} 129 130meta_data() { 131 cat <<END 132<?xml version="1.0"?> 133<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd"> 134<resource-agent name="portblock"> 135<version>1.0</version> 136 137<longdesc lang="en"> 138Resource script for portblock. It is used to temporarily block ports 139using iptables. In addition, it may allow for faster TCP reconnects 140for clients on failover. Use that if there are long lived TCP 141connections to an HA service. This feature is enabled by setting the 142tickle_dir parameter and only in concert with action set to unblock. 143Note that the tickle ACK function is new as of version 3.0.2 and 144hasn't yet seen widespread use. 145</longdesc> 146<shortdesc lang="en">Block and unblocks access to TCP and UDP ports</shortdesc> 147 148<parameters> 149<parameter name="protocol" unique="0" required="1"> 150<longdesc lang="en"> 151The protocol used to be blocked/unblocked. 152</longdesc> 153<shortdesc lang="en">protocol</shortdesc> 154<content type="string" default="${OCF_RESKEY_protocol_default}" /> 155</parameter> 156 157<parameter name="portno" unique="0" required="1"> 158<longdesc lang="en"> 159The port number used to be blocked/unblocked. 160</longdesc> 161<shortdesc lang="en">portno</shortdesc> 162<content type="string" default="${OCF_RESKEY_portno_default}" /> 163</parameter> 164 165<parameter name="action" unique="0" required="1"> 166<longdesc lang="en"> 167The action (block/unblock) to be done on the protocol::portno. 168</longdesc> 169<shortdesc lang="en">action</shortdesc> 170<content type="string" default="${OCF_RESKEY_action_default}" /> 171</parameter> 172 173<parameter name="reset_local_on_unblock_stop" unique="0" required="0"> 174<longdesc lang="en"> 175If for some reason the long lived server side TCP sessions won't be cleaned up 176by a reconfiguration/flush/stop of whatever services this portblock protects, 177they would linger in the connection table, even after the IP is gone 178and services have been switched over to another node. 179 180An example would be the default NFS kernel server. 181 182These "known" connections may seriously confuse and delay a later switchback. 183 184Enabling this option will cause this agent to try to get rid of these connections 185by injecting a temporary iptables rule to TCP-reset outgoing packets from the 186blocked ports, and additionally tickle them locally, 187just before it starts to DROP incoming packets on "unblock stop". 188</longdesc> 189<shortdesc lang="en">(try to) reset server TCP sessions when unblock stops</shortdesc> 190<content type="boolean" default="${OCF_RESKEY_reset_local_on_unblock_stop_default}" /> 191</parameter> 192 193<parameter name="ip" unique="0" required="0"> 194<longdesc lang="en"> 195The IP address used to be blocked/unblocked. 196</longdesc> 197<shortdesc lang="en">ip</shortdesc> 198<content type="string" default="${OCF_RESKEY_ip_default}" /> 199</parameter> 200 201<parameter name="tickle_dir" unique="0" required="0"> 202<longdesc lang="en"> 203The shared or local directory (_must_ be absolute path) which 204stores the established TCP connections. 205</longdesc> 206<shortdesc lang="en">Tickle directory</shortdesc> 207<content type="string" default="${OCF_RESKEY_tickle_dir_default}" /> 208</parameter> 209 210<parameter name="sync_script" unique="0" required="0"> 211<longdesc lang="en"> 212If the tickle_dir is a local directory, then the TCP connection state 213file has to be replicated to other nodes in the cluster. It can be 214csync2 (default), some wrapper of rsync, or whatever. It takes the 215file name as a single argument. For csync2, set it to "csync2 -xv". 216</longdesc> 217<shortdesc lang="en">Connection state file synchronization script</shortdesc> 218<content type="string" default="${OCF_RESKEY_sync_script_default}" /> 219</parameter> 220</parameters> 221 222<actions> 223<action name="start" timeout="20s" /> 224<action name="stop" timeout="20s" /> 225<action name="status" depth="0" timeout="10s" interval="10s" /> 226<action name="monitor" depth="0" timeout="10s" interval="10s" /> 227<action name="meta-data" timeout="5s" /> 228<action name="validate-all" timeout="5s" /> 229</actions> 230</resource-agent> 231END 232} 233 234 235# 236# Because this is the normal usage, we consider "block" 237# resources to be pseudo-resources -- that is, their status can't 238# be reliably determined through external means. 239# This is because we expect an "unblock" resource to come along 240# and disable us -- but we're still in some sense active... 241# 242 243#active_grep_pat {udp|tcp} portno,portno 244active_grep_pat() 245{ 246 w="[ ][ ]*" 247 any="0\\.0\\.0\\.0/0" 248 echo "^DROP${w}${1}${w}--${w}${any}${w}${3}${w}multiport${w}dports${w}${2}\>" 249} 250 251#chain_isactive {udp|tcp} portno,portno ip 252chain_isactive() 253{ 254 PAT=`active_grep_pat "$1" "$2" "$3"` 255 $IPTABLES $wait -n -L INPUT | grep "$PAT" >/dev/null 256} 257 258save_tcp_connections() 259{ 260 [ -z "$OCF_RESKEY_tickle_dir" ] && return 261 statefile=$OCF_RESKEY_tickle_dir/$OCF_RESKEY_ip 262 if [ -z "$OCF_RESKEY_sync_script" ]; then 263 netstat -tn |awk -F '[:[:space:]]+' ' 264 $8 == "ESTABLISHED" && $4 == "'$OCF_RESKEY_ip'" \ 265 {printf "%s:%s\t%s:%s\n", $4,$5, $6,$7}' | 266 dd of="$statefile".new conv=fsync status=none && 267 mv "$statefile".new "$statefile" 268 else 269 netstat -tn |awk -F '[:[:space:]]+' ' 270 $8 == "ESTABLISHED" && $4 == "'$OCF_RESKEY_ip'" \ 271 {printf "%s:%s\t%s:%s\n", $4,$5, $6,$7}' \ 272 > $statefile 273 $OCF_RESKEY_sync_script $statefile > /dev/null 2>&1 & 274 fi 275} 276 277tickle_remote() 278{ 279 [ -z "$OCF_RESKEY_tickle_dir" ] && return 280 echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle 281 f=$OCF_RESKEY_tickle_dir/$OCF_RESKEY_ip 282 [ -r $f ] || return 283 $TICKLETCP -n 3 < $f 284} 285 286tickle_local() 287{ 288 [ -z "$OCF_RESKEY_tickle_dir" ] && return 289 f=$OCF_RESKEY_tickle_dir/$OCF_RESKEY_ip 290 [ -r $f ] || return 291 292 checkcmd="netstat -tn" 293 if ! have_binary "netstat"; then 294 checkcmd="ss -Htn" 295 fi 296 297 # swap "local" and "remote" address, 298 # so we tickle ourselves. 299 # We set up a REJECT with tcp-reset before we do so, so we get rid of 300 # the no longer wanted potentially long lived "ESTABLISHED" connection 301 # entries on the IP we are going to delet in a sec. These would get in 302 # the way if we switch-over and then switch-back in quick succession. 303 local i 304 awk '{ print $2, $1; }' $f | $TICKLETCP 305 $checkcmd | grep -Fw $OCF_RESKEY_ip || return 306 for i in 0.1 0.5 1 2 4 ; do 307 sleep $i 308 awk '{ print $2, $1; }' $f | $TICKLETCP 309 $checkcmd | grep -Fw $OCF_RESKEY_ip || break 310 done 311} 312 313SayActive() 314{ 315 echo "$CMD DROP rule for INPUT chain [$*] is running (OK)" 316} 317 318SayConsideredActive() 319{ 320 echo "$CMD DROP rule for INPUT chain [$*] considered to be running (OK)" 321} 322 323SayInactive() 324{ 325 echo "$CMD DROP rule for INPUT chain [$*] is inactive" 326} 327 328#IptablesStatus {udp|tcp} portno,portno ip {block|unblock} 329IptablesStatus() { 330 local rc 331 rc=$OCF_ERR_GENERIC 332 activewords="$CMD $1 $2 is running (OK)" 333 if chain_isactive "$1" "$2" "$3"; then 334 case $4 in 335 block) 336 SayActive $* 337 rc=$OCF_SUCCESS 338 ;; 339 *) 340 SayInactive $* 341 rc=$OCF_NOT_RUNNING 342 ;; 343 esac 344 else 345 case $4 in 346 block) 347 if ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" status; then 348 SayConsideredActive $* 349 rc=$OCF_SUCCESS 350 else 351 SayInactive $* 352 rc=$OCF_NOT_RUNNING 353 fi 354 ;; 355 356 *) 357 if ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" status; then 358 SayActive $* 359 #This is only run on real monitor events. 360 save_tcp_connections 361 rc=$OCF_SUCCESS 362 else 363 SayInactive $* 364 rc=$OCF_NOT_RUNNING 365 fi 366 ;; 367 esac 368 fi 369 370 return $rc 371} 372 373#IptablesBLOCK {udp|tcp} portno,portno ip 374IptablesBLOCK() 375{ 376 local rc=0 377 local try_reset=false 378 if [ "$1/$4/$__OCF_ACTION" = tcp/unblock/stop ] && 379 ocf_is_true $reset_local_on_unblock_stop 380 then 381 try_reset=true 382 fi 383 if 384 chain_isactive "$1" "$2" "$3" 385 then 386 : OK -- chain already active 387 else 388 if $try_reset ; then 389 $IPTABLES $wait -I OUTPUT -p "$1" -s "$3" -m multiport --sports "$2" -j REJECT --reject-with tcp-reset 390 tickle_local 391 fi 392 $IPTABLES $wait -I INPUT -p "$1" -d "$3" -m multiport --dports "$2" -j DROP 393 rc=$? 394 if $try_reset ; then 395 $IPTABLES $wait -D OUTPUT -p "$1" -s "$3" -m multiport --sports "$2" -j REJECT --reject-with tcp-reset 396 fi 397 fi 398 399 return $rc 400} 401 402#IptablesUNBLOCK {udp|tcp} portno,portno ip 403IptablesUNBLOCK() 404{ 405 if 406 chain_isactive "$1" "$2" "$3" 407 then 408 $IPTABLES $wait -D INPUT -p "$1" -d "$3" -m multiport --dports "$2" -j DROP 409 else 410 : Chain Not active 411 fi 412 413 return $? 414} 415 416#IptablesStart {udp|tcp} portno,portno ip {block|unblock} 417IptablesStart() 418{ 419 ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" start 420 case $4 in 421 block) IptablesBLOCK "$@";; 422 unblock) 423 IptablesUNBLOCK "$@" 424 rc=$? 425 tickle_remote 426 #ignore run_tickle_tcp exit code! 427 return $rc 428 ;; 429 *) usage; return 1; 430 esac 431 432 return $? 433} 434 435#IptablesStop {udp|tcp} portno,portno ip {block|unblock} 436IptablesStop() 437{ 438 ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" stop 439 case $4 in 440 block) IptablesUNBLOCK "$@";; 441 unblock) 442 save_tcp_connections 443 IptablesBLOCK "$@" 444 ;; 445 *) usage; return 1;; 446 esac 447 448 return $? 449} 450 451# 452# Check if the port is valid, this function code is not decent, but works 453# 454CheckPort() { 455# Examples of valid port: "1080", "1", "0080" 456# Examples of invalid port: "1080bad", "0", "0000", "" 457 echo $1 |egrep -qx '[0-9]+(:[0-9]+)?(,[0-9]+(:[0-9]+)?)*' 458} 459 460IptablesValidateAll() 461{ 462 check_binary $IPTABLES 463 case $protocol in 464 tcp|udp) 465 ;; 466 *) 467 ocf_log err "Invalid protocol $protocol!" 468 exit $OCF_ERR_CONFIGURED 469 ;; 470 esac 471 472 if CheckPort "$portno"; then 473 : 474 else 475 ocf_log err "Invalid port number $portno!" 476 exit $OCF_ERR_CONFIGURED 477 fi 478 479 if [ -n "$OCF_RESKEY_tickle_dir" ]; then 480 if [ x"$action" != x"unblock" ]; then 481 ocf_log err "Tickles are only useful with action=unblock!" 482 exit $OCF_ERR_CONFIGURED 483 fi 484 if [ ! -d "$OCF_RESKEY_tickle_dir" ]; then 485 ocf_log err "The tickle dir doesn't exist!" 486 exit $OCF_ERR_INSTALLED 487 fi 488 fi 489 490 case $action in 491 block|unblock) 492 ;; 493 *) 494 ocf_log err "Invalid action $action!" 495 exit $OCF_ERR_CONFIGURED 496 ;; 497 esac 498 499 if ocf_is_true $reset_local_on_unblock_stop; then 500 if [ $action != unblock ] ; then 501 ocf_log err "reset_local_on_unblock_stop is only relevant with action=unblock" 502 exit $OCF_ERR_CONFIGURED 503 fi 504 if [ -z $OCF_RESKEY_tickle_dir ] ; then 505 ocf_log warn "reset_local_on_unblock_stop works best with tickle_dir enabled as well" 506 fi 507 fi 508 509 return $OCF_SUCCESS 510} 511 512if 513 ( [ $# -ne 1 ] ) 514then 515 usage 516 exit $OCF_ERR_ARGS 517fi 518 519case $1 in 520 meta-data) meta_data 521 exit $OCF_SUCCESS 522 ;; 523 524 usage) usage 525 exit $OCF_SUCCESS 526 ;; 527 *) ;; 528esac 529 530if [ -z "$OCF_RESKEY_protocol" ]; then 531 ocf_log err "Please set OCF_RESKEY_protocol" 532 exit $OCF_ERR_CONFIGURED 533fi 534 535if [ -z "$OCF_RESKEY_portno" ]; then 536 ocf_log err "Please set OCF_RESKEY_portno" 537 exit $OCF_ERR_CONFIGURED 538fi 539 540if [ -z "$OCF_RESKEY_action" ]; then 541 ocf_log err "Please set OCF_RESKEY_action" 542 exit $OCF_ERR_CONFIGURED 543fi 544 545# iptables v1.4.20+ is required to use -w (wait) 546version=$(iptables -V | awk -F ' v' '{print $NF}') 547ocf_version_cmp "$version" "1.4.19.1" 548if [ "$?" -eq "2" ]; then 549 wait="-w" 550else 551 wait="" 552fi 553 554protocol=$OCF_RESKEY_protocol 555portno=$OCF_RESKEY_portno 556action=$OCF_RESKEY_action 557ip=$OCF_RESKEY_ip 558reset_local_on_unblock_stop=$OCF_RESKEY_reset_local_on_unblock_stop 559 560case $1 in 561 start) 562 IptablesStart $protocol $portno $ip $action 563 ;; 564 565 stop) 566 IptablesStop $protocol $portno $ip $action 567 ;; 568 569 status|monitor) 570 IptablesStatus $protocol $portno $ip $action 571 ;; 572 573 validate-all) 574 IptablesValidateAll 575 ;; 576 577 *) usage 578 exit $OCF_ERR_UNIMPLEMENTED 579 ;; 580esac 581 582exit $? 583