A simple internet black hole
This is a very simple WebGL-powered “network telescope” (also known as Internet motion sensor or black hole); it works by listening on a subnet of unused IP address space and recording all the traffic it gets.
Now an unused IP address is pretty much like an empty house. You can knock all you want, but no one will answer. What we now want to do is set up an empty house, put a camera at the door and record any unwanted visitors. So whenever someone comes knocking, we’ll have a pic.
So we wait patiently; nothing happens for a long time; then maybe a traveling salesman knocks. In a couple of weeks, a burglar tries to break in, and so on.
We want to speed this up, so we set up an entire neighborhood of empty houses; now something happens almost every day.
In this analogy, whole neighborhood of empty houses is our subnet. By having more than 1 IP we increase the odds of catching something.
Because we expect no traffic on an unused subnet, we can assume all traffic is either from malicious bots, port scan attempts or even the backscatter traffic (which happens whenever somebody spoofs an IP address from that subnet, performs any kind of request, and the target server tries to reply to us).
You can get results if you log all the traffic on your home IP address, however having a whole subnet of public IPs is better.
For this to work, all the traffic destined to our dark subnet had to be routed to a single IP address. That IP address was assigned to a modest Linux box, where the Ruby script below was doing the following:
- inspecting all the incoming traffic using pcap
- using MaxMind’s GeoLiteCity database to resolve each IP to longitude and latitude
- and finally saving timestamp, IP and coordinates to a MySQL database.
#!/usr/bin/ruby require 'pcaplet' require 'rubygems' require 'active_record' require 'geoip_city' class Req < ActiveRecord::Base set_table_name "requests" end Req.establish_connection(:adapter => "mysql", :database => "ip-data", :host => "localhost", :username => "root", :password => "") g = GeoIPCity::Database.new('GeoLiteCity.dat') dump = Pcaplet.new #('-s 1500 -i eth1') WORK_filter = Pcap::Filter.new('not host x.x.x.x', dump.capture) SELF_filter = Pcap::Filter.new('not host y.y.y.y', dump.capture) LAN_filter = Pcap::Filter.new('not src net 10.0.0.0/8', dump.capture) dump.add_filter(WORK_filter & SELF_filter & LAN_filter) dump.each_packet do |pkt| begin if pkt puts ip = pkt.src.to_s loc = g.look_up(ip.to_s) n = Req.new n.ip = ip.to_s n.lon = loc[:longitude].to_s n.lat = loc[:latitude].to_s n.save! end rescue Exception => e #puts "No IP" #p e end end
Another script, running periodically as a Cron job, did some simple aggregation:
- select last day’s worth of coordinates and group count them by IP
- save the results into a JSON file on disk, suitable for loading into the WebGL app.
WebGL App was a slightly modified Google WebGL Globe (slightly modified in the sense that I tweaked the color scheme and locked the globe to autorotate). For the lack of a better and more involved solution, I used a very simple refreshing mechanism: Meta refresh tag, reloading the globe every 15 minutes.
This is the final result (static screengrab):
And half of the globe really is completely empty and dark. Behold, the dark side of the Earth. (The only light is somebody knocking from Hawaii).