2095 lines
62 KiB
JavaScript
2095 lines
62 KiB
JavaScript
|
||
"use strict";
|
||
|
||
// 限制可被引用的域名
|
||
var approvedDomains = ['norse-corp.com'];
|
||
if (top.location != self.location && approvedDomains.indexOf(top.location.hostname) === -1) {
|
||
top.location = self.location.href
|
||
}
|
||
|
||
// 每6个小时刷新页面
|
||
var refreshSeconds = 60 * 60 * 6; // 6 hours
|
||
setTimeout("location.reload()", refreshSeconds * 1000);
|
||
|
||
// 呈现消息
|
||
function showMessage(message) {
|
||
document.getElementById('message-text').innerHTML = message;
|
||
document.getElementById('message-panel').style.display = "block";
|
||
}
|
||
|
||
// 隐藏消息
|
||
function hideMessage() {
|
||
document.getElementById('message-panel').style.display = 'none';
|
||
}
|
||
|
||
|
||
(function(window) {
|
||
var VSN = "1.1";
|
||
|
||
// 限制使用Chrome浏览器
|
||
/*
|
||
var isChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
|
||
if(!isChrome) {
|
||
showMessage("The IPViking digital attack map only supports the Google Chrome browser.");
|
||
return;
|
||
}
|
||
*/
|
||
|
||
var settings = {
|
||
// 解析成数字的数据属性
|
||
numberProps: ["dport", "latitude", "longitude", "latitude2", "longitude2"],
|
||
// 作为统计信息更新的闪光的颜色
|
||
triggerColor: "red",
|
||
// 范围文本最低透明度
|
||
minTextOpacity: 0.35,
|
||
// 这个数字开始后丢弃攻击
|
||
maxAttacks: 100,
|
||
radius: 5,
|
||
countryColor: d3.scale.log()
|
||
.domain([1, 1200])
|
||
.range([d3.rgb(30, 30, 30), d3.rgb(30, 65, 140)]),
|
||
tableBarWidth: d3.scale.log()
|
||
.domain([1, 500])
|
||
.range([1, 130]),
|
||
|
||
// 布局设置
|
||
linkAnchor: false,
|
||
linkSiblings: false,
|
||
|
||
// 表格行数
|
||
topTableRows: 10,
|
||
portTableRows: 8,
|
||
consoleTableRows: 8,
|
||
pruneInterval: 3600,
|
||
dataPruneInterval: 60,
|
||
|
||
// Websocket 设置
|
||
//wsHost: "ws://64.19.78.244:443/",
|
||
wsHost: "ws://127.0.0.1:9999",
|
||
psk: "18c989796c61724d4661b019f2779848dd69ae62",
|
||
wsTimeout: 30000
|
||
};
|
||
|
||
/*
|
||
* HTML 接口
|
||
*/
|
||
d3.selectAll(".vsn").text(VSN);
|
||
|
||
var timestampedData = [];
|
||
|
||
function prune() {
|
||
// 使用 lodash 库, 用 _.select 实现折半搜索来找到时间对数范围的起始值
|
||
|
||
var now = new Date().getTime() / 1000;
|
||
|
||
for (var i in timestampedData) {
|
||
if (timestampedData[i].pruneTS > now) break;
|
||
}
|
||
|
||
var expiredData = [];
|
||
|
||
if (i > 0) {
|
||
expiredData = timestampedData.splice(0, i);
|
||
}
|
||
|
||
for (var n = 0; n < expiredData.length; n++) {
|
||
// todo:
|
||
// statsManager.remove(timestampData[n]);
|
||
linkModels.forEach(function(model) {
|
||
model.remove(expiredData[n]);
|
||
});
|
||
}
|
||
}
|
||
|
||
var displayLabel = {
|
||
// 显示常规信息的标签
|
||
elt: d3.select("#display-label"),
|
||
|
||
set: function(text) {
|
||
this.elt.text(text);
|
||
},
|
||
|
||
clear: function() {
|
||
this.elt.text("");
|
||
}
|
||
};
|
||
|
||
if (!window.chrome) {
|
||
displayLabel.set("Too slow? Try Chrome.");
|
||
}
|
||
|
||
d3.selectAll(".info-btn").on("click", function() {
|
||
d3.event.preventDefault();
|
||
var drawerContent = d3.select("#drawer");
|
||
if (drawerContent.style("display") === "none") {
|
||
drawerContent
|
||
.transition()
|
||
.style("display", "block");
|
||
|
||
d3.selectAll(".info-btn").classed("blue-bg", true);
|
||
d3.selectAll(".info-btn").classed("gray-bg", false);
|
||
d3.selectAll(".info-text").classed('icon-info', false);
|
||
d3.selectAll(".info-text").classed('icon-close', true);
|
||
} else {
|
||
drawerContent
|
||
.transition()
|
||
.style("display", "none");
|
||
d3.selectAll(".info-btn").classed("gray-bg", true);
|
||
d3.selectAll(".info-btn").classed("blue-bg", false);
|
||
d3.selectAll(".info-text").classed('icon-info', true);
|
||
d3.selectAll(".info-text").classed('icon-close', false);
|
||
}
|
||
});
|
||
|
||
var loadingToggle = (function() {
|
||
// 切换为加载HTML状态
|
||
var loading = true;
|
||
|
||
return function() {
|
||
if (loading) {
|
||
d3.select("#content")
|
||
.transition()
|
||
.duration(1000)
|
||
.style("opacity", 1);
|
||
|
||
d3.select("#loading")
|
||
.transition()
|
||
.duration(1000)
|
||
.style("opacity", 0);
|
||
} else {
|
||
d3.select("#content").style("opacity", 0);
|
||
d3.select("#loading")
|
||
.transition()
|
||
.duration(1000)
|
||
.style("opacity", 1);
|
||
}
|
||
}
|
||
})();
|
||
loadingToggle();
|
||
|
||
/*
|
||
* 定义变量
|
||
*/
|
||
|
||
// 监听 .toggles
|
||
(function() {
|
||
var toggles = d3.selectAll(".toggle");
|
||
var data = toggles[0]
|
||
.map(function(elt) {
|
||
return d3.select(elt.getAttribute("data-target"));
|
||
});
|
||
|
||
toggles
|
||
.data(data)
|
||
.on("click", function(d) {
|
||
d3.event.preventDefault();
|
||
if (d.style("display") === "none") {
|
||
d.style("display", "block");
|
||
} else {
|
||
d.style("display", "none");
|
||
}
|
||
});
|
||
})();
|
||
|
||
|
||
// 设置d3的地图
|
||
var width = window.innerWidth,
|
||
height = window.innerHeight;
|
||
|
||
// 从经纬转换为像素坐标
|
||
var projection = d3.geo.mercator()
|
||
.scale(width / 8.5)
|
||
.translate([width / 2, height / 1.7]);
|
||
|
||
// 使用projection将geojson绘制为svg的path
|
||
var path = d3.geo.path().projection(projection);
|
||
|
||
// SVG -- 我们的空白画布
|
||
var svg = d3.select("#content").append("svg")
|
||
.attr("class", "overlay")
|
||
.attr("width", width)
|
||
.attr("height", height);
|
||
|
||
svg.append("defs")
|
||
.append("filter")
|
||
.attr("id", "blur")
|
||
.append("feGaussianBlur")
|
||
.attr("stdDeviation", 2);
|
||
|
||
|
||
// 添加Attacks到 .attacks svg group
|
||
var node = svg.selectAll(".node"),
|
||
link = svg.selectAll(".link");
|
||
|
||
// 目标城市集群
|
||
var _clusters = d3.map();
|
||
|
||
var colorizer = d3.scale.category20();
|
||
// 端口到属性的映射表
|
||
var ports;
|
||
|
||
|
||
/**************
|
||
* 内部API
|
||
*/
|
||
|
||
function spanWrap(content, classes) {
|
||
// Returns the content wrapped in the span
|
||
return '<span class="' + (classes ? classes.join(" ") : "") + '">' +
|
||
content + '</span>'
|
||
}
|
||
|
||
function dist(x1, y1, x2, y2) {
|
||
// Returns the distance between two points
|
||
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
|
||
}
|
||
|
||
function rgbaString(c, a) {
|
||
// Helper to get the color as an rgba string
|
||
return "rgba(" + c.r + "," + c.g + "," + c.b + "," + a + ")";
|
||
}
|
||
|
||
function parsePorts(rawPorts) {
|
||
// Given the csv list of ports, process it
|
||
var ports = [];
|
||
for (var i = 0; i < rawPorts.length; ++i) {
|
||
var port = parseInt(rawPorts[i].port);
|
||
if (port in ports) {
|
||
ports[port] = ports[port] + ", " + rawPorts[i].service;
|
||
} else {
|
||
ports[port] = rawPorts[i].service;
|
||
}
|
||
}
|
||
|
||
// Fix certain port strings
|
||
ports[80] = "http";
|
||
return ports;
|
||
}
|
||
|
||
var getID = (function() {
|
||
// Generate unique enough IDs
|
||
var i = 0;
|
||
return function() {
|
||
return i++;
|
||
}
|
||
})();
|
||
|
||
|
||
function flagPath(iso) {
|
||
// Return the path to the flag for the given countrycode
|
||
if (iso === "O1") {
|
||
return "images/militarywhite.svg";
|
||
} else {
|
||
return "images/flags/" + iso + ".png";
|
||
}
|
||
}
|
||
|
||
function flagTag(iso) {
|
||
return '<span class="flag f16 ' + iso.toLowerCase() + '"></span>';
|
||
}
|
||
|
||
function isNumber(n) {
|
||
// Returns true if n is a number
|
||
return !isNaN(parseFloat(n)) && isFinite(n);
|
||
}
|
||
|
||
var particler = function() {
|
||
// 返回 生成着色粒子的函数
|
||
var particle = new Image(),
|
||
tempFileCanvas = d3.select("#content")
|
||
.append("canvas")
|
||
.attr("class", "buffer")
|
||
.node();
|
||
|
||
particle.src = "data:image/png;base64,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";
|
||
|
||
tempFileCanvas.width = 64;
|
||
tempFileCanvas.height = 64;
|
||
|
||
return function(r, g, b, a) {
|
||
var imgCtx = tempFileCanvas.getContext("2d"),
|
||
imgData, i;
|
||
|
||
imgCtx.drawImage(particle, 0, 0);
|
||
|
||
//if(particle.width > self.innerWidth){particle.width=self.innerWidth;} if(particle.width < 1){particle.width=1;}
|
||
//if(particle.height > self.innerHeight){particle.height=self.innerHeight;} if(particle.height < 1){particle.height=1;}
|
||
imgData = imgCtx.getImageData(2, 2, 64, 64);
|
||
|
||
//imgData = imgCtx.getImageData(0, 0, particle.width, particle.height);
|
||
|
||
i = imgData.data.length;
|
||
while ((i -= 4) > -1) {
|
||
imgData.data[i + 3] = imgData.data[i] * a;
|
||
if (imgData.data[i + 3]) {
|
||
imgData.data[i] = r;
|
||
imgData.data[i + 1] = g;
|
||
imgData.data[i + 2] = b;
|
||
}
|
||
}
|
||
|
||
imgCtx.putImageData(imgData, 0, 0);
|
||
return tempFileCanvas;
|
||
}
|
||
}();
|
||
|
||
var nodeModel = {
|
||
/*
|
||
* 提供了一个API,用于管理各种力量的布局节点
|
||
*
|
||
* Node Types:
|
||
* - Attack Nodes {type: attack}
|
||
* - Target Nodes {type: target}
|
||
* - Anchor Nodes, ie City {type: anchor}
|
||
*/
|
||
|
||
// Configuration
|
||
linkSiblings: true,
|
||
linkAnchor: false,
|
||
target: true,
|
||
interval: 50,
|
||
pathLength: 15,
|
||
targetMaxAge: 200,
|
||
scaleTargetVel: d3.scale.log().domain([1, 40]).range([40, 100]),
|
||
|
||
// Constants
|
||
ATTACKS: "attacks",
|
||
TARGETS: "targets",
|
||
ANCHORS: "anchors",
|
||
|
||
lastPrune: new Date().getTime() / 1000,
|
||
|
||
// The list of nodes, shared with the force layout
|
||
nodes: undefined,
|
||
// The list of links, shared with the force layout
|
||
links: undefined,
|
||
|
||
// Force layout to make the elements move correctly
|
||
force: d3.layout.force()
|
||
.size([width, height])
|
||
.friction(0.25)
|
||
.gravity(0)
|
||
.charge(-10)
|
||
.chargeDistance(50)
|
||
.linkDistance(15)
|
||
.linkStrength(function(d) {
|
||
return d.linkStrength || 0.5;
|
||
}),
|
||
|
||
prune: function() {
|
||
var now = new Date().getTime() / 1000;
|
||
|
||
if (now - this.lastPrune > 10) {
|
||
this.lastPrune = now;
|
||
|
||
for (var i in this.nodes) {
|
||
if (this.nodes[i].pruneTS > now) {
|
||
if (i > 0) {
|
||
this.nodes.splice(0, i);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
for (var i in this.links) {
|
||
if (this.links[i].pruneTS > now) {
|
||
if (i > 0) {
|
||
this.links.splice(0, i);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
get: function(type) {
|
||
return this.nodes.filter(function(n) {
|
||
return n.type === type;
|
||
});
|
||
},
|
||
|
||
_mapKey: function(d) {
|
||
return d.city + d.latitude + d.longitude;
|
||
},
|
||
|
||
_remove: function(n, j) {
|
||
for (var i = 0; i < this.links.length; i++) {
|
||
if (this.links[i].source.id === n.id ||
|
||
this.links[i].target.id === n.id) {
|
||
this.links.splice(i--, 1);
|
||
}
|
||
}
|
||
|
||
if (typeof j !== 'undefined') this.nodes.splice(j, 1);
|
||
|
||
// Remove a node, and its associated links
|
||
for (var i = 0; i < this.nodes.length; i++) {
|
||
if (n.id === this.nodes[i].id) {
|
||
this.nodes.splice(i--, 1);
|
||
}
|
||
}
|
||
|
||
},
|
||
|
||
_shift: function(type) {
|
||
for (var i = 0; i < this.nodes.length; i++) {
|
||
if (this.nodes[i].type === type) {
|
||
this._remove(this.nodes[i], i);
|
||
break;
|
||
}
|
||
}
|
||
},
|
||
|
||
_getsertAnchorFor: function(attack) {
|
||
// Get the anchor for the given node, inserting it if its not present
|
||
var key = this._mapKey(attack),
|
||
anchor = this.nodes.filter(function(n) {
|
||
return n.key === key;
|
||
})[0];
|
||
|
||
if (anchor) {
|
||
return anchor;
|
||
} else {
|
||
var newAnchor = {
|
||
id: getID(),
|
||
key: key,
|
||
type: this.ANCHORS,
|
||
x: attack.cx,
|
||
y: attack.cy,
|
||
cx: attack.cx,
|
||
cy: attack.cy,
|
||
country: attack.country,
|
||
city: attack.city,
|
||
fixed: true,
|
||
pruneTS: (new Date()).getTime() / 1000 + settings.dataPruneInterval
|
||
}
|
||
this.nodes.push(newAnchor);
|
||
return newAnchor;
|
||
}
|
||
},
|
||
|
||
pushAttack: function(attack) {
|
||
/*
|
||
* Push a new Attack
|
||
* Nodes are inserted at the source location and linked to an anchor
|
||
* centered at this source, and all of the adjacent nodes. Links
|
||
* are stronger to nodes of the same type.
|
||
*/
|
||
// Clear out old nodes
|
||
while (this.get(this.ATTACKS).length > 50) {
|
||
this._shift(this.ATTACKS);
|
||
}
|
||
if (this.linkSiblings) {
|
||
var key = this._mapKey(attack),
|
||
that = this;
|
||
this.nodes.forEach(function(n) {
|
||
if (that._mapKey(n) === key) { //&& n.dport === d.dport ) {
|
||
that.links.push({
|
||
source: n,
|
||
target: attack,
|
||
pruneTS: (new Date()).getTime() / 1000 + settings.dataPruneInterval,
|
||
linkStrength: n.dport === attack.dport ? 0.5 : 0.25
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
// Anchor
|
||
var anchor = this._getsertAnchorFor(attack);
|
||
if (this.linkAnchor) {
|
||
this.links.push({
|
||
source: anchor,
|
||
target: attack,
|
||
linkStrength: 1.0
|
||
});
|
||
}
|
||
|
||
// Target
|
||
if (this.target) {
|
||
var initialVelocity = -0.0001;
|
||
var target = {
|
||
type: this.TARGETS,
|
||
age: 0,
|
||
path: [],
|
||
h: dist(attack.x, attack.y, attack.targetX, attack.targetY),
|
||
id: getID(),
|
||
x: attack.x,
|
||
y: attack.y,
|
||
cx: attack.targetX,
|
||
cy: attack.targetY,
|
||
startX: attack.x,
|
||
startY: attack.y,
|
||
city: attack.city2,
|
||
country: attack.country2,
|
||
theta: Math.atan((attack.targetY - attack.y) /
|
||
(attack.targetX - attack.x)),
|
||
|
||
dport: attack.dport,
|
||
pruneTS: (new Date()).getTime() / 1000 + settings.dataPruneInterval
|
||
};
|
||
// this.links.push({source: this._getsertAnchorFor(target),
|
||
// target: target, linkStrength: 1.0});
|
||
this.nodes.push(target);
|
||
}
|
||
|
||
// Decorate and add the attack node
|
||
attack.type = this.ATTACKS;
|
||
attack.age = 0;
|
||
this.nodes.push(attack);
|
||
|
||
// TODO - is this necessary?!
|
||
this.force.start();
|
||
},
|
||
|
||
step: function() {
|
||
// Step the simulation
|
||
this.nodes.forEach(function(n) {
|
||
n.age++;
|
||
});
|
||
|
||
this.get(this.TARGETS)
|
||
.filter(function(t) {
|
||
return t.age > this.targetMaxAge ||
|
||
"arrivalAge" in t && t["arrivalAge"] + 40 < t.age;
|
||
}, this)
|
||
.forEach(function(t) {
|
||
this._remove(t);
|
||
}, this);
|
||
},
|
||
|
||
start: function() {
|
||
// Start the layout
|
||
var that = this;
|
||
|
||
// Initialize the array references
|
||
this.nodes = [];
|
||
this.links = [];
|
||
this.force
|
||
.nodes(this.nodes)
|
||
.links(this.links)
|
||
.on("tick", (function() {
|
||
//var targetTrack;
|
||
|
||
return function(e) {
|
||
// Tick the force layout
|
||
that.step();
|
||
that.get(that.ATTACKS).forEach(function(d) {
|
||
var scale = 0.1;
|
||
d.x += scale * (d.cx - d.x) * e.alpha;
|
||
d.y += scale * (d.cy - d.y) * e.alpha;
|
||
});
|
||
|
||
that.get(that.TARGETS).forEach(function(d) {
|
||
//DEBUGGING
|
||
// if (!targetTrack) targetTrack = d.id;
|
||
// Update the target's path
|
||
d.path.unshift({
|
||
x: d.x,
|
||
y: d.y
|
||
});
|
||
if (d.path.length > that.pathLength)
|
||
d.path.pop();
|
||
|
||
if (d.arrivalAge) {
|
||
d.fixed = true;
|
||
} else {
|
||
var travelled = dist(d.x, d.y, d.startX, d.startY),
|
||
//v = (Math.sqrt(travelled * 50) + 180) * e.alpha,
|
||
v = that.scaleTargetVel(d.age) * e.alpha,
|
||
toTarget = dist(d.cx, d.cy, d.x, d.y);
|
||
|
||
if (v <= toTarget) {
|
||
var theta = Math.atan2(d.cy - d.y, d.cx - d.x);
|
||
//r = v / d.h;
|
||
d.x += v * Math.cos(theta);
|
||
d.y += v * Math.sin(theta);
|
||
} else {
|
||
//debugger;
|
||
// Arrived at target
|
||
d.x = d.cx;
|
||
d.y = d.cy;
|
||
d.arrivalAge = d.age;
|
||
}
|
||
}
|
||
});
|
||
that.force.resume();
|
||
}
|
||
})())
|
||
.start();
|
||
|
||
// Prevent the alpha from 'cooling' to 0
|
||
d3.timer(this.force.resume);
|
||
}
|
||
};
|
||
|
||
// 缓存和查询城市<=>城市链接的抽象模型
|
||
var LinksModel = {
|
||
// {ORIGINCOUNTRY: {ORIGINCITY: {TRGTCOUNTRY: {TRGTCOUNTRY: {DPORT: COUNT}}}}}
|
||
// Created in via .extend: _links: {},
|
||
// {COUNTRY: {CITY: {latitude: LAT, longitude: LON}}}
|
||
_locs: {},
|
||
|
||
insertLink: function(origin, target, port) {
|
||
if (!(origin.country in this._links)) {
|
||
this._links[origin.country] = {};
|
||
}
|
||
|
||
if (!(origin.city in this._links[origin.country])) {
|
||
this._links[origin.country][origin.city] = {};
|
||
}
|
||
|
||
var originLinks = this._links[origin.country][origin.city];
|
||
if (!(target.country in originLinks)) {
|
||
originLinks[target.country] = {};
|
||
}
|
||
|
||
if (!(target.city in originLinks[target.country])) {
|
||
originLinks[target.country][target.city] = {};
|
||
}
|
||
|
||
var targetLinks = originLinks[target.country][target.city]
|
||
if (!(port in targetLinks)) {
|
||
targetLinks[port] = 1;
|
||
} else {
|
||
targetLinks[port] = targetLinks[port] + 1;
|
||
}
|
||
},
|
||
|
||
removeLink: function(origin, target, port) {
|
||
var link1 = this._links[origin.country];
|
||
|
||
if (!link1) return;
|
||
|
||
var link2 = this._links[origin.country][origin.city];
|
||
|
||
if (!link2) return;
|
||
|
||
var target1 = link2[target.country];
|
||
|
||
if (!target1) return;
|
||
|
||
var target2 = link2[target.country][target.city];
|
||
|
||
if (!target2) return;
|
||
|
||
if (target2[port] > 1) {
|
||
target2[port]--;
|
||
} else if (target2[port] !== undefined) {
|
||
delete target2[port];
|
||
}
|
||
},
|
||
|
||
insertLoc: function(loc) {
|
||
if (!(loc.country in this._locs)) {
|
||
this._locs[loc.country] = {}
|
||
}
|
||
|
||
if (!(loc.city in this._locs)) {
|
||
this._locs[loc.country][loc.city] = {
|
||
latitude: loc.latitude,
|
||
longitude: loc.longitude
|
||
};
|
||
}
|
||
},
|
||
|
||
removeLoc: function(loc, origin, target, port) {
|
||
var link1 = this._links[origin.country];
|
||
|
||
if (!link1) return;
|
||
|
||
var link2 = this._links[origin.country][origin.city];
|
||
|
||
if (!link2) return;
|
||
|
||
var target1 = link2[target.country];
|
||
|
||
if (!target1) return;
|
||
|
||
var target2 = link2[target.country][target.city]
|
||
|
||
if (!target2) return;
|
||
|
||
if (!target2[port]) {
|
||
delete this._links[origin.country][origin.city];
|
||
delete this._locs[loc.country][loc.city]
|
||
}
|
||
},
|
||
|
||
_distanceBetween: function(pt1, pt2) {
|
||
return Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) +
|
||
Math.pow(pt1[1] - pt2[1], 2));
|
||
},
|
||
|
||
getCity: function(country, city) {
|
||
if (country in this._locs && city in this._locs[country]) {
|
||
var loc = this._locs[country][city],
|
||
pt = projection([loc.longitude, loc.latitude]),
|
||
info = {
|
||
country: country,
|
||
city: city,
|
||
latitude: loc.latitude,
|
||
longitude: loc.longitude,
|
||
pt: pt
|
||
};
|
||
|
||
if (country in this._links && city in this._links[country]) {
|
||
info.counts = this._links[country][city];
|
||
}
|
||
|
||
return info;
|
||
}
|
||
},
|
||
|
||
getCities: function() {
|
||
// Returns list of all cities
|
||
var cities = [];
|
||
for (var country in this._locs) {
|
||
for (var city in this._locs[country]) {
|
||
cities.push(this.getCity(country, city));
|
||
}
|
||
}
|
||
return cities;
|
||
},
|
||
|
||
getCountry: function(country) {
|
||
var cities = [];
|
||
for (var city in this._links[country] || {}) {
|
||
cities.push(this.getCity(country, city));
|
||
}
|
||
return cities;
|
||
},
|
||
|
||
pixelsFromNearest: function(pt) {
|
||
// Returns the pixels from the nearest source
|
||
var closest;
|
||
for (var country in this._links) {
|
||
for (var city in this._links[country]) {
|
||
var info = this.getCity(country, city);
|
||
|
||
if (info) {
|
||
var distance = this._distanceBetween(pt, info.pt);
|
||
if (!closest || distance < closest.distance) {
|
||
info.distance = distance;
|
||
closest = info;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return closest
|
||
},
|
||
|
||
total: function(counts) {
|
||
// Total up a counts array, recursive
|
||
if (isNumber(counts)) {
|
||
return counts;
|
||
} else {
|
||
var sum = 0;
|
||
for (var key in counts) {
|
||
sum += this.total(counts[key]);
|
||
}
|
||
return sum;
|
||
}
|
||
},
|
||
|
||
cityToLinks: function(city, strokeOrigin, strokeTarget) {
|
||
if (!city) return [];
|
||
|
||
var links = [];
|
||
for (var country in city.counts) {
|
||
if (!city.counts[country]) continue;
|
||
for (var cityKey in city.counts[country]) {
|
||
var info = this.getCity(country, cityKey),
|
||
counts = city.counts[country][cityKey];
|
||
|
||
if (!info || !counts || !Object.keys(counts).length) continue;
|
||
|
||
for (var dport in counts) {
|
||
var r = circleScale(counts[dport]),
|
||
color = colorizer(dport),
|
||
source = {
|
||
x: city.pt[0],
|
||
y: city.pt[1],
|
||
r: r
|
||
},
|
||
target = {
|
||
x: info.pt[0],
|
||
y: info.pt[1],
|
||
r: r
|
||
};
|
||
|
||
if (strokeTarget) {
|
||
source.strokeStyle = color;
|
||
} else {
|
||
source.fillStyle = color;
|
||
}
|
||
|
||
if (strokeOrigin) {
|
||
target.strokeStyle = color;
|
||
} else {
|
||
target.fillStyle = color;
|
||
}
|
||
|
||
links.push({
|
||
count: counts[dport],
|
||
source: source,
|
||
target: target,
|
||
width: lineScale(counts[dport]),
|
||
color: color
|
||
});
|
||
}
|
||
}
|
||
}
|
||
return links.sort(function(l1, l2) {
|
||
return l2.count - l1.count;
|
||
});
|
||
},
|
||
|
||
dPortLinks: function(dport) {
|
||
var links = []
|
||
for (var sourceCountry in this._links) {
|
||
for (var sourceCity in this._links[sourceCountry]) {
|
||
var s = this.getCity(sourceCountry, sourceCity);
|
||
for (var targetCountry in s.counts) {
|
||
for (var targetCity in s.counts[targetCountry]) {
|
||
var t = this.getCity(targetCountry, targetCity);
|
||
for (var targetdport in s.counts[targetCountry][targetCity]) {
|
||
var c = s.counts[targetCountry][targetCity][targetdport];
|
||
if (dport && dport === targetdport) {
|
||
links.push({
|
||
count: c,
|
||
source: s,
|
||
target: t,
|
||
dport: dport
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return links;
|
||
},
|
||
|
||
extend: function(o) {
|
||
o.__proto__ = this;
|
||
o._links = {};
|
||
return o;
|
||
}
|
||
};
|
||
|
||
// Set up the link models, one for origins, and one for targets
|
||
var linkModels = {
|
||
origins: LinksModel.extend({
|
||
insert: function(d) {
|
||
this.insertLink({
|
||
country: d.country,
|
||
city: d.city
|
||
}, {
|
||
country: d.country2,
|
||
city: d.city2
|
||
},
|
||
d.dport);
|
||
|
||
this.insertLoc({
|
||
country: d.country,
|
||
city: d.city,
|
||
latitude: d.latitude,
|
||
longitude: d.longitude
|
||
});
|
||
},
|
||
remove: function(d) {
|
||
this.removeLink({
|
||
country: d.country,
|
||
city: d.city
|
||
}, {
|
||
country: d.country2,
|
||
city: d.city2
|
||
},
|
||
d.dport);
|
||
|
||
this.removeLoc({
|
||
country: d.country,
|
||
city: d.city,
|
||
latitude: d.latitude,
|
||
longitude: d.longitude
|
||
}, {
|
||
country: d.country,
|
||
city: d.city
|
||
}, {
|
||
country: d.country2,
|
||
city: d.city2
|
||
},
|
||
d.dport);
|
||
}
|
||
}),
|
||
|
||
targets: LinksModel.extend({
|
||
insert: function(d) {
|
||
this.insertLink({
|
||
country: d.country2,
|
||
city: d.city2
|
||
}, {
|
||
country: d.country,
|
||
city: d.city
|
||
},
|
||
d.dport);
|
||
|
||
this.insertLoc({
|
||
country: d.country2,
|
||
city: d.city2,
|
||
latitude: d.latitude2,
|
||
longitude: d.longitude2
|
||
});
|
||
},
|
||
remove: function(d) {
|
||
this.removeLink({
|
||
country: d.country2,
|
||
city: d.city2
|
||
}, {
|
||
country: d.country,
|
||
city: d.city
|
||
},
|
||
d.dport);
|
||
|
||
this.removeLoc({
|
||
country: d.country2,
|
||
city: d.city2,
|
||
latitude: d.latitude2,
|
||
longitude: d.longitude2
|
||
}, {
|
||
country: d.country2,
|
||
city: d.city2
|
||
}, {
|
||
country: d.country,
|
||
city: d.city
|
||
},
|
||
d.dport);
|
||
}
|
||
})
|
||
};
|
||
|
||
var countryModel = {
|
||
// The raw list of data
|
||
_raw: undefined,
|
||
_iso2: undefined,
|
||
_iso3: undefined,
|
||
_countries: undefined,
|
||
|
||
push: function(country) {
|
||
// Push a new country
|
||
this._raw.push(country);
|
||
if (country.iso2) this._iso2[country.iso2] = country;
|
||
if (country.iso3) this._iso3[country.iso3] = country;
|
||
if (country.country) this._countries[country.country] = country;
|
||
},
|
||
|
||
set: function(raw) {
|
||
this._raw = [];
|
||
this._iso2 = {};
|
||
this._iso3 = {};
|
||
this._countries = {};
|
||
for (var i = 0; i < raw.length; i++) {
|
||
this.push(raw[i]);
|
||
}
|
||
},
|
||
|
||
getByIso2: function(iso2) {
|
||
return this._iso2[iso2.toUpperCase()];
|
||
},
|
||
|
||
getByIso3: function(iso3) {
|
||
return this._iso3[iso3.toUpperCase()];
|
||
},
|
||
|
||
getByCountry: function(country) {
|
||
return this._countries[country];
|
||
}
|
||
};
|
||
|
||
/*
|
||
* painter handles rendering to the canvas
|
||
*/
|
||
|
||
// Prepare canvas and buffer
|
||
var canvas = d3.select("#content").append("canvas")
|
||
.text("This browser doesn't support Canvas elements")
|
||
.attr("id", "visible-canvas")
|
||
.attr("class", "overlay")
|
||
.attr("width", width)
|
||
.attr("height", height)
|
||
|
||
var bufCanvas = d3.select("#content").append("canvas")
|
||
.attr("class", "buffer overlay")
|
||
.attr("width", width)
|
||
.attr("height", height);
|
||
|
||
// 使得绘制的元素比例一致
|
||
var logScale = d3.scale.log()
|
||
.domain([1, 200])
|
||
.range([1, 10]);
|
||
|
||
var lineScale = function(x) {
|
||
return logScale(x)
|
||
};
|
||
var circleScale = function(x) {
|
||
return Math.ceil(1.4 * logScale(x))
|
||
};
|
||
var colorScale = (function() {
|
||
var log = d3.scale.log()
|
||
.domain([1, 600])
|
||
.range([1, 100]);
|
||
return function(v) {
|
||
return log(v);
|
||
}
|
||
})();
|
||
|
||
var painter = {
|
||
// The various drawings
|
||
drawings: {
|
||
// Draw the attack nodes
|
||
nodes: {
|
||
// The visible nodes
|
||
active: true,
|
||
nodeModel: nodeModel,
|
||
// Canvas composition: "lighter", "darker", ...
|
||
compositeOperation: undefined,
|
||
|
||
getRadius: function(d) {
|
||
// Given a node, return the radius
|
||
var growthEnd = 60,
|
||
growthMax = 80,
|
||
growthStep = growthMax / growthEnd,
|
||
shrinkEnd = 120,
|
||
shrinkMin = 20,
|
||
shrinkStep = (growthMax - shrinkMin) / (shrinkEnd - growthEnd);
|
||
|
||
if (d.age >= 0 && d.age < growthEnd) {
|
||
return growthStep * d.age;
|
||
} else if (d.age < shrinkEnd) {
|
||
return growthMax - shrinkStep * (d.age - growthEnd);
|
||
} else {
|
||
return shrinkMin;
|
||
}
|
||
},
|
||
|
||
draw: function(context) {
|
||
nodeModel.prune();
|
||
|
||
if (this.compositeOperation) {
|
||
context.globalCompositeOperation = this.compositeOperation;
|
||
}
|
||
|
||
var that = this;
|
||
nodeModel.get(nodeModel.ATTACKS)
|
||
.forEach(function(n) {
|
||
var c = d3.rgb(colorizer(n.dport)),
|
||
r = that.getRadius(n);
|
||
context.drawImage(particler(c.r, c.g, c.b, 1),
|
||
n.x - r / 2, n.y - r / 2, r, r);
|
||
});
|
||
|
||
}
|
||
},
|
||
|
||
// Draw new node pings
|
||
pings: {
|
||
active: true,
|
||
order: -1,
|
||
duration: 80,
|
||
scaleRadius: d3.scale.linear().domain([1, 80]).range([1, 48]),
|
||
scaleOpacity: d3.scale.linear().domain([1, 80]).range([1, 0]),
|
||
draw: function(context) {
|
||
var pi = Math.PI;
|
||
for (var i = 0; i < nodeModel.nodes.length; i++) {
|
||
var n = nodeModel.nodes[i];
|
||
if (n.type === nodeModel.ATTACKS && n.age < this.duration) {
|
||
context.globalAlpha = this.scaleOpacity(n.age);
|
||
context.strokeStyle = colorizer(n.dport);
|
||
context.lineWidth = 3;
|
||
context.beginPath();
|
||
context.arc(n.x, n.y, this.scaleRadius(n.age),
|
||
0, 2 * pi);
|
||
context.stroke();
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
// Draw the origin nodes
|
||
origins: {
|
||
active: true,
|
||
order: -2,
|
||
draw: function(context) {
|
||
context.globalAlpha = 0.65;
|
||
context.fillStyle = "#fff";
|
||
|
||
var cities = linkModels.origins.getCities();
|
||
|
||
var ceil = Math.ceil;
|
||
var pi = Math.PI;
|
||
|
||
for (var i = 0; i < cities.length; i++) {
|
||
var total = linkModels.origins.total(cities[i].counts),
|
||
latlng = projection([cities[i].longitude,
|
||
cities[i].latitude
|
||
]),
|
||
r = ceil(circleScale(total));
|
||
context.beginPath();
|
||
context.arc(latlng[0], latlng[1], r, 0, pi * 2);
|
||
context.fill();
|
||
}
|
||
}
|
||
},
|
||
|
||
// Draw the city nodes
|
||
targets: {
|
||
active: true,
|
||
order: -2,
|
||
draw: function(context) {
|
||
context.globalAlpha = 0.65;
|
||
context.strokeStyle = "#fff";
|
||
context.fillStyle = "#fff";
|
||
|
||
var cities = linkModels.targets.getCities();
|
||
|
||
var ceil = Math.ceil;
|
||
var pi = Math.PI;
|
||
|
||
for (var i = 0; i < cities.length; i++) {
|
||
var total = linkModels.targets.total(cities[i].counts),
|
||
latlng = projection([cities[i].longitude,
|
||
cities[i].latitude
|
||
]),
|
||
r = ceil(circleScale(total));
|
||
context.beginPath();
|
||
context.arc(latlng[0], latlng[1], r, 0, pi * 2);
|
||
context.stroke();
|
||
context.fillRect(latlng[0] - 1, latlng[1] - 1, 2, 2);
|
||
}
|
||
}
|
||
},
|
||
|
||
// Draw the target impact
|
||
targetImpact: {
|
||
active: true,
|
||
impactRadiusScale: d3.scale.linear().domain([1, 40]).range([1, 30]),
|
||
impactOpacityScale: d3.scale.linear().domain([1, 40]).range([1, 0]),
|
||
impactWidth: 1,
|
||
|
||
draw: function(ctx) {
|
||
var pi = Math.PI;
|
||
|
||
ctx.fillStyle = "#f00";
|
||
nodeModel.get(nodeModel.TARGETS)
|
||
.forEach(function(n) {
|
||
var c = d3.rgb(colorizer(n.dport)),
|
||
afterArrival = n.age - n["arrivalAge"];
|
||
r = 10;
|
||
|
||
if ("arrivalAge" in n) {
|
||
var r = this.impactRadiusScale(afterArrival);
|
||
ctx.save();
|
||
ctx.globalAlpha = this.impactOpacityScale(afterArrival);
|
||
ctx.strokeStyle = c.toString();
|
||
ctx.lineWidth = this.impactWidth;
|
||
ctx.beginPath();
|
||
ctx.arc(n.x, n.y, r, 0, 2 * pi);
|
||
ctx.closePath();
|
||
ctx.stroke();
|
||
ctx.restore();
|
||
}
|
||
|
||
if (n.path.length > 0) {
|
||
var point = n.path[n.path.length - 1];
|
||
if (n.x != point.x && n.y != point.y) {
|
||
ctx.save();
|
||
var grd = ctx.createLinearGradient(
|
||
n.x, n.y, point.x, point.y);
|
||
grd.addColorStop(0, rgbaString(c, 1));
|
||
grd.addColorStop(1, rgbaString(c, 0));
|
||
ctx.lineCap = 'round';
|
||
ctx.lineWidth = 2;
|
||
ctx.beginPath();
|
||
ctx.moveTo(n.x, n.y);
|
||
ctx.strokeStyle = grd;
|
||
ctx.lineTo(point.x, point.y);
|
||
ctx.closePath();
|
||
ctx.stroke();
|
||
ctx.restore();
|
||
}
|
||
}
|
||
|
||
ctx.drawImage(particler(c.r, c.g, c.b, 1),
|
||
n.x - r / 2, n.y - r / 2, r, r);
|
||
|
||
}, this);
|
||
}
|
||
},
|
||
|
||
// Draw the provided links
|
||
// A link: {source: _, target: _, color: _, width: _}
|
||
// source/target: {x: _, y: _, strokeStyle: _, fillStyle: _}
|
||
links: {
|
||
active: true,
|
||
order: -1,
|
||
data: [],
|
||
|
||
draw: function(context) {
|
||
var pi = Math.PI;
|
||
|
||
for (var i = 0; i < this.data.length; i++) {
|
||
context.beginPath();
|
||
context.strokeStyle = this.data[i].color;
|
||
context.moveTo(this.data[i].source.x, this.data[i].source.y);
|
||
context.lineTo(this.data[i].target.x, this.data[i].target.y);
|
||
context.lineWidth = this.data[i].width;
|
||
context.lineCap = "round";
|
||
context.stroke();
|
||
|
||
context.lineWidth = 2;
|
||
context.beginPath();
|
||
context.arc(this.data[i].source.x, this.data[i].source.y,
|
||
this.data[i].source.r || 5, 0, pi * 2);
|
||
if (this.data[i].source.fillStyle) {
|
||
context.fillStyle = this.data[i].source.fillStyle;
|
||
context.fill();
|
||
}
|
||
if (this.data[i].source.strokeStyle) {
|
||
context.strokeStyle = this.data[i].source.strokeStyle;
|
||
context.stroke();
|
||
}
|
||
|
||
context.beginPath();
|
||
context.fillStyle = this.data[i].target.fillStyle || "#fff";
|
||
context.arc(this.data[i].target.x, this.data[i].target.y,
|
||
this.data[i].target.r || 5, 0, pi * 2);
|
||
if (this.data[i].target.fillStyle) {
|
||
context.fillStyle = this.data[i].target.fillStyle;
|
||
context.fill();
|
||
}
|
||
if (this.data[i].target.strokeStyle) {
|
||
context.strokeStyle = this.data[i].target.strokeStyle;
|
||
context.stroke();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
},
|
||
|
||
// State
|
||
_activeCanvas: {
|
||
canvas: canvas,
|
||
context: canvas.node().getContext("2d")
|
||
},
|
||
|
||
_clearContext: function(context) {
|
||
context.save();
|
||
context.clearRect(0, 0, width, height);
|
||
context.restore();
|
||
},
|
||
|
||
_drawSort: function(d1, d2) {
|
||
return d1.order || 0 - d2.order || 0;
|
||
},
|
||
|
||
redraw: function() {
|
||
this.drawStart = new Date().getTime();
|
||
|
||
// Draw the active drawings
|
||
this._clearContext(this._activeCanvas.context);
|
||
|
||
for (var drawing in this.drawings) {
|
||
if (this.drawings[drawing].active) {
|
||
this.drawings[drawing].draw(this._activeCanvas.context);
|
||
}
|
||
}
|
||
|
||
var that = this;
|
||
|
||
var nextFrame = 1000 / 30 - (new Date().getTime() - this.drawStart);
|
||
|
||
if (nextFrame < 0) nextFrame = 0;
|
||
|
||
this._timeout = setTimeout(function() {
|
||
that.redraw()
|
||
}, nextFrame);
|
||
},
|
||
|
||
start: function(interval) {
|
||
this.redraw();
|
||
},
|
||
|
||
stop: function() {
|
||
clearTimeout(this._timeout);
|
||
}
|
||
};
|
||
|
||
// Event handling for setting the painter's links
|
||
var _ = (function() {
|
||
var previous;
|
||
|
||
function cityKey(n) {
|
||
return [n.country, n.city];
|
||
}
|
||
|
||
canvas.on("mousemove", function() {
|
||
// TODO -- this is a hacky mess
|
||
var nearestOrigin = linkModels.origins.pixelsFromNearest(d3.mouse(this)),
|
||
nearestTarget = linkModels.targets.pixelsFromNearest(d3.mouse(this)),
|
||
nearest, msg, model, mapfn, strokeOrigin, strokeTarget;
|
||
if (nearestOrigin || nearestTarget) {
|
||
if (!nearestTarget || nearestOrigin.distance <= nearestTarget.distance) {
|
||
nearest = nearestOrigin;
|
||
msg = "Attacks originating from: ";
|
||
model = linkModels.origins;
|
||
strokeOrigin = true;
|
||
}
|
||
if (!nearestOrigin || nearestTarget.distance < nearestOrigin.distance) {
|
||
nearest = nearestTarget;
|
||
msg = "Attacks targeting: ";
|
||
model = linkModels.targets;
|
||
strokeTarget = true;
|
||
}
|
||
}
|
||
|
||
if (nearest && nearest.distance < 20) {
|
||
var key = cityKey(nearest);
|
||
if (key !== previous) {
|
||
painter.drawings.links.data =
|
||
model.cityToLinks(nearest, strokeOrigin, strokeTarget);
|
||
displayLabel.set(msg +
|
||
(nearest.city === "" ? "<unknown>" : nearest.city) + ", " + countryModel.getByIso2(nearest.country).country);
|
||
}
|
||
} else {
|
||
previous = key;
|
||
painter.drawings.links.data.length = 0;
|
||
displayLabel.clear();
|
||
}
|
||
});
|
||
})();
|
||
|
||
/*
|
||
* The legend
|
||
*/
|
||
(function() {
|
||
var legend = d3.select("#legend-container")
|
||
.append("div")
|
||
.attr("id", "legend");
|
||
|
||
var attack = legend.append("div"),
|
||
width = 20,
|
||
height = 20;
|
||
attack.append("h4").text("每个粒子代表一个攻击");
|
||
attack.append("canvas")
|
||
.attr("width", width * 2)
|
||
.attr("height", height * 2)
|
||
.node().getContext("2d")
|
||
.drawImage(particler(255, 255, 255, 1),
|
||
width / 2, height / 2, width, height);
|
||
|
||
var clusters = legend.append("div").attr("class", "clusters");
|
||
clusters.append("h4").text("这是攻击来源");
|
||
|
||
var height = 30;
|
||
var clusterList = clusters.append("ul").selectAll("li")
|
||
.data([1, 10, 200])
|
||
.enter().append("li");
|
||
clusterList.append("svg")
|
||
.style("width", function(d) {
|
||
return circleScale(d) * 2;
|
||
})
|
||
.style("height", height)
|
||
.append("circle")
|
||
.attr("fill", "white")
|
||
.attr("cy", function(d) {
|
||
return height - circleScale(d);
|
||
})
|
||
.attr("cx", function(d) {
|
||
return circleScale(d);
|
||
})
|
||
.attr("r", function(d) {
|
||
return circleScale(d);
|
||
});
|
||
clusterList.append("p")
|
||
.text(function(d) {
|
||
return d;
|
||
});
|
||
|
||
var countryColors = legend.append("div").attr("class", "country-colors");
|
||
countryColors.append("h4").text("这是被攻击国家");
|
||
|
||
var r = 4;
|
||
var countryColorList = countryColors.append("ul").selectAll("li")
|
||
.data([1, 5, 25, 100, 500])
|
||
.enter().append("li");
|
||
countryColorList.append("svg")
|
||
.style("width", r * 2)
|
||
.style("height", r * 2)
|
||
.append("circle")
|
||
.attr("fill", function(d) {
|
||
return settings.countryColor(d);
|
||
})
|
||
.attr("cy", r)
|
||
.attr("cx", r)
|
||
.attr("r", r);
|
||
countryColorList.append("p")
|
||
.text(function(d) {
|
||
return d;
|
||
});
|
||
})(window);
|
||
|
||
/*
|
||
* Stats the state machine
|
||
*/
|
||
function Stats(params) {
|
||
this.state = params.state || d3.map();
|
||
this.elt = params.elt || d3.select("body");
|
||
this.tag = params.tag || "div";
|
||
|
||
this.insert = function(incoming) {
|
||
// Insert a new item, updating the state. params.insert should mutate
|
||
params.insert(incoming, this.state);
|
||
};
|
||
|
||
this.data = function() {
|
||
// Get the data as a list
|
||
if (params.data) return params.data(this.state);
|
||
else this.state;
|
||
}
|
||
|
||
this.redraw = function() {
|
||
if (params.redraw) {
|
||
params.redraw()
|
||
} else {
|
||
this.elt.selectAll(this.tag)
|
||
.data(this.data())
|
||
.enter().append(this.tag)
|
||
.text(function(d) {
|
||
return d
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
var statsManager = {
|
||
insert: function(incoming) {
|
||
for (var i = 0; i < this.stats.length; ++i) {
|
||
this.stats[i].insert(incoming);
|
||
}
|
||
},
|
||
|
||
redraw: function() {
|
||
for (var i = 0; i < this.stats.length; i++) {
|
||
this.stats[i].redraw();
|
||
}
|
||
},
|
||
|
||
stats: [
|
||
new Stats({
|
||
// Color countries
|
||
state: {
|
||
sources: {},
|
||
targets: {}
|
||
},
|
||
|
||
insert: function(d, state) {
|
||
function pushState(m, key) {
|
||
if (key in m) {
|
||
m[key] = m[key] + 1;
|
||
} else {
|
||
m[key] = 1;
|
||
}
|
||
}
|
||
|
||
//pushState(state.sources, d.country);
|
||
pushState(state.targets, d.country2);
|
||
|
||
//d3.select("#" + d.country)
|
||
// .attr("fill", d3.rgb(colorScale(state.sources[d.country]), 0, 0).toString());
|
||
d3.select("#" + d.country2)
|
||
.attr("fill", settings.countryColor(state.targets[d.country2]));
|
||
},
|
||
|
||
redraw: function() {}
|
||
}),
|
||
|
||
// Origin data table
|
||
new Stats({
|
||
state: d3.map(),
|
||
|
||
insert: function(incoming, state) {
|
||
this.updated = incoming.country;
|
||
if (state.has(incoming.country)) {
|
||
state.set(incoming.country,
|
||
state.get(incoming.country) + 1);
|
||
} else {
|
||
state.set(incoming.country, 1);
|
||
}
|
||
},
|
||
|
||
redraw: function() {
|
||
var data = this.state.entries()
|
||
.sort(function(d1, d2) {
|
||
return d2.value - d1.value;
|
||
})
|
||
.slice(0, settings.topTableRows),
|
||
updated = this.updated;
|
||
|
||
var rows = d3.select("#left-data").selectAll("tr.row")
|
||
.data(data, function(d) {
|
||
return d.key;
|
||
});
|
||
rows.enter()
|
||
.append("tr")
|
||
.attr("class", "row")
|
||
.on("mouseenter", function(d) {
|
||
var country = countryModel.getByIso2(d.key);
|
||
displayLabel.set("Attacks originating from: " +
|
||
(country ? country.country : d.key));
|
||
|
||
// On mouseenter, use all country data to create links
|
||
painter.drawings.links.data =
|
||
linkModels.origins.getCountry(d.key)
|
||
.reduce(function(acc, city) {
|
||
return acc.concat(
|
||
linkModels.origins.cityToLinks(
|
||
city, true, false));
|
||
}, []);
|
||
})
|
||
.on("mouseleave", function() {
|
||
displayLabel.clear();
|
||
});
|
||
rows.sort(function(d1, d2) {
|
||
return d2.value - d1.value;
|
||
});
|
||
rows.exit().remove();
|
||
|
||
rows.filter(function(d) {
|
||
return d.key == updated
|
||
})
|
||
.style("color", settings.triggerColor)
|
||
.transition()
|
||
.duration(1000)
|
||
.style("color", "white");
|
||
|
||
var cols = rows.selectAll("td")
|
||
.data(function(d) {
|
||
var country = countryModel.getByIso2(d.key);
|
||
return [
|
||
'<div class="bar" style="width: ' +
|
||
settings.tableBarWidth(d.value + 1) + '"></div>',
|
||
spanWrap(d.value, ["numeric"]),
|
||
flagTag(d.key),
|
||
(country ? country.country : d.key)
|
||
];
|
||
});
|
||
cols.enter().append("td");
|
||
cols.html(function(d) {
|
||
return d;
|
||
});
|
||
cols.exit().remove();
|
||
}
|
||
}),
|
||
|
||
// Targeted data table
|
||
new Stats({
|
||
state: d3.map(),
|
||
|
||
insert: function(incoming, state) {
|
||
this.updated = incoming.country2;
|
||
if (state.has(incoming.country2)) {
|
||
state.set(incoming.country2,
|
||
state.get(incoming.country2) + 1);
|
||
} else {
|
||
state.set(incoming.country2, 1);
|
||
}
|
||
},
|
||
|
||
redraw: function() {
|
||
var data = this.state.entries()
|
||
.sort(function(d1, d2) {
|
||
return d2.value - d1.value;
|
||
})
|
||
.slice(0, settings.topTableRows),
|
||
updated = this.updated;
|
||
|
||
var rows = d3.select("#right-data").selectAll("tr.row")
|
||
.data(data, function(d) {
|
||
return d.key;
|
||
});
|
||
rows.enter()
|
||
.append("tr")
|
||
.attr("class", "row")
|
||
.on("mouseenter", function(d) {
|
||
var country = countryModel.getByIso2(d.key);
|
||
displayLabel.set("Attacks targeting: " +
|
||
(country ? country.country : d.key));
|
||
|
||
// On mouseenter, use all country data to create links
|
||
painter.drawings.links.data =
|
||
linkModels.targets.getCountry(d.key)
|
||
.reduce(function(acc, city) {
|
||
return acc.concat(
|
||
linkModels.targets.cityToLinks(
|
||
city, false, true));
|
||
}, []);
|
||
})
|
||
.on("mouseleave", function() {
|
||
displayLabel.clear();
|
||
});
|
||
rows.sort(function(d1, d2) {
|
||
return d2.value - d1.value;
|
||
});
|
||
rows.exit().remove();
|
||
|
||
rows.filter(function(d) {
|
||
return d.key == updated
|
||
})
|
||
.style("color", "blue")
|
||
.transition()
|
||
.duration(1000)
|
||
.style("color", "white");
|
||
|
||
var cols = rows.selectAll("td")
|
||
.data(function(d) {
|
||
var country = countryModel.getByIso2(d.key);
|
||
return [
|
||
'<div class="bar" style="width: ' +
|
||
settings.tableBarWidth(d.value + 1) + '"></div>',
|
||
spanWrap(d.value, ["numeric"]),
|
||
flagTag(d.key),
|
||
(country ? country.country : d.key)
|
||
];
|
||
});
|
||
cols.enter().append("td");
|
||
cols.html(function(d) {
|
||
return d;
|
||
});
|
||
cols.exit().remove();
|
||
}
|
||
}),
|
||
|
||
new Stats({
|
||
state: d3.map(),
|
||
|
||
insert: function(incoming) {
|
||
this.updated = incoming.dport;
|
||
if (this.state.has(incoming.dport)) {
|
||
this.state.set(incoming.dport,
|
||
this.state.get(incoming.dport) + 1);
|
||
} else {
|
||
this.state.set(incoming.dport, 1);
|
||
}
|
||
},
|
||
|
||
redraw: function() {
|
||
var data = this.state.entries()
|
||
.sort(function(d1, d2) {
|
||
return d2.value - d1.value;
|
||
})
|
||
.slice(0, settings.portTableRows),
|
||
updated = this.updated;
|
||
|
||
var rows = d3.select("#bottom-right-data").selectAll("tr.row")
|
||
// XXX - I'm not sure why d is undef the first time through.
|
||
.data(data, function(d, i) {
|
||
return d ? d.key : i;
|
||
});
|
||
rows.enter()
|
||
.append("tr")
|
||
.attr("class", "row")
|
||
.on("mouseenter", function(d) {
|
||
var port = ports[d.key];
|
||
displayLabel.set("Attacks made over: " +
|
||
(port ? port : "unknown") +
|
||
" [" + d.key + "]");
|
||
painter.drawings.links.data =
|
||
linkModels.origins.dPortLinks(d.key).map(function(c) {
|
||
var r = circleScale(c.count),
|
||
color = colorizer(c.dport);
|
||
return {
|
||
source: {
|
||
x: c.source.pt[0],
|
||
y: c.source.pt[1],
|
||
fillStyle: color,
|
||
r: r
|
||
},
|
||
target: {
|
||
x: c.target.pt[0],
|
||
y: c.target.pt[1],
|
||
strokeStyle: color,
|
||
r: r
|
||
},
|
||
width: lineScale(c.count),
|
||
color: colorizer(c.dport)
|
||
}
|
||
});
|
||
})
|
||
.on("mouseleave", function(d) {
|
||
displayLabel.clear();
|
||
})
|
||
rows.sort(function(d1, d2) {
|
||
return d2.value - d1.value;
|
||
});
|
||
rows.exit().remove();
|
||
|
||
rows.filter(function(d) {
|
||
return d.key == updated
|
||
})
|
||
.style("color", function(d) {
|
||
return colorizer(d.key);
|
||
})
|
||
.transition()
|
||
.duration(1000)
|
||
.style("color", "white");
|
||
|
||
var cols = rows.selectAll("td")
|
||
.data(function(d) {
|
||
return [
|
||
'<div class="bar" style="width: ' +
|
||
settings.tableBarWidth(d.value + 1) + '"></div>',
|
||
spanWrap(d.value, ["numeric"]),
|
||
'<span class="port-circle" style="color:' + colorizer(d.key) +
|
||
'">●</span>',
|
||
ports[d.key] ? ports[d.key] : "unknown",
|
||
spanWrap(d.key, ["numeric"])
|
||
];
|
||
});
|
||
//function(d) { return d; });
|
||
cols.enter().append("td");
|
||
cols.html(function(d) {
|
||
return d;
|
||
});
|
||
cols.exit().remove();
|
||
}
|
||
}),
|
||
|
||
new Stats({
|
||
// #console stats manager
|
||
state: [],
|
||
|
||
insert: function(incoming, state) {
|
||
state.push(incoming);
|
||
while (state.length > settings.consoleTableRows) {
|
||
state.shift();
|
||
}
|
||
return state;
|
||
},
|
||
|
||
redraw: function() {
|
||
var that = this;
|
||
var rows = d3.select("#events-data").selectAll("tr.row")
|
||
.data(this.state, function(d) {
|
||
return d.id;
|
||
});
|
||
|
||
rows.enter().append("tr")
|
||
.style("color", function(d) {
|
||
return colorizer(d.dport);
|
||
})
|
||
.attr("class", "row");
|
||
rows.exit().remove();
|
||
|
||
var cols = rows.selectAll("td")
|
||
.data(function(d) {
|
||
return [
|
||
d.datetime,
|
||
spanWrap(d.org, ["org", "overflow"]),
|
||
spanWrap(
|
||
(d.city === "" ? "unknown" : d.city) + ", " +
|
||
countryModel.getByIso2(d.country).country, ["location", "overflow"]),
|
||
d.md5,
|
||
spanWrap(
|
||
(d.city2 === "" ? "unknown" : d.city2) + ", " +
|
||
countryModel.getByIso2(d.country2).country, ["location", "overflow", "numeric"]),
|
||
spanWrap(d.service || "unknown", ["service", "overflow"]),
|
||
spanWrap(d.dport, ["numeric"])
|
||
];
|
||
});
|
||
cols.enter().append("td")
|
||
.html(function(d) {
|
||
return d;
|
||
});
|
||
cols.exit().remove();
|
||
}
|
||
|
||
})
|
||
]
|
||
};
|
||
|
||
// Keep track of the rate
|
||
var rateTicker = {
|
||
data: [],
|
||
interval: 90000,
|
||
graph: d3.select("#content")
|
||
.append("svg")
|
||
.attr("id", "rate-graph"),
|
||
x: d3.time.scale().range([800, 0]),
|
||
opacity: d3.time.scale().range([0, 1]),
|
||
|
||
push: function(d) {
|
||
this.data.push({
|
||
date: Date.now(),
|
||
key: d.dport
|
||
});
|
||
},
|
||
|
||
clean: function(toDate) {
|
||
// Clean out old data
|
||
while (this.data.length > 0 && this.data[0].date < toDate) {
|
||
this.data.shift();
|
||
}
|
||
},
|
||
|
||
step: function() {
|
||
var that = this,
|
||
now = Date.now(),
|
||
range = [now - this.interval, now];
|
||
this.clean(range[0]);
|
||
this.x.domain(range);
|
||
this.opacity.domain(range);
|
||
|
||
var simpleLine = function(h) {
|
||
return function(d) {
|
||
var cx = that.x(d.date);
|
||
return d3.svg.line()([
|
||
[cx, 0],
|
||
[cx, h || 20]
|
||
])
|
||
}
|
||
};
|
||
var ticks = this.graph.selectAll("path.tick")
|
||
.data(this.data, function(d) {
|
||
return d.date;
|
||
});
|
||
|
||
ticks.enter().append("path")
|
||
.attr("class", "tick")
|
||
.attr("d", simpleLine())
|
||
.style("stroke", function(d) {
|
||
return colorizer(d.key);
|
||
});
|
||
|
||
ticks.exit().remove();
|
||
|
||
ticks
|
||
//.transition().duration(30)
|
||
.attr("d", simpleLine())
|
||
.style("opacity", function(d) {
|
||
return that.opacity(d.date);
|
||
});
|
||
},
|
||
|
||
start: function() {
|
||
var that = this;
|
||
setInterval(function() {
|
||
that.step();
|
||
}, 50);
|
||
}
|
||
};
|
||
|
||
|
||
var wsDiscTime = 0;
|
||
|
||
function start(loc, psk) {
|
||
var webSocket = new WebSocket(loc || settings.wsHost);
|
||
|
||
var pauser = {
|
||
elt: d3.selectAll(".controls"),
|
||
_buffer: [],
|
||
|
||
unbuffer: function(d) {
|
||
while (this._buffer.length > 0) {
|
||
this.insert(this._buffer.shift());
|
||
}
|
||
statsManager.redraw();
|
||
},
|
||
|
||
insert: function(d) {
|
||
nodeModel.pushAttack(d);
|
||
statsManager.insert(d);
|
||
for (var model in linkModels) {
|
||
linkModels[model].insert(d);
|
||
}
|
||
rateTicker.push(d);
|
||
|
||
d.pruneTS = new Date().getTime() / 1000 + settings.pruneInterval;
|
||
timestampedData.push(d);
|
||
},
|
||
|
||
push: function(d) {
|
||
if (this.paused()) {
|
||
this._buffer.push(d);
|
||
} else {
|
||
this.insert(d);
|
||
statsManager.redraw();
|
||
}
|
||
},
|
||
|
||
paused: function() {
|
||
return this.elt.node().dataset.paused === "true";
|
||
},
|
||
|
||
toggle: function() {
|
||
var dataset = this.elt.node().dataset;
|
||
var button = d3.selectAll(".play-pause");
|
||
|
||
if (this.paused()) {
|
||
button.classed("icon-play", false);
|
||
button.classed("icon-pause", true);
|
||
this.elt.node().dataset.paused = "false";
|
||
this.unbuffer();
|
||
} else {
|
||
button.classed("icon-pause", false);
|
||
button.classed("icon-play", true);
|
||
this.elt.node().dataset.paused = "true";
|
||
}
|
||
}
|
||
};
|
||
pauser.elt.on("click", function() {
|
||
pauser.toggle();
|
||
});
|
||
|
||
webSocket.onopen = function() {
|
||
wsDiscTime = 0;
|
||
d3.select("#events-data").selectAll("tr.row").remove();
|
||
setInterval(function () {
|
||
webSocket.send(psk || settings.psk);
|
||
}, 1000);
|
||
};
|
||
|
||
webSocket.onmessage = function(evt) {
|
||
if (!evt) {
|
||
return;
|
||
}
|
||
|
||
// Parse the json to a js obj and clean the data
|
||
var datum = eval("(" + evt.data + ")");
|
||
|
||
if (datum.longitude == 0 && datum.latitude == 0) {
|
||
datum.longitude = -5;
|
||
datum.latitude = -50;
|
||
}
|
||
|
||
var startLoc = projection([datum.longitude, datum.latitude]);
|
||
var endLoc = projection([datum.longitude2, datum.latitude2]);
|
||
|
||
if (datum.error) {
|
||
showMessage("ERROR: " + datum.error.msg);
|
||
}
|
||
|
||
for (var prop in datum) {
|
||
if (settings.numberProps.indexOf(prop) !== -1) {
|
||
datum[prop] = Number(datum[prop]);
|
||
}
|
||
}
|
||
|
||
function cleanCountry(country) {
|
||
// Clean incoming country code
|
||
if (country === "USA") {
|
||
return "US";
|
||
}
|
||
return country
|
||
}
|
||
|
||
datum.country = cleanCountry(datum.country);
|
||
datum.country2 = cleanCountry(datum.country2);
|
||
|
||
datum.service = datum.dport in ports ? ports[datum.dport] : undefined;
|
||
datum.cx = startLoc[0];
|
||
datum.cy = startLoc[1];
|
||
datum.x = startLoc[0];
|
||
datum.y = startLoc[1];
|
||
datum.targetX = endLoc[0];
|
||
datum.targetY = endLoc[1];
|
||
datum.id = getID();
|
||
datum.datetime = (new Date()).toISOString()
|
||
.replace("T", " ")
|
||
.slice(0, -2);
|
||
|
||
pauser.push(datum);
|
||
};
|
||
|
||
webSocket.onclose = function() {
|
||
//try to reconnect in 5 seconds
|
||
var interval = 1000;
|
||
|
||
wsDiscTime += 1000;
|
||
|
||
if (wsDiscTime > settings.wsTimeout) {
|
||
showMessage("We are having difficulties in the WebSocket connectivity. We will continue trying...");
|
||
wsDiscTime = 0;
|
||
}
|
||
|
||
setTimeout(function() {
|
||
console.log("websocket closed, reconnecting in " + interval + "ms");
|
||
start(loc, psk);
|
||
}, interval);
|
||
};
|
||
|
||
return webSocket;
|
||
}
|
||
|
||
/*
|
||
* Load external data, and manage loading state
|
||
*/
|
||
|
||
queue()
|
||
.defer(d3.json, "data/readme-world.json")
|
||
.defer(d3.tsv, "data/port-names.tsv")
|
||
.defer(d3.csv, "data/country-codes.csv")
|
||
.await(function(error, world, rawPorts, countryCodes) {
|
||
// Update the countryModel
|
||
countryModel.set(countryCodes);
|
||
countryModel.push({
|
||
iso2: "O1",
|
||
country: "Mil/Gov"
|
||
});
|
||
|
||
// Temporary mapping to key the map
|
||
var mapCodes = {};
|
||
countryCodes.forEach(function(d) {
|
||
mapCodes[Number(d.isonum)] = d.iso2;
|
||
});
|
||
|
||
// Enter the countries
|
||
svg.append("g")
|
||
.attr("class", "world")
|
||
.selectAll("path")
|
||
.data(topojson.feature(world, world.objects.countries).features)
|
||
.enter().insert("path")
|
||
.attr("class", "country")
|
||
.attr("id", function(d) {
|
||
return mapCodes[d.id];
|
||
})
|
||
.attr("fill", settings.countryColor(0))
|
||
.attr("d", path);
|
||
|
||
ports = parsePorts(rawPorts);
|
||
|
||
loadingToggle();
|
||
var webSocket = start();
|
||
nodeModel.start();
|
||
painter.start();
|
||
rateTicker.start();
|
||
});
|
||
|
||
// Export the external API
|
||
window.IPViking = {
|
||
settings: settings
|
||
};
|
||
|
||
var unknownLoc = projection([-5, -50]);
|
||
|
||
d3.selectAll("#unknown-icon").style({
|
||
'left': unknownLoc[0] - 18,
|
||
'top': unknownLoc[1] - 18
|
||
});
|
||
|
||
setInterval(function() {
|
||
prune();
|
||
}, 30000);
|
||
})(window);
|