This function uses centinel.command to issue
    a traceroute command, wait for it to finish execution and
    parse the results out to a dictionary.

    :param domain: the domain to be queried
    :param method: the packet type used for traceroute, UDP by default
    :param cmd_arguments: the list of arguments that need to be passed
                          to traceroute.
    :param external:
    :param log_prefix:
    :return:

def traceroute(domain, method="udp", cmd_arguments=None,
               external=None, log_prefix=''):
    """
    This function uses centinel.command to issue
    a traceroute command, wait for it to finish execution and
    parse the results out to a dictionary.

    :param domain: the domain to be queried
    :param method: the packet type used for traceroute, UDP by default
    :param cmd_arguments: the list of arguments that need to be passed
                          to traceroute.
    :param external:
    :param log_prefix:
    :return:
    """

    # the method specified by the function parameter here will
    # over-ride the ones given in cmd_arguments because
    # traceroute will use the last one in the argument list.
    _cmd_arguments = []

    logging.debug("%sRunning traceroute for "
                  "%s using %s probes." % (log_prefix, domain, method))

    results = {"method": method}

    if cmd_arguments is not None:
        _cmd_arguments = copy.deepcopy(cmd_arguments)

    if method == "tcp":
        if platform in ['linux', 'linux2']:
            _cmd_arguments.append('-T')
        elif platform == "darwin":
            _cmd_arguments.append('-P')
            _cmd_arguments.append('tcp')

    elif method == "udp":
        if platform in ['linux', 'linux2']:
            _cmd_arguments.append('-U')
        elif platform == "darwin":
            _cmd_arguments.append('-P')
            _cmd_arguments.append('udp')

    elif method == "icmp":
        if platform in ['linux', 'linux2']:
            _cmd_arguments.append('-I')
        elif platform == "darwin":
            _cmd_arguments.append('-P')
            _cmd_arguments.append('icmp')

    cmd = ['traceroute'] + _cmd_arguments + [domain]

    caller = command.Command(cmd, _traceroute_callback)
    caller.start()
    if not caller.started:
        if caller.exception is not None:
            if "No such file or directory" in caller.exception:
                message = "traceroute not found or not installed"
            else:
                message = ("traceroute thread threw an "
                           "exception: %s" % caller.exception)
        elif "enough privileges" in caller.notifications:
            message = "not enough privileges"
        elif "not known" in caller.notifications:
            message = "name or service not known"
        else:
            message = caller.notifications

        results["dest_name"] = domain
        results["error"] = message
        if external is not None and type(external) is dict:
            external[domain] = results
        return results

    forcefully_terminated = False
    timeout = 60
    start_time = time.time()
    # check every second to see if the execution has stopped
    while caller.thread.isAlive():
        if (time.time() - start_time) > timeout:
            caller.stop()
            forcefully_terminated = True
            break
        time.sleep(1)
    # we are only accurate down to seconds, so we have
    # to round up
    time_elapsed = int(time.time() - start_time)

    output_string = caller.notifications

    try:
        parsed_output = trparse.loads(output_string)
    except Exception as exc:
        results["dest_name"] = domain
        results["error"] = str(exc)
        results["raw"] = output_string
        if external is not None and type(external) is dict:
            external[domain] = results
        return results

    hops = list()
    for hop in parsed_output.hops:
        hop_json = {"index": hop.idx, "asn": hop.asn}
        probes_json = []
        for probe in hop.probes:
            probes_json.append({"name": probe.name,
                                "ip": probe.ip,
                                "rtt": probe.rtt,
                                "anno": probe.anno})
        hop_json["probes"] = probes_json
        hops.append(hop_json)

    results["dest_name"] = parsed_output.dest_name
    results["dest_ip"] = parsed_output.dest_ip
    results["hops"] = hops
    results["forcefully_terminated"] = forcefully_terminated
    results["time_elapsed"] = time_elapsed

    # the external result is used when threading to store
    # the results in the list container provided.
    if external is not None and type(external) is dict:
        external[domain] = results

    return results