Skip to content

Instantly share code, notes, and snippets.

@amirkdv
Last active August 29, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amirkdv/7137442ae849b776e423 to your computer and use it in GitHub Desktop.
Save amirkdv/7137442ae849b776e423 to your computer and use it in GitHub Desktop.
Generate deterministic *nix ports for services

Example usage:

$ python gimme_port.py
usage: gimme_port.py [-r|--resolve] NAME

$ python gimme_port.py muhc.ca.http
29595

$ python gimme_port.py staging.muhc.ca.http
port 31079 is in use by 'nc' (pid: 28569), use --resolve

$ python gimme_port.py --resolve staging.muhc.ca.http
42820
#!/usr/bin/env python
# exit codes: 0 success, 1 bad usage, 2 port in use
import sys
import subprocess
import hashlib
import os
# upper and lower bounds on generated port numbers
PORT_LOWER = 2000
PORT_UPPER = 65535
def err_usage():
sys.stderr.write('usage: %s [-r|--resolve] NAME\n' % sys.argv[0])
sys.exit(1)
def err_port_in_use(port, pid, prog):
msg = "port %d is in use by '%s' (pid: %s), use --resolve\n" % (port, prog, pid)
sys.stderr.write(msg)
sys.exit(2)
# takes a string and converts it to a valid *nix port.
def port_gen(name):
md5_int = int(hashlib.md5(name).hexdigest()[:8], 16)
return (md5_int % (PORT_UPPER - PORT_LOWER)) + PORT_LOWER
# gives a dict (of ints to strings) containing the ports corresponding to
# all UDP and listening TCP sockets currently in use. Values are pid/programs as
# reported by netstat.
def occupied_ports():
# netstat options:
# --ip: address family (must proceed other options). includes raw, udp and tcp sockets.
# -l, --listening: show only listening sockets. (These are omitted by default.)
# -n, --numeric: do not try to resolve socket addresses (address and port number of local end).
# -p, --program: include a column including PID/PROGRAM for each socket.
lines = subprocess.check_output(['netstat', '--ip', '-lnp']).split('\n')[2:]
records = [line.split() for line in lines]
ports = {int(record[3].split(':')[-1]): record[-1] for record in records if len(record)}
return ports
def main(name, resolve=False):
in_use = occupied_ports()
port = port_gen(name)
if port not in in_use:
return port
else:
if resolve:
max_port = max(in_use) if in_use else PORT_LOWER
return max_port + 10
else:
[pid, prog] = in_use[port].split('/')
err_port_in_use(port, pid, prog)
if __name__ == '__main__':
if os.getuid() != 0:
sys.stderr.write('user is not root; sudo please!\n')
sys.exit(1)
args = sys.argv[1:]
if len(args) < 1:
err_usage()
resolve = False
if args[0] == '-r' or args[0] == '--resolve':
args.pop(0)
resolve = True
if len(args) != 1:
err_usage()
print(main(args[0], resolve=resolve))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment