Commits (3)
import bisect, smtplib, socket, sqlite3, struct, subprocess, sys
from email.mime.text import MIMEText
from email.utils import formatdate
net = '2001:67c:1254:'
def binFromIp(ip):
ip = socket.inet_pton(socket.AF_INET6, ip)
ip1, ip2 = struct.unpack('>QQ', ip)
return bin(ip1)[2:].rjust(64, '0') + bin(ip2)[2:].rjust(64, '0')
def ipFromBin(ip):
ip += '0' * (128 - len(ip))
return socket.inet_ntop(socket.AF_INET6,
struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2)))
def ip6route(*args):
r = {}
for route in subprocess.check_output(('ip', '-6', '-o', 'r') + args
# 2001:67c:1254:e:e::/80 via fe80::64ec:caff:fe4f:4a5b dev re6stnet7 proto babel src 2001:67c:1254:c0::1 metric 1024 pref medium
# unreachable 2001:67c:1254:c0::/64 dev lo proto kernel metric 256 error -101 pref medium
# unreachable 2001:67c:1254::/48 dev lo metric 1024 error -113 pref medium
route = route.split()
unreachable = route[0] == 'unreachable'
dst = route[unreachable]
if dst.startswith(net):
dst, n = dst.split('/')
except ValueError:
n = 128
# None (unreachable)
# fe80::64ec:caff:fe4f:4a5b, re6stnet7
r[binFromIp(dst)[:int(n)]] = None \
if unreachable else (route[2], route[4])
return sorted(r.iteritems()) # (dst, (via, dev))
def lookup(r, dst):
i = bisect.bisect(r, (dst,))
if i == len(r) or dst < r[i][0]:
i -= 1
if dst.startswith(r[i][0]):
return r[i][1]
def via_str(via):
return 'unreachable' if via is None else '%s (%s)' % via
class Invalid(dict):
def __init__(self):
before = ip6route()
cache = ip6route('l', 'cached')
after = ip6route()
for dst, via in cache:
expected = lookup(before, dst)
after_ = lookup(after, dst)
if expected != via != after_:
self[ipFromBin(dst)] = expected, via, after_
def __str__(self):
return "\n".join(
"%s: expected %s, got %s (after: %s)" % (ip, via_str(expected), via_str(via), via_str(after))
for ip, (expected, via, after) in sorted(self.iteritems()))
if __name__ == "__main__":
if len(sys.argv) > 1:
from time import time, sleep
db, smtp, to = sys.argv[1:]
db = sqlite3.connect(db)
db.execute("CREATE TABLE IF NOT EXISTS invalid (ip, start, end)")
host = socket.gethostname()
def sendmail(subject, text):
text = str(text)
msg = MIMEText(text)
msg['Subject'] = subject
msg['From'] = host
msg['Date'] = formatdate(now)
print msg['Date'], subject
print text
s = smtplib.SMTP(smtp)
s.sendmail(None, to, msg.as_string())
except socket.error:
print 'W: sendmail failed (ignore)'
t = 0
while 1:
now = time()
t += 60
if now < t:
sleep(t - now)
now = time()
t = now
invalid = Invalid()
recovered = set() # of ip now having valid routes in cachec
with db:
for ip, in db.execute("SELECT ip FROM invalid WHERE end IS NULL"):
del invalid[ip]
except KeyError:
db.execute("UPDATE invalid SET end=? WHERE ip=?", (int(now), ip))
if recovered:
sendmail("recovered: %s invalid routes in cache" % len(recovered),
if not invalid:
db.executemany("INSERT INTO invalid VALUES (?, ?, NULL)", (
(ip, int(now)) for ip in invalid))
sendmail("%s new invalid routes in cache" % len(invalid), invalid)
invalid = Invalid()
if invalid: