#!/usr/bin/env python
# vim: set ts=8 sw=4 sts=4 et ai:
# Standalone UDP ping utility to keep an UDP connection through a NATing
# gateway alive. Run from the same machine as your sip client/PBX which needs
# its NAT to stay punctured.
#
# Usage: udp-keepalive.py sip.myprovider.com
# Walter Doekes 2011-2012, for Voys Telecom
#
# v2: added SO_REUSEADDR
# v3: never abort on failure, add log instead, add basic dameonize capability,
#     add a few helpful notes
import socket, sys


def udp_keepalive(hosts, data='\0\0\0\0', dport=None, sport=None, quiet=False):
    fromaddr = ('0.0.0.0', sport)

    for host in hosts:
        addrinfo = socket.getaddrinfo(host, dport, socket.AF_INET,
                                      socket.SOCK_DGRAM, socket.SOL_UDP)
        for family, type, proto, unused, address in addrinfo:
            sock = socket.socket(family, type, proto)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            try:
                sock.bind(fromaddr)
                sock.sendto(data, address)
                if not quiet:
                    print 'sent %r to %r' % (data, address)
            except:
                print >>sys.stderr, ('could not send from %r to %r' %
                                     (fromaddr, address))
            finally:
                sock.close()


if __name__ == '__main__':
    import os, time
    from optparse import OptionParser
    argv0 = os.path.basename(__file__)
    parser = OptionParser()
    parser.add_option('-d', '--dport', dest='dport', default='5060',
            action='store', type='int',
            help='destination PORT (defaults to 5060)', metavar='PORT')
    parser.add_option('-s', '--sport', dest='sport', default='5060',
            action='store', type='int',
            help='source PORT (defaults to 5060)', metavar='PORT')
    parser.add_option('-q', '--quiet', dest='quiet', default=False,
            action='store_true',
            help='quiet operation')
    parser.add_option('-D', '--daemon', dest='daemon', default=False,
            action='store_true',
            help='fork into daemon mode')
    (opt, args) = parser.parse_args()
   
    if not args:
        print >>sys.stderr, '%s: Need one or more hosts to ping.' % argv0
        print >>sys.stderr, "Try `%s --help' for more information." % argv0
        sys.exit(1)

    if opt.daemon:
        opt.quiet = True
        os.chdir('/')
        sys.stdin.close()
        sys.stdout.close()
        sys.stderr.close()
        # Double fork, since everyone (including upstart) expects us to.
        if os.fork():
            os._exit(0)
        if os.fork():
            os._exit(0)
        # .. and run setsid??
        # Reopen stderr, so we can write and not die.
        sys.stderr = open('/dev/null', 'a')

    if not opt.quiet:
        print 'Press CTRL+C to quit'

    while True:
        try:
            udp_keepalive(args, dport=opt.dport, sport=opt.sport, quiet=opt.quiet)
            time.sleep(15)
        except KeyboardInterrupt:
            sys.exit(0)
