docs: 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