local http = require "http"
local io = require "io"
local json = require "json"
local stdnse = require "stdnse"
local tab = require "tab"
local table = require "table"
local ipOps = require "ipOps"
description = [[
Lists the geographic locations of each hop in a traceroute and optionally
saves the results to a KML file, plottable on Google earth and maps.
]]
---
-- @usage
-- nmap --traceroute --script traceroute-geolocation
--
-- @output
-- | traceroute-geolocation:
-- | hop RTT ADDRESS GEOLOCATION
-- | 1 ...
-- | 2 ...
-- | 3 ...
-- | 4 ...
-- | 5 16.76 e4-0.barleymow.stk.router.colt.net (194.68.128.104) 62,15 Sweden (Unknown)
-- | 6 48.61 te0-0-2-0-crs1.FRA.router.colt.net (212.74.65.49) 54,-2 United Kingdom (Unknown)
-- | 7 57.16 87.241.37.146 42,12 Italy (Unknown)
-- | 8 157.85 212.162.64.146 42,12 Italy (Unknown)
-- | 9 ...
-- |_ 10 ...
-- @xmloutput
--
--
--
--
--
-- 5
-- 16.76
-- 194.68.128.104
-- e4-0.barleymow.stk.router.colt.net
-- 62
-- 15
--
--
-- 6
-- 48.61
-- 212.74.65.49
-- te0-0-2-0-crs1.FRA.router.colt.net
-- 54
-- -2
--
--
-- @args traceroute-geolocation.kmlfile full path and name of file to write KML
-- data to. The KML file can be used in Google earth or maps to plot the
-- traceroute data.
--
author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"safe", "external", "discovery"}
local arg_kmlfile = stdnse.get_script_args(SCRIPT_NAME .. ".kmlfile")
hostrule = function(host)
if ( not(host.traceroute) ) then
return false
end
return true
end
--
-- GeoPlugin requires no API key and has no limitations on lookups
--
local function geoLookup(ip, no_cache)
local output = stdnse.registry_get({SCRIPT_NAME, ip})
if output then return output end
local response = http.get("www.geoplugin.net", 80, "/json.gp?ip="..ip, {any_af=true})
local stat, loc = json.parse(response.body)
local get_value = function (d)
local t = type(d)
return (t == "string" or t == "number") and d or nil
end
if not (stat
and get_value(loc.geoplugin_latitude)
and get_value(loc.geoplugin_longitude)) then
return nil
end
output = {
lat = loc.geoplugin_latitude,
lon = loc.geoplugin_longitude,
reg = get_value(loc.geoplugin_regionName) or "Unknown",
ctry = get_value(loc.geoplugin_countryName) or "Unknown"
}
if not no_cache then
stdnse.registry_add_table({SCRIPT_NAME}, ip, output)
end
return output
end
local function createKMLFile(filename, coords)
local header = '\r\n'
local footer = ''
local output = {}
for _, coord in ipairs(coords) do
output[#output+1] = ("%s,%s, 0.\r\n"):format(coord.lon, coord.lat)
end
local f = io.open(filename, "w")
if ( not(f) ) then
return false, "Failed to create KML file"
end
f:write(header .. table.concat(output) .. footer)
f:close()
return true
end
-- Tables used to accumulate output.
local output_structured = {}
local output = tab.new(4)
local coordinates = {}
local function output_hop(count, ip, name, rtt, geo)
if ip then
local label
if name then
label = ("%s (%s)"):format(name or "", ip)
else
label = ("%s"):format(ip)
end
if geo then
table.insert(output_structured, { hop = count, ip = ip, hostname = name, rtt = ("%.2f"):format(rtt), lat = geo.lat, lon = geo.lon })
tab.addrow(output, count, ("%.2f"):format(rtt), label, ("%.3f,%.3f %s (%s)"):format(geo.lat, geo.lon, geo.ctry, geo.reg))
table.insert(coordinates, { hop = count, lat = geo.lat, lon = geo.lon })
else
table.insert(output_structured, { hop = count, ip = ip, hostname = name, rtt = ("%.2f"):format(rtt) })
tab.addrow(output, count, ("%.2f"):format(rtt), label, ("%s,%s"):format("- ", "- "))
end
else
table.insert(output_structured, { hop = count })
tab.addrow(output, count, "...")
end
end
action = function(host)
tab.addrow(output, "HOP", "RTT", "ADDRESS", "GEOLOCATION")
for count = 1, #host.traceroute do
local hop = host.traceroute[count]
-- avoid timedout hops, marked as empty entries
-- do not add the current scanned host.ip
if hop.ip then
local rtt = tonumber(hop.srtt) * 1000
local geo
if not ipOps.isPrivate(hop.ip) then
-- be sure not to cache the target address, since it's not likely to be
-- a hop for something else.
geo = geoLookup(hop.ip, ipOps.compare_ip(hop.ip, "eq", host.ip) )
end
output_hop(count, hop.ip, hop.name, rtt, geo)
else
output_hop(count)
end
end
if (#output_structured > 0) then
output = tab.dump(output)
if ( arg_kmlfile ) then
if ( not(createKMLFile(arg_kmlfile, coordinates)) ) then
output = output .. ("\n\nERROR: Failed to write KML to file: %s"):format(arg_kmlfile)
end
end
return output_structured, stdnse.format_output(true, output)
end
end