]> andersk Git - routeradvert-scan.git/blob - routeradvert-scan.py
Move to -c rcc-auto.
[routeradvert-scan.git] / routeradvert-scan.py
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
28 import BeautifulSoup
29 import errno
30 import math
31 import mechanize
32 import os
33 import re
34 import signal
35 import socket
36 import subprocess
37 import sys
38 import time
39 import urllib
40
41 # configuration
42 interface = 'eth1'
43 timeout = 120
44 zclass = 'rcc-auto'
45 zinstance = '6to4'
46 zsig = '%s on %s' % (sys.argv[0], socket.gethostname())
47 cert_file = os.path.join(os.path.dirname(__file__), 'sipbcert.pem')
48 # end configuration
49
50 mac_re = re.compile(r'^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$')
51
52 seen_macs = {}
53
54 def msg(m):
55     os.spawnlp(os.P_WAIT, 'zwrite', 'zwrite', '-q', '-d', '-c', zclass, '-i', zinstance, '-s', zsig, '-O', 'auto', '-m', m)
56
57 br = mechanize.Browser()
58 br.add_client_certificate("https://nic.mit.edu", cert_file, cert_file)
59
60 def 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
79 def show_mac(mac, (t, owner)):
80     return '%s (%s)' % (mac, 'unknown owner' if owner is None else owner)
81
82 def show_macs():
83     return ', '.join(show_mac(mac, v) for mac, v in seen_macs.iteritems())
84
85 def 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
100 signal.signal(signal.SIGALRM, lambda signum, frame: check_gone())
101
102 p = subprocess.Popen(['tcpdump', '-elnp', '-i', interface, 'icmp6 and (ip6[40] = 134)'], stdout=subprocess.PIPE)
103 while True:
104     check_gone()
105     while True:
106         try:
107             line = p.stdout.readline()
108         except IOError, (e, s):
109             if e == errno.EINTR:
110                 continue
111         break
112     signal.alarm(0)
113     if line == '':
114         break
115     words = line.split()
116     if len(words) >= 2 and mac_re.match(words[1]):
117         mac = words[1]
118         t = time.time() + timeout
119         if mac in seen_macs:
120             seen_macs[mac][0] = t
121         else:
122             seen_macs[mac] = [t, get_owner(mac)]
123             msg('New 6to4 router: %s\n%s\nCurrent 6to4 routers: %s' %
124                 (show_mac(mac, seen_macs[mac]),
125                  line.rstrip('\n'),
126                  show_macs()))
127     else:
128         print >>sys.stderr, 'Unrecognized line: ', line.rstrip('\n')
This page took 0.867919 seconds and 5 git commands to generate.