| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- <!doctype html>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0">
- <title>Wireshark: IP Location Map</title>
- <link rel="stylesheet" href="https://unpkg.com/leaflet@1.4.0/dist/leaflet.css"
- integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA=="
- crossorigin="">
- <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css"
- integrity="sha512-BBToHPBStgMiw0lD4AtkRIZmdndhB6aQbXpX7omcrXeG2PauGBl2lzq2xUZTxaLxYz5IDHlmneCZ1IJ+P3kYtQ=="
- crossorigin="">
- <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css"
- integrity="sha512-RLEjtaFGdC4iQMJDbMzim/dOvAu+8Qp9sw7QE4wIMYcg2goVoivzwgSZq9CsIxp4xKAZPKh5J2f2lOko2Ze6FQ=="
- crossorigin="">
- <!--
- <link rel="stylesheet" href="https://unpkg.com/leaflet-measure@3.1.0/dist/leaflet-measure.css"
- integrity="sha512-wgiKVjb46JxgnGNL6xagIy2+vpqLQmmHH7fWD/BnPzouddSmbRTf6xatWIRbH2Rgr2F+tLtCZKbxnhm5Xz0BcA=="
- crossorigin="">
- -->
- <style>
- html, body {
- margin: 0;
- padding: 0;
- height: 100%;
- }
- #map {
- height: 100%;
- }
- .file-picker-enabled #map, #file-picker-container {
- display: none;
- }
- .file-picker-enabled #file-picker-container {
- display: block;
- margin: 2em;
- }
- .range-control {
- padding: 3px 5px;
- color: #333;
- background: #fff;
- opacity: .5;
- }
- .range-control:hover { opacity: 1; }
- .range-control-label { padding-right: 3px; }
- .range-control-input { padding: 0; width: 130px; }
- .range-control-input, .range-control-label { vertical-align: middle; }
- </style>
- <script src="https://unpkg.com/leaflet@1.4.0/dist/leaflet.js"
- integrity="sha512-QVftwZFqvtRNi0ZyCtsznlKSWOStnDORoefr1enyq5mVL4tmKB3S/EnC3rRJcxCPavG10IcrVGSmPh6Qw5lwrg=="
- crossorigin=""></script>
- <script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"
- integrity="sha512-MQlyPV+ol2lp4KodaU/Xmrn+txc1TP15pOBF/2Sfre7MRsA/pB4Vy58bEqe9u7a7DczMLtU5wT8n7OblJepKbg=="
- crossorigin=""></script>
- <!--
- <script src="https://unpkg.com/leaflet-measure@3.1.0/dist/leaflet-measure.js"
- integrity="sha512-ovh6EqS7MUI3QjLWBM7CY8Gu8cSM5x6vQofUMwKGbHVDPSAS2lmNv6Wq5es5WCz1muyojQxcc8rA3CvVjD2Z+A=="
- crossorigin=""></script>
- -->
- <script>
- var map;
- function sortIpKey(v) {
- if (/\./.test(v)) {
- // Assume IPv4. Convert 192.0.2.34 -> 192.000.002.034 for alpha sort.
- return v.replace(/\b\d\b/g, '00$&').replace(/\b\d{2}\b/g, '0$&');
- } else {
- // Assume IPv6. We won't handle :: correctly. Hope for the best.
- return v;
- }
- }
- function escapeHtml(text) {
- if (!text) return '';
- return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
- }
- function sanitizeHtml(text) {
- // Handle legacy data containing <div class="geoip_property">...</div>
- // (since Wireshark 2.0) or <br/> (before v1.99.0-rc1-1781-g7e63805708).
- text = text
- .replace(/<div[^>]*>/g, '')
- .replace(/<\/div>|<br\/>/g, '\n')
- .replace(/'/g, "'");
- return escapeHtml(text).replace(/\n/g, '<br>');
- }
- var RangeControl = L.Control.extend({
- options: {
- // @option label: String = 'Speed:'
- // The HTML text to be displayed next to the slider.
- label: '',
- title: '',
- min: 0,
- max: 100,
- value: 0,
- // @option onChange: Function = *
- // A `Function` that is called on slider value changes.
- // Called with two arguments, the new and previous range value.
- },
- onAdd: function(map) {
- var className = 'range-control';
- var container = L.DomUtil.create('div', className + ' leaflet-bar');
- L.DomEvent.disableClickPropagation(container);
- var label = L.DomUtil.create('label', className + '-label', container);
- var labelText = L.DomUtil.create('span', className + '-label', label);
- labelText.title = this.options.title;
- labelText.innerHTML = this.options.label;
- var input = L.DomUtil.create('input', className + '-input', label);
- this._input = input;
- input.type = 'range';
- input.min = this.options.min;
- input.max = this.options.max;
- this._lastValue = input.valueAsNumber = this.options.value;
- L.DomEvent.on(input, 'change', this._onInputChange, this);
- return container;
- },
- _onInputChange: function(ev) {
- var value = this._input.valueAsNumber;
- if (value !== this._lastValue) {
- if (this.options.onChange) {
- this.options.onChange(value, this._lastValue);
- }
- this._lastValue = value;
- }
- }
- });
- var rangeControl = function(options) {
- return new RangeControl(options);
- };
- function loadGeoJSON(obj) {
- 'use strict';
- if (map) map.remove();
- map = L.map('map');
- var tileServer = 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png';
- L.tileLayer(tileServer, {
- minZoom: 2,
- maxZoom: 16,
- subdomains: 'abcd',
- attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'
- }).addTo(map);
- L.control.scale().addTo(map);
- // Measurement tool, useful for investigating accuracy-related issues.
- if (L.control.measure) {
- L.control.measure({
- primaryLengthUnit: 'kilometers',
- secondaryLengthUnit: 'miles'
- }).addTo(map);
- }
- var geoJson = L.geoJSON(obj, {
- pointToLayer: function(feature, latlng) {
- // MaxMind databases use km for accuracy, but they always use
- // 50, 100, 200 or 1000. That is too course, so ignore it and use a
- // fixed 1km radius.
- // See https://gitlab.com/wireshark/wireshark/-/issues/14693#note_400735005
- return L.circle(latlng, {radius: 1e3});
- },
- onEachFeature: function(feature, layer) {
- var props = feature.properties;
- var title, lines = [];
- if (props.title && props.description) {
- title = escapeHtml(props.title);
- lines.push(sanitizeHtml(props.description));
- } else {
- title = escapeHtml(props.ip);
- if (props.autonomous_system_number) {
- var line = 'AS: ' + props.autonomous_system_number;
- line += ' (' + props.autonomous_system_organization + ')';
- lines.push(escapeHtml(line));
- }
- if (props.city) {
- lines.push(escapeHtml('City: ' + props.city));
- }
- if (props.country) {
- lines.push(escapeHtml('Country: ' + props.country));
- }
- if ('packets' in props) {
- lines.push(escapeHtml('Packets: ' + props.packets));
- }
- if ('bytes' in props) {
- lines.push(escapeHtml('Bytes: ' + props.bytes));
- }
- }
- if (title) {
- layer.bindTooltip(title, {
- offset: [10, 0],
- direction: 'right',
- sticky: true
- });
- }
- if (title && lines.length) {
- layer.bindPopup('<b>' + title + '</b><br>' + lines.join('<br>'));
- }
- }
- });
- map.on('zoomend', function() {
- // Ensure that the circles are clearly visible even when zoomed out.
- // Larger values will increase the size of the circle.
- var visibleZoomLevel = 9;
- var radius = 1e3;
- if (map.getZoom() < visibleZoomLevel) {
- // Enlarge radius to ensure it is easy to select.
- radius *= map.getZoomScale(visibleZoomLevel, map.getZoom());
- }
- geoJson.eachLayer(function(layer) {
- layer.setRadius(radius);
- });
- });
- // Cluster nearby/overlapping nodes by default.
- var clusterGroup = L.markerClusterGroup({
- zoomToBoundsOnClick: false,
- spiderfyOnMaxZoom: false,
- maxClusterRadius: 10
- });
- clusterGroup.addTo(map).addLayer(geoJson);
- map.fitWorld().fitBounds(clusterGroup.getBounds());
- // Summarize nodes within the cluster.
- clusterGroup.on('clustermouseover', function(ev) {
- // More addresses will be stripped.
- var cutoff = 30;
- var cluster = ev.propagatedFrom;
- var addresses = cluster.getAllChildMarkers().map(function(marker) {
- return marker.getTooltip().getContent();
- });
- addresses.sort(function(a, b) {
- a = sortIpKey(a);
- b = sortIpKey(b);
- return a === b ? 0 : (a < b ? -1 : 1);
- });
- var deleted = addresses.splice(cutoff).length;
- var title = addresses.join('<br>');
- if (deleted) {
- title += '<br>(and ' + deleted + ' more)';
- }
- cluster.bindTooltip(title, {
- offset: [10, 0],
- direction: 'right',
- sticky: true,
- opacity: 0.8
- }).openTooltip();
- }).on('clustermouseout', function(ev) {
- ev.propagatedFrom.unbindTooltip();
- }).on('clusterclick', function(ev) {
- ev.propagatedFrom.spiderfy();
- });
- // Provide an option to disable clustering
- rangeControl({
- label: 'Cluster radius:',
- title: 'Control merging of nearby nodes. Set to the minimum to disable merges.',
- min: 0,
- max: 100,
- value: clusterGroup.options.maxClusterRadius,
- onChange: function(value, oldValue) {
- // Apply new radius: remove map, clear markers and finally add new.
- clusterGroup.options.maxClusterRadius = value;
- clusterGroup.remove().clearLayers().addTo(map);
- // Value 0: clustering is disabled, the map is directly used.
- geoJson.remove().addTo(value === 0 ? map : clusterGroup);
- }
- }).addTo(map);
- }
- function showError(msg) {
- document.getElementById('error-message').textContent = msg;
- document.body.classList.add('file-picker-enabled');
- }
- function loadData(data) {
- 'use strict';
- var html_match, what, error;
- var reOldHtml = /^ *var endpoints = (\{[\s\S]+? *\});$/m;
- // Complicated regex to support html-minifier.
- var reNewHtml = /<script[^>]+id="?ipmap-data"?(?: [^>]*)?>\s*(\{[\S\s]+?\})\s*<\/script>/;
- if ((html_match = reNewHtml.exec(data))) {
- // Match new ipmap.html file.
- what = 'new ipmap.html';
- data = html_match[1];
- } else if ((html_match = reOldHtml.exec(data))) {
- // Match old ipmap.html file
- what = 'old ipmap.html';
- var text = html_match[1].replace(/'/g, '"');
- text = text.replace(/ class="geoip_property"/g, '');
- data = text.replace(/\/\/ Start endpoint list.*/, '');
- } else if (/^\s*\{[\s\S]+\}\s*$/.test(data)) {
- // Assume GeoJSON (.json) file.
- what = 'GeoJSON file';
- } else {
- what = 'unknown file';
- error = 'Unrecognized file contents';
- }
- if (!error) {
- try {
- loadGeoJSON(JSON.parse(data));
- return true;
- } catch (e) {
- error = e;
- }
- }
- var msg = 'Failed to load map data from ' + what + ': ' + error;
- msg += '; data was: ' + data.substring(0, 120);
- if (data.length > 100) msg += '... (' + data.length + ' bytes)';
- showError(msg);
- }
- (function() {
- 'use strict';
- function loadFromUrl(url) {
- var xhr = new XMLHttpRequest();
- xhr.open('GET', url, true);
- xhr.onload = function() {
- if (xhr.status !== 200) {
- showError('Failed to retrieve ' + url + ': ' + xhr.status + ' ' + xhr.statusText);
- return;
- }
- loadData(xhr.responseText);
- };
- xhr.onerror = function() {
- showError('Failed to retrieve ' + url + ': ' + xhr.status + ' ' + xhr.statusText);
- };
- xhr.send(null);
- }
- addEventListener('load', function() {
- // Note: FileReader and classList do not work with IE9 or older.
- var fileSelector = document.getElementById('file-picker');
- fileSelector.addEventListener('change', function() {
- if (!fileSelector.files.length) {
- return;
- }
- document.body.classList.remove('file-picker-enabled');
- var reader = new FileReader();
- reader.onload = function() {
- if (!loadData(reader.result)) {
- document.body.classList.add('file-picker-enabled');
- }
- };
- reader.onerror = function() {
- showError('Failed to read file.');
- };
- reader.readAsText(fileSelector.files[0]);
- });
- // Force file picker when the "file" URL is given.
- var url = location.search.match(/[?&]url=([^&]*)/);
- if (url) {
- url = decodeURIComponent(url[1]);
- if (url) {
- loadFromUrl(url);
- } else {
- showError('');
- }
- return;
- }
- var data = document.getElementById('ipmap-data');
- if (data) {
- loadData(data.textContent);
- } else {
- showError('');
- }
- });
- }());
- </script>
- <div id="file-picker-container">
- <label>Select an ipmap.html or GeoJSON .json file as created by Wireshark.<br>
- <input type="file" id="file-picker" accept=".json,.html"></label>
- <p id="error-message"></p>
- </div>
- <div id="map"></div>
- <!--
- Wireshark will append a script tag (id="ipmap-data" type="application/json")
- below, containing a GeoJSON object. If missing, then a file picker will be
- displayed which can be useful during development.
- -->
|