ctdb-scripts: Use ss instead of netstat for finding TCP connections
authorMartin Schwenke <martin@meltin.net>
Thu, 27 Aug 2015 03:22:49 +0000 (13:22 +1000)
committerAmitay Isaacs <amitay@samba.org>
Sun, 17 Apr 2016 11:54:13 +0000 (13:54 +0200)
ss with a filter is much faster than post-processing output from
netstat.  CTDB already has a hard dependency on iproute2 for IP
address handling, so depending on ss is no big deal.

Signed-off-by: Martin Schwenke <martin@meltin.net>
Reviewed-by: Amitay Isaacs <amitay@gmail.com>
ctdb/config/functions
ctdb/tests/eventscripts/stubs/ss [new file with mode: 0755]

index 782978dad842833608460a2c904b29bbfb1739c3..8a8ee8c89f6be53de8b56e4c0a2214690a61c1f2 100755 (executable)
@@ -531,11 +531,7 @@ get_tcp_connections_for_ip ()
 {
     _ip="$1"
 
-    netstat -tn | awk -v ip=$_ip \
-       'index($1, "tcp") == 1 && \
-        (index($4, ip ":") == 1 || index($4, "::ffff:" ip ":") == 1) \
-        && $6 == "ESTABLISHED" \
-        {print $4" "$5}'
+    ss -tn state established "src [$_ip]" | awk 'NR > 1 {print $3, $4}'
 }
 
 ########################################################
@@ -1183,17 +1179,24 @@ update_tickles ()
        # What public IPs do I hold?
        _ips=$(ctdb -X ip | awk -F'|' -v pnn=$pnn '$3 == pnn {print $2}')
 
-       # IPs as a regexp choice
-       _ipschoice="($(echo $_ips | sed -e 's/ /|/g' -e 's/\./\\\\./g'))"
+       # IPs and port as ss filters
+       _ip_filter=""
+       for _ip in $_ips ; do
+           _ip_filter="${_ip_filter}${_ip_filter:+ || }src [${_ip}]"
+       done
+       _port_filter="sport == :${_port}"
 
        # Record connections to our public IPs in a temporary file.
        # This temporary file is in CTDB's private state directory and
        # $$ is used to avoid a very rare race involving CTDB's script
        # debugging.  No security issue, nothing to see here...
        _my_connections="${tickledir}/${_port}.connections.$$"
-       netstat -tn |
-       awk -v destpat="^${_ipschoice}:${_port}\$" \
-         '$1 == "tcp" && $6 == "ESTABLISHED" && $4 ~ destpat {print $5, $4}' |
+       # Parentheses are needed around the filters for precedence but
+       # the parentheses can't be empty!
+       ss -tn state established \
+          "${_ip_filter:+( ${_ip_filter} )}" \
+          "${_port_filter:+( ${_port_filter} )}" |
+       awk 'NR > 1 {print $4, $3}' |
        sort >"$_my_connections"
 
        # Record our current tickles in a temporary file
diff --git a/ctdb/tests/eventscripts/stubs/ss b/ctdb/tests/eventscripts/stubs/ss
new file mode 100755 (executable)
index 0000000..e8d8044
--- /dev/null
@@ -0,0 +1,88 @@
+#!/bin/bash
+
+prog="ss"
+
+usage ()
+{
+    cat >&2 <<EOF
+Usage: $prog -tn state established [ '(' ip-filter ')' ] [ '(' port-filter ')' ]
+
+A fake ss stub that prints items depending on the variables
+FAKE_NETSTAT_TCP_ESTABLISHED and FAKE_NETSTAT_TCP_ESTABLISHED_FILE.
+
+Note that "-tn state established" must be given.
+
+EOF
+    exit 1
+}
+
+if [ "$1" != "-tn" -o "$2" != "state" -o "$3" != "established" ] ; then
+    usage
+fi
+
+shift 3
+
+# Check if socket has matches in both ok_ips and ok_ports
+filter_socket ()
+{
+    ok_ips="$1"
+    ok_ports="$2"
+    socket="$3"
+
+    ip="${socket%:*}"
+    port="${socket##*:}"
+
+    if [ "$ok_ports" != "|" -a "${ok_ports#*|${port}|}" = "$ok_ports" ] ; then
+       return 1
+    fi
+    if [ "$ok_ips" != "|" -a "${ok_ips#*|${ip}|}" = "$ok_ips" ] ; then
+       return 1
+    fi
+
+    return 0
+}
+
+ss_tcp_established ()
+{
+    echo "Recv-Q Send-Q Local Address:Port Peer Address:Port"
+
+    # Very limited implementation:
+    # We only expect to find || inside parentheses
+    # We don't expect to see && - it is implied by juxtaposition
+    # Operator for port comparison is ignored and assumed to be ==
+
+    # Build lists of source ports and source IP addresses where each
+    # entry is surrounded by '|' characters.  These lists can be
+    # easily "searched" using the POSIX prefix and suffix removal
+    # operators.
+    in_parens=false
+    sports="|"
+    srcs="|"
+
+    while [ -n "$1" ] ; do
+       case "$1" in
+           \() in_parens=true ; shift ;;
+           \)) in_parens=false ; shift ;;
+           \|\|) if ! $in_parens ; then usage ; fi ; shift ;;
+           sport) p="${3#:}" ; sports="${sports}${p}|" ; shift 3 ;;
+           src) ip="${2#\[}" ; ip="${ip%\]}" ; srcs="${srcs}${ip}|" ; shift 2 ;;
+           *) usage ;;
+       esac
+    done
+
+    for i in $FAKE_NETSTAT_TCP_ESTABLISHED ; do
+       src="${i%|*}"
+       dst="${i#*|}"
+       if filter_socket "$srcs" "$sports" "$src" ; then
+           echo 0 0 "$src" "$dst"
+       fi
+    done
+    while read src dst ; do
+       if filter_socket "$srcs" "$sports" "$src" ; then
+           echo 0 0 "$src" "$dst"
+       fi
+    done <"$FAKE_NETSTAT_TCP_ESTABLISHED_FILE"
+}
+
+# Yes, lose the quoting so we can do a hacky parsing job
+ss_tcp_established $*