]> andersk Git - routeradvert-scan.git/blame_incremental - routeradvert-scan.py
Look up owners of offending MAC addresses.
[routeradvert-scan.git] / routeradvert-scan.py
... / ...
CommitLineData
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# routeradvert-scan.py: Scan for rogue IPv6 router advertisements.
5#
6# Copyright © 2010 Anders Kaseorg <andersk@mit.edu>
7#
8# Permission is hereby granted, free of charge, to any person
9# obtaining a copy of this software and associated documentation files
10# (the “Software”), to deal in the Software without restriction,
11# including without limitation the rights to use, copy, modify, merge,
12# publish, distribute, sublicense, and/or sell copies of the Software,
13# and to permit persons to whom the Software is furnished to do so,
14# subject to the following conditions:
15#
16# The above copyright notice and this permission notice shall be
17# included in all copies or substantial portions of the Software.
18#
19# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
20# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
23# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
24# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26# SOFTWARE.
27
28import BeautifulSoup
29import errno
30import math
31import mechanize
32import os
33import re
34import signal
35import socket
36import subprocess
37import sys
38import time
39import urllib
40
41# configuration
42interface = 'eth1'
43timeout = 30
44zclass = 'andersk-auto'
45zinstance = '6to4'
46zsig = '%s on %s' % (sys.argv[0], socket.gethostname())
47cert_file = '/home/anders/Private/andersk.pem'
48# end configuration
49
50mac_re = re.compile(r'^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$')
51
52seen_macs = {}
53
54def msg(m):
55 os.spawnlp(os.P_WAIT, 'zwrite', 'zwrite', '-q', '-d', '-c', zclass, '-i', zinstance, '-s', zsig, '-m', m)
56
57br = mechanize.Browser()
58br.add_client_certificate("https://nic.mit.edu", cert_file, cert_file)
59
60def get_owner(mac):
61 for lookup in ('host', 'less'):
62 try:
63 data = urllib.urlencode({
64 'action': 'Lookup Host',
65 'lookup': lookup,
66 'mac': mac
67 })
68 br.open('https://nic.mit.edu/bin/dynareg',
69 data)
70 soup = BeautifulSoup.BeautifulSoup(br.response().read())
71 for tag in soup.findAll('td'):
72 if tag.string == 'owner:':
73 return tag.nextSibling.string or \
74 tag.nextSibling.contents[0]['value']
75 except:
76 pass
77 return None
78
79def show_mac(mac, (t, owner)):
80 return '%s (%s)' % (mac, 'unknown owner' if owner is None else owner)
81
82def show_macs():
83 return ', '.join(show_mac(mac, v) for mac, v in seen_macs.iteritems())
84
85def check_gone():
86 now = time.time()
87 next = None
88 for (mac, (t, owner)) in seen_macs.items():
89 if t < now:
90 del seen_macs[mac]
91 msg('Gone 6to4 router: %s\nCurrent 6to4 routers: %s' %
92 (show_mac(mac, (t, owner)), show_macs()))
93 elif next is None or next > t:
94 next = t
95 if next is None:
96 signal.alarm(0)
97 else:
98 signal.alarm(int(math.ceil(next - now)))
99
100msg('Starting %s' % sys.argv[0])
101
102signal.signal(signal.SIGALRM, lambda signum, frame: check_gone())
103
104p = subprocess.Popen(['tcpdump', '-elnp', '-i', interface, 'icmp6 and (ip6[40] = 134)'], stdout=subprocess.PIPE)
105while True:
106 check_gone()
107 while True:
108 try:
109 line = p.stdout.readline()
110 except IOError, (e, s):
111 if e == errno.EINTR:
112 continue
113 break
114 signal.alarm(0)
115 if line == '':
116 break
117 words = line.split()
118 if len(words) >= 2 and mac_re.match(words[1]):
119 mac = words[1]
120 t = time.time() + timeout
121 if mac in seen_macs:
122 seen_macs[mac][0] = t
123 else:
124 seen_macs[mac] = [t, get_owner(mac)]
125 msg('New 6to4 router: %s\n%s\nCurrent 6to4 routers: %s' %
126 (show_mac(mac, seen_macs[mac]),
127 line.rstrip('\n'),
128 show_macs()))
129 else:
130 print >>sys.stderr, 'Unrecognized line: ', line.rstrip('\n')
This page took 0.028614 seconds and 5 git commands to generate.