summaryrefslogtreecommitdiffstats
path: root/scripts/traceroute-geolocation.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/traceroute-geolocation.nse')
-rw-r--r--scripts/traceroute-geolocation.nse187
1 files changed, 187 insertions, 0 deletions
diff --git a/scripts/traceroute-geolocation.nse b/scripts/traceroute-geolocation.nse
new file mode 100644
index 0000000..4224731
--- /dev/null
+++ b/scripts/traceroute-geolocation.nse
@@ -0,0 +1,187 @@
+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
+-- <table>
+-- <elem key="hop">1</elem>
+-- </table>
+-- <table>
+-- <elem key="hop">2</elem>
+-- </table>
+-- <table>
+-- <elem key="hop">3</elem>
+-- </table>
+-- <table>
+-- <elem key="hop">4</elem>
+-- </table>
+-- <table>
+-- <elem key="hop">5</elem>
+-- <elem key="rtt">16.76</elem>
+-- <elem key="ip">194.68.128.104</elem>
+-- <elem key="hostname">e4-0.barleymow.stk.router.colt.net</elem>
+-- <elem key="lat">62</elem>
+-- <elem key="lon">15</elem>
+-- </table>
+-- <table>
+-- <elem key="hop">6</elem>
+-- <elem key="rtt">48.61</elem>
+-- <elem key="ip">212.74.65.49</elem>
+-- <elem key="hostname">te0-0-2-0-crs1.FRA.router.colt.net</elem>
+-- <elem key="lat">54</elem>
+-- <elem key="lon">-2</elem>
+-- </table>
+--
+-- @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 = '<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://earth.google.com/kml/2.0"><Document><Placemark><LineString><coordinates>\r\n'
+ local footer = '</coordinates></LineString><Style><LineStyle><color>#ff0000ff</color></LineStyle></Style></Placemark></Document></kml>'
+
+ 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