1############################################################################ 2# Copyright (C) Internet Systems Consortium, Inc. ("ISC") 3# 4# This Source Code Form is subject to the terms of the Mozilla Public 5# License, v. 2.0. If a copy of the MPL was not distributed with this 6# file, you can obtain one at https://mozilla.org/MPL/2.0/. 7# 8# See the COPYRIGHT file distributed with this work for additional 9# information regarding copyright ownership. 10############################################################################ 11 12############################################################################ 13# 14# This tool allows an arbitrary number of TCP connections to be made to the 15# specified service and to keep them open until told otherwise. It is 16# controlled by writing text commands to a TCP socket (default port: 5309). 17# 18# Currently supported commands: 19# 20# - open <COUNT> <HOST> <PORT> 21# 22# Opens <COUNT> TCP connections to <HOST>:<PORT> and keeps them open. 23# <HOST> must be an IP address (IPv4 or IPv6). 24# 25# - close <COUNT> 26# 27# Close the oldest <COUNT> previously established connections. 28# 29############################################################################ 30 31from __future__ import print_function 32 33import datetime 34import errno 35import os 36import select 37import signal 38import socket 39import sys 40import time 41 42 43# Timeout for establishing all connections requested by a single 'open' command. 44OPEN_TIMEOUT = 2 45VERSION_QUERY = b'\x00\x1e\xaf\xb8\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07version\x04bind\x00\x00\x10\x00\x03' 46 47def log(msg): 48 print(datetime.datetime.now().strftime('%d-%b-%Y %H:%M:%S.%f ') + msg) 49 50 51def open_connections(active_conns, count, host, port): 52 queued = [] 53 errors = [] 54 55 try: 56 socket.inet_aton(host) 57 family = socket.AF_INET 58 except socket.error: 59 family = socket.AF_INET6 60 61 log('Opening %d connections...' % count) 62 63 for _ in range(count): 64 sock = socket.socket(family, socket.SOCK_STREAM) 65 sock.setblocking(0) 66 err = sock.connect_ex((host, port)) 67 if err not in (0, errno.EINPROGRESS): 68 log('%s on connect for socket %s' % (errno.errorcode[err], sock)) 69 errors.append(sock) 70 else: 71 queued.append(sock) 72 73 start = time.time() 74 while queued: 75 now = time.time() 76 time_left = OPEN_TIMEOUT - (now - start) 77 if time_left <= 0: 78 break 79 _, wsocks, _ = select.select([], queued, [], time_left) 80 for sock in wsocks: 81 queued.remove(sock) 82 err = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) 83 if err: 84 log('%s for socket %s' % (errno.errorcode[err], sock)) 85 errors.append(sock) 86 else: 87 sock.send(VERSION_QUERY) 88 active_conns.append(sock) 89 90 if errors: 91 log('result=FAIL: %d connection(s) failed' % len(errors)) 92 elif queued: 93 log('result=FAIL: Timed out, aborting %d pending connections' % len(queued)) 94 for sock in queued: 95 sock.close() 96 else: 97 log('result=OK: Successfully opened %d connections' % count) 98 99 100def close_connections(active_conns, count): 101 log('Closing %s connections...' % "all" if count == 0 else str(count)) 102 if count == 0: 103 count = len(active_conns) 104 for _ in range(count): 105 sock = active_conns.pop(0) 106 sock.close() 107 log('result=OK: Successfully closed %d connections' % count) 108 109 110def sigterm(*_): 111 log('SIGTERM received, shutting down') 112 os.remove('ans.pid') 113 sys.exit(0) 114 115 116def main(): 117 active_conns = [] 118 119 signal.signal(signal.SIGTERM, sigterm) 120 121 with open('ans.pid', 'w') as pidfile: 122 print(os.getpid(), file=pidfile) 123 124 listenip = '10.53.0.6' 125 try: 126 port = int(os.environ['CONTROLPORT']) 127 except KeyError: 128 port = 5309 129 130 log('Listening on %s:%d' % (listenip, port)) 131 132 ctlsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 133 ctlsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 134 ctlsock.bind((listenip, port)) 135 ctlsock.listen(1) 136 137 while True: 138 (clientsock, _) = ctlsock.accept() 139 log('Accepted control connection from %s' % clientsock) 140 cmdline = clientsock.recv(512).decode('ascii').strip() 141 if cmdline: 142 log('Received command: %s' % cmdline) 143 cmd = cmdline.split() 144 if cmd[0] == 'open': 145 count, host, port = cmd[1:] 146 open_connections(active_conns, int(count), host, int(port)) 147 elif cmd[0] == 'close': 148 (count, ) = cmd[1:] 149 close_connections(active_conns, int(count)) 150 else: 151 log('result=FAIL: Unknown command') 152 clientsock.close() 153 154 155if __name__ == '__main__': 156 main() 157