Skip to content

Commit e96ab2d

Browse files
committed
add net-tree visualization
1 parent a75a5ef commit e96ab2d

7 files changed

+299
-20
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
visualization/*.json
1+
visualization/*.json
2+
visualization/*.png

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The databases created from the gathered data will be and stay open-source!
1313
<a href="https://github.com/O-X-L/risk-db/blob/latest/visualization">
1414
<img src="https://raw.githubusercontent.com/O-X-L/risk-db/refs/heads/latest/visualization/world_map_example.webp" alt="World Map Example" width="800"/>
1515
<img src="https://raw.githubusercontent.com/O-X-L/risk-db/refs/heads/latest/visualization/asn_chart_example.webp" alt="ASN Chart Example" width="800"/>
16+
<img src="https://raw.githubusercontent.com/O-X-L/risk-db/refs/heads/latest/visualization/net_tree_example.webp" alt="Net Tree Example" width="800"/>
1617
</a>
1718

1819
----

visualization/README.md

+29-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
It uses this javascript library: [StephanWagner/svgMap](https://github.com/StephanWagner/svgMap)
66

7-
Use:
7+
Usage:
88

99
1. Download the free [IPInfo country database](https://ipinfo.io/products/free-ip-database)
1010

@@ -26,11 +26,11 @@ Use:
2626

2727
----
2828

29-
## ASN Chart
29+
## ASN Bubble-Chart
3030

3131
It uses this javascript library: [d3js](https://d3js.org/)
3232

33-
Use:
33+
Usage:
3434

3535
1. Copy the `risk_asn_med.json` into this directory
3636

@@ -43,3 +43,29 @@ Use:
4343
3. Open the file in your browser: [http://localhost:8000/asn_chart.html](http://localhost:8000/world_map.html)
4444

4545
<img src="https://raw.githubusercontent.com/O-X-L/risk-db/refs/heads/latest/visualization/asn_chart_example.webp" alt="ASN Chart Example" width="800"/>
46+
47+
----
48+
49+
## Network Tree-Chart
50+
51+
It uses this javascript library: [d3js](https://d3js.org/)
52+
53+
Usage:
54+
55+
1. Download the free [IPInfo ASN database](https://ipinfo.io/products/free-ip-database)
56+
57+
2. Generate the JSON file
58+
59+
```bash
60+
python3 net_tree.py -a ~/Downloads/country_asn.mmdb -n ~/Downloads/risk_net4_med.json
61+
```
62+
63+
3. For testing - run the minimal python3 webserver to enable JS to access the JSON file:
64+
65+
```bash
66+
python3 test_server.py
67+
```
68+
69+
4. Open the file in your browser: [http://localhost:8000/net_tree.html](http://localhost:8000/world_map.html)
70+
71+
<img src="https://raw.githubusercontent.com/O-X-L/risk-db/refs/heads/latest/visualization/net_tree_example.webp" alt="Net Tree Example" width="800"/>

visualization/asn_chart.html

+16-16
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
.bubble-asn {
1515
font-weight: bold;
16+
text-anchor: middle;
1617
}
1718

1819
.bubble-info {
@@ -44,7 +45,10 @@
4445
</style>
4546
</head>
4647
<body>
47-
<!-- see: https://observablehq.com/@d3/bubble-chart/2 -->
48+
<!--
49+
see: https://observablehq.com/@d3/bubble-chart/2
50+
see2: https://medium.com/@jhren/d3-data-visualization-in-javascript-use-d3-js-to-create-a-bubble-chart-3fba181faacd
51+
-->
4852
<div id="infos"></div>
4953
<svg id="chart"></svg>
5054
<script>
@@ -73,35 +77,35 @@
7377
function showASNInfos(e) {
7478
let infos = JSON.parse(e.currentTarget.querySelectorAll(".bubble-info")[0].textContent);
7579
let infoContainer = document.getElementById('infos');
76-
infoContainer.innerHTML = '<b>ASN</b>: ' + infos.asn + '<br>';
77-
infoContainer.innerHTML += '<b>Org</b>: ' + infos.info.org.name + '<br>';
80+
infoContainer.innerHTML = `<b>ASN</b>: ${infos.asn}<br>`;
81+
infoContainer.innerHTML += `<b>Org</b>: ${infos.info.org.name}<br>`;
7882
if (infos.info.org.country != '' && infos.info.org.country !== null) {
79-
infoContainer.innerHTML += '<b>Country</b>: ' + infos.info.org.country + '<br>';
83+
infoContainer.innerHTML += `<b>Country</b>: ${infos.info.org.country}<br>`;
8084
}
81-
infoContainer.innerHTML += '<b>IPv4 Addresses</b>: ' + numberWithDot(infos.info.ipv4) + '<br>';
82-
infoContainer.innerHTML += '<b>IPv6 Addresses</b>: ' + numberWithDot(infos.info.ipv6) + '<br>';
85+
infoContainer.innerHTML += `<b>IPv4 Addresses</b>: ${numberWithDot(infos.info.ipv4)}<br>`;
86+
infoContainer.innerHTML += `<b>IPv6 Addresses</b>: ${numberWithDot(infos.info.ipv6)}<br>`;
8387
let kinds = [];
8488
for (let [k, v] of Object.entries(infos.kind)) {
8589
if (v === true) {
8690
kinds.push(k);
8791
}
8892
}
8993
if (kinds.length > 0) {
90-
infoContainer.innerHTML += '<b>Kind</b>: ' + kinds.join(', ') + '<br>';
94+
infoContainer.innerHTML += `<b>Kind</b>: ${kinds.join(', ')}<br>`;
9195
}
92-
let urls = ['<a href="https://risk.oxl.app/api/asn/' + infos.asn + '">oxl_riskdb</a>'];
96+
let urls = [`<a href="https://risk.oxl.app/api/asn/${infos.asn}">oxl_riskdb</a>`];
9397
for (let [k, v] of Object.entries(infos.info.url)) {
94-
urls.push('<a href="' + v + '">' + k + '</a>');
98+
urls.push(`<a href="${v}">${k}</a>`);
9599
}
96-
infoContainer.innerHTML += '<b>URLs</b>: ' + urls.join(', ') + '<br><hr>';
100+
infoContainer.innerHTML += `<b>URLs</b>: ${urls.join(', ')}<br><hr>`;
97101
for (let [k, v] of Object.entries(infos.reports)) {
98-
let r = '<small><b>Reports ' + k + '</b>: ';
102+
let r = `<small><b>Reports ${k}</b>: `;
99103
if (k == 'rel_by_ip4') {
100104
r += v;
101105
} else {
102106
r += numberWithDot(v);
103107
}
104-
infoContainer.innerHTML += r + '</small><br>';
108+
infoContainer.innerHTML += `${r}</small><br>`;
105109
}
106110
}
107111

@@ -148,20 +152,16 @@
148152
.attr("fill", d => colorScale(d.data.asn));
149153

150154
bubbles.append("text")
151-
.style("text-anchor", "middle")
152155
.append("tspan")
153156
.attr("class", "bubble-asn")
154157
.text(d => d.data.asn);
155158

156159
// hover infos
157160
bubbles.append("text")
158-
.style("text-anchor", "bottom")
159161
.append("tspan")
160162
.attr("class", "bubble-info")
161163
.text(d => JSON.stringify(d.data));
162164

163-
d3.selectAll("text").style("fill", "black");
164-
165165
for (let b of document.querySelectorAll(".bubble")) {
166166
b.addEventListener("mouseover", showASNInfos);
167167
}

visualization/net_tree.html

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<link rel="icon" type="image/svg" href="https://files.oxl.at/img/oxl3_sm.png">
7+
8+
<script src="https://d3js.org/d3.v4.min.js"></script>
9+
<style>
10+
#chart {
11+
width: 100vw;
12+
height: 100vh;
13+
}
14+
15+
.net {
16+
overflow: hidden;
17+
font-weight: bold;
18+
transform
19+
}
20+
21+
.net-cidr {
22+
display: inline-block;
23+
margin-top: 100px;
24+
}
25+
26+
.net-info {
27+
visibility: hidden;
28+
display: none;
29+
}
30+
31+
#infos {
32+
width: 20vw;
33+
height: 25vh;
34+
background-color: whitesmoke;
35+
opacity: 0.85;
36+
z-index: 2;
37+
border-radius: 10px;
38+
border-width: 2px;
39+
border-color: black;
40+
border-style: solid;
41+
position: absolute;
42+
padding: 20px;
43+
overflow: hidden;
44+
}
45+
46+
</style>
47+
</head>
48+
<body>
49+
<!--
50+
see: https://observablehq.com/@d3/treemap/2
51+
see2: https://d3-graph-gallery.com/graph/treemap_basic.html
52+
-->
53+
<div id="infos"></div>
54+
<svg id="chart"></svg>
55+
<script>
56+
// start test webserver: 'python3 test_server.py'
57+
58+
const WIDTH = window.innerWidth;
59+
const HEIGHT = window.innerHeight - 50;
60+
const NET_PAD = 5;
61+
const CATEGORIES = ['all', 'bot', 'probe', 'rate', 'attack', 'crawler'];
62+
const COLORSCHEME = [
63+
// red-ish
64+
"#B93022", "#FF383D", "#D32669", "#C43F03", "#E70291", "#E211C0", "#6A3D52", "#C75839", "#E7A8B0",
65+
"#DB7277", "#A23E48",
66+
// purple-ish
67+
"#9F8AD2", "#4B2ED1", "#9C1091", "#EA77DF", "#8D0BE8", "#BB65BC", "#993CF3", "#9F09EF", "#CD0099",
68+
"#EA2794", "#DC8581", "#9C1686",
69+
// orange-ish
70+
"#E37F6F", "#C5824E", "#FDCA29", "#D99620", "#E7C014", "#F9C630", "#F6BC55", "#B9AB58", "#DB8E94",
71+
"#D27E7E",
72+
];
73+
74+
function numberWithDot(x) {
75+
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
76+
}
77+
78+
function showNetInfos(e) {
79+
let infos = JSON.parse(e.currentTarget.querySelectorAll(".net-info")[0].textContent);
80+
81+
var x = event.clientX;
82+
var y = event.clientY;
83+
console.log(infos, x, y);
84+
85+
var infoContainer = document.getElementById('infos');
86+
infoContainer.style.left = `${x}px`;
87+
infoContainer.style.top = `${y}px`;
88+
89+
infoContainer.innerHTML = `<b>Network</b>: ${infos.name}<br>`;
90+
infoContainer.innerHTML += `<b>Reported IPs</b>: ${infos.reported_ips}<br>`;
91+
infoContainer.innerHTML += `<b>Reputation</b>: ${infos.reputation.toUpperCase()}<br>`;
92+
infoContainer.innerHTML += `<b>ASN</b>: ${infos.asn}<br>`;
93+
infoContainer.innerHTML += `<b>Org</b>: ${infos.as_name}<br>`;
94+
95+
if (infos.country !== undefined) {
96+
infoContainer.innerHTML += `<b>Country</b>: ${infos.country}<br>`;
97+
}
98+
99+
let urls = [];
100+
for (let [k, v] of Object.entries(infos.url)) {
101+
urls.push(`<a href="${v}">${k}</a>`);
102+
}
103+
infoContainer.innerHTML += `<b>URLs</b>: ${urls.join(', ')}<br><hr>`;
104+
105+
for (let c of CATEGORIES) {
106+
if (c in infos) {
107+
infoContainer.innerHTML += `<small><b>Reports ${c}</b>: ${numberWithDot(infos[c])}</small><br>`;
108+
}
109+
}
110+
}
111+
112+
function createChart() {
113+
d3.json('net_tree.json', function(data) {
114+
var svg = d3.select("#chart");
115+
116+
var root = d3.stratify()
117+
.id(function(d) { return d.name; })
118+
.parentId(function(d) { if (d.name == 'root') { return null } else { return 'root' }; })
119+
(data.children);
120+
121+
root.sum(function(d) { return +d.all })
122+
123+
d3.treemap()
124+
.size([WIDTH, HEIGHT])
125+
.padding(4)
126+
(root)
127+
128+
const colorScale = d3.scaleOrdinal()
129+
.range(COLORSCHEME);
130+
131+
const nets = svg.selectAll(".net")
132+
.data(root.descendants().slice(1))
133+
.enter()
134+
.append("g")
135+
.attr("class", "net");
136+
137+
nets.append("rect")
138+
.attr('x', function (d) { return d.x0; })
139+
.attr('y', function (d) { return d.y0; })
140+
.attr('width', function (d) { return d.x1 - d.x0; })
141+
.attr('height', function (d) { return d.y1 - d.y0; })
142+
.style("fill", d => colorScale(d.data.reported_ips))
143+
.attr("class", "net");
144+
145+
nets.append("text")
146+
.append("tspan")
147+
.attr("class", "net-cidr")
148+
.text(d => d.data.name)
149+
.style('fill', 'whitesmoke');
150+
151+
// hover infos
152+
nets.append("text")
153+
.style("text-anchor", "bottom")
154+
.append("tspan")
155+
.attr("class", "net-info")
156+
.text(d => JSON.stringify(d.data));
157+
158+
for (let n of document.querySelectorAll(".net")) {
159+
let r = n.querySelectorAll("rect")[0];
160+
if (r === undefined) {
161+
continue;
162+
}
163+
let c = n.querySelectorAll("text")[0];
164+
c.setAttribute("transform", `translate(${r.x.baseVal.value + NET_PAD}, ${r.y.baseVal.value + NET_PAD + 30})`);
165+
166+
n.addEventListener("mouseover", showNetInfos);
167+
}
168+
169+
});
170+
}
171+
172+
createChart();
173+
</script>
174+
</body>
175+
</html>

0 commit comments

Comments
 (0)