]>
Commit | Line | Data |
---|---|---|
cde4019a AK |
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 | ||
cb363a3e | 28 | import BeautifulSoup |
cde4019a AK |
29 | import errno |
30 | import math | |
cb363a3e | 31 | import mechanize |
cde4019a AK |
32 | import os |
33 | import re | |
34 | import signal | |
35 | import socket | |
36 | import subprocess | |
37 | import sys | |
38 | import time | |
cb363a3e | 39 | import urllib |
cde4019a AK |
40 | |
41 | # configuration | |
42 | interface = 'eth1' | |
599d33b8 AK |
43 | timeout = 120 |
44 | zclass = 'rcc-auto' | |
cde4019a AK |
45 | zinstance = '6to4' |
46 | zsig = '%s on %s' % (sys.argv[0], socket.gethostname()) | |
599d33b8 | 47 | cert_file = os.path.join(os.path.dirname(__file__), 'sipbcert.pem') |
cde4019a AK |
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): | |
599d33b8 | 55 | os.spawnlp(os.P_WAIT, 'zwrite', 'zwrite', '-q', '-d', '-c', zclass, '-i', zinstance, '-s', zsig, '-O', 'auto', '-m', m) |
cde4019a | 56 | |
cb363a3e AK |
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 | ||
cde4019a AK |
85 | def check_gone(): |
86 | now = time.time() | |
87 | next = None | |
cb363a3e | 88 | for (mac, (t, owner)) in seen_macs.items(): |
cde4019a AK |
89 | if t < now: |
90 | del seen_macs[mac] | |
cb363a3e AK |
91 | msg('Gone 6to4 router: %s\nCurrent 6to4 routers: %s' % |
92 | (show_mac(mac, (t, owner)), show_macs())) | |
cde4019a AK |
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: | |
cb363a3e | 120 | seen_macs[mac][0] = t |
cde4019a | 121 | else: |
cb363a3e AK |
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())) | |
cde4019a AK |
127 | else: |
128 | print >>sys.stderr, 'Unrecognized line: ', line.rstrip('\n') |