Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
df-scripts/rtbh-watch.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
184 lines (147 sloc)
6.71 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import argparse | |
import csv | |
import ConfigParser | |
import datetime | |
import json | |
import re | |
import smtplib | |
import subprocess | |
import sys | |
from email.mime.text import MIMEText | |
from filecmp import cmp as fcmp | |
from shutil import copy2 | |
from tabulate import tabulate | |
def matching_items(data, community): | |
result = {} | |
for (key, value) in data.items(): | |
for subitem in value: | |
age = subitem["age"] | |
for attr in subitem["attrs"]: | |
for comm in community: | |
if attr["type"] == 2 and attr["as_paths"]: | |
origin_asn = attr["as_paths"][0]["asns"][-1] | |
if attr["type"] == 8 and comm in attr["communities"]: | |
result[key] = [age, origin_asn] | |
break | |
return result | |
def update_routes( comms, wd, ts ): | |
"""Query gobgp to obtain blackhole routes""" | |
myfile = wd+ts+'_routes.csv' | |
if fcmp(wd+'latest_routes.csv',wd+'current_routes.csv') is False: | |
copy2(wd+'latest_routes.csv',wd+'current_routes.csv') | |
with open(myfile, 'w') as f: | |
for family in ['ipv4', 'ipv6']: | |
route = subprocess.Popen(['gobgp','global','rib','-a',family,'-j'], stdout=subprocess.PIPE) | |
route = json.loads(route.stdout.read()) | |
cidrs = matching_items(route, comms) | |
table = [] | |
for (key, value) in cidrs.items(): | |
table.append([key,datetime.datetime.fromtimestamp(value[0]).strftime('%Y-%m-%d'),'AS{}'.format(value[1])]) | |
wr = csv.writer(f) | |
for row in table: | |
wr.writerow(row) | |
copy2(myfile,wd+'latest_routes.csv') | |
def diff_routes(wd): | |
"""diff the current routes with the last set of routes | |
and print tables of advertised and withdrawn routes""" | |
with open(wd+'latest_routes.csv','rb') as f: | |
reader = csv.reader(f) | |
latest = list(reader) | |
with open(wd+'current_routes.csv','rb') as f: | |
reader = csv.reader(f) | |
current = list(reader) | |
latest = sort_table(latest) | |
current = sort_table(current) | |
advertised = [x for x in latest if x not in current] | |
withdrawn = [x for x in current if x not in latest] | |
return advertised,withdrawn | |
def all_routes(wd): | |
"""print all current blackhole routes""" | |
with open(wd+'latest_routes.csv','rb') as f: | |
reader = csv.reader(f) | |
current = list(reader) | |
return sort_table(current) | |
def sort_table(data): | |
"""sort list and remove entries with no first value""" | |
table = [] | |
for row in data: | |
table.append([row[0], row[1], row[2]]) | |
table.sort(lambda x,y: cmp(x[0],y[0])) | |
table.sort(lambda x,y: cmp(x[2],y[2])) | |
return table | |
def commplain(comm): | |
"""Convert as:comm to plain decimal""" | |
left,right = re.split('\.|:', comm) | |
ret = int(left) * 65536 + int(right) | |
return ret | |
def mail(wd,mailfrom,rcptto,smtp_server,diff=False,all=False): | |
"""email blackhole routes to recipient""" | |
text = '' | |
if diff is True: | |
mail_adv, mail_with = diff_routes(wd) | |
adv = [] | |
for row in mail_adv: | |
adv.append([row[0], row[1], row[2], 'http://bgp.he.net/{0}'.format(row[2])]) | |
withdrawn = [] | |
for row in mail_with: | |
withdrawn.append([row[0], row[1], row[2], 'http://bgp.he.net/{0}'.format(row[2])]) | |
if adv != [] or mail_with != []: | |
text += '\nNewly advertised blackhole routes:\n\n{0}\n\nWithdrawn blackhole routes:\n\n{1}'.format(tabulate(adv,['IP prefix','Date Advertised','ASN','Link to ASN Info']), tabulate(withdrawn,['IP prefix','Date Advertised','ASN','Link to ASN Info'])) | |
if all is True: | |
mail_all = all_routes(wd) | |
allroutes = [] | |
for row in mail_all: | |
allroutes.append([row[0], row[1], row[2], 'http://bgp.he.net/{0}'.format(row[2])]) | |
text += '\n\nAll current blackhole routes:\n\n{0}'.format(tabulate(allroutes,['IP Prefix','Date Advertised','ASN','Link to ASN Info'])) | |
if text == '': | |
print 'No data to email. Exiting...' | |
return | |
msg = MIMEText(text) | |
msg['Subject'] = 'Blackhole routes advertised on the Internet2 network' | |
msg['From'] = mailfrom | |
msg['To'] = rcptto | |
s = smtplib.SMTP(smtp_server) | |
s.sendmail(mailfrom, [rcptto], msg.as_string()) | |
s.quit() | |
def main(args): | |
"""Called when running rtbh-watch.py as a script""" | |
parser = argparse.ArgumentParser(description='Query the bird daemon for blackhole routes. Print output to screen or email results.', epilog='Example: python rtbh-watch.py -e -a') | |
group = parser.add_mutually_exclusive_group(required=True) | |
group.add_argument('-s','--screen', help='Print results to screen', action='store_const', const=True) | |
group.add_argument('-e','--email', help='Email results to email address', action='store_const', const=True) | |
group.add_argument('-u','--update', help='Update the local files from bird', action='store_const', const=True) | |
parser.add_argument('-c','--comm', help='communities to search for. Defaults to (11537,911),(11164,53666),(65535,666)', default=["11537:911","11164:53666","65535:666"]) | |
parser.add_argument('-f','--file', help='config file', default='rtbh-watch.ini') | |
group2 = parser.add_argument_group(description='You need to pass one or both of the following flags') | |
group2.add_argument('-d','--diff', help='Print which routes have changed', action='store_const', const=True) | |
group2.add_argument('-a','--all-routes', help='Print all blackhole routes', action='store_const', const=True) | |
args = parser.parse_args() | |
config = ConfigParser.ConfigParser() | |
try: | |
config.readfp(open(args.file)) | |
except: | |
print('Configuration file {0} must exist'.format(args.file)) | |
return | |
wd = config.get('General','working_dir') | |
rcptto = config.get('Email','recipient') | |
mailfrom = config.get('Email','from') | |
smtp_server = config.get('Email','smtp_server') | |
ts = '{:%Y-%m-%d_%H-%M-%S}'.format(datetime.datetime.now()) | |
comm = [commplain(c) for c in args.comm] | |
if args.update is True: | |
update_routes(comm, wd, ts) | |
if args.screen is True: | |
if args.diff is True: | |
advertised,withdrawn = diff_routes(wd) | |
print '\nNewly advertised blackhole routes:\n' | |
print tabulate(advertised,['IP Prefix','Date Advertised','ASN']) | |
print '\nWithdrawn blackhole routes:\n' | |
print tabulate(withdrawn,['IP Prefix','Date Advertised','ASN']) | |
if args.all_routes is True: | |
print '\nAll current blackhole routes:\n' | |
print tabulate(all_routes(wd),['IP Prefix','Date Advertised','ASN']) | |
if args.email is True: | |
mail(wd,mailfrom,rcptto,smtp_server,args.diff,args.all_routes) | |
if __name__ == '__main__': | |
main(sys.argv) |