#!/usr/bin/python

"""The purpose of this script is to take the RIPE NCC's routing database
and transform a comma separated list of AS-SETs (-as-sets) into IRCnet's
I-Line structure. It will look at all ipv4 and ipv6 route objects in a
certain AS number, and it finds the AS numbers by expanding the AS-SET
members. It uses the class speficied on the commandline (-class). You can
also use the flag -asns to specify a list of AS numbers. These two flags
can be combined or used in isolation (as is shown by the examples below.
Example usage:
$ asset_ilines.py -as-sets AS-IP-MAN-PEERING-CIXP,AS-IP-MAN-PEERING-TIX \
	-class 210 -output ilines.as-set.conf
$ asset_ilines.py -asns 8404,20932 -class 220 -output ilines.asn.conf
"""

import getopt
import sys
import types
import time
import os
import getpass
import socket
import re

def usage():
  print """Usage:
  -h (-help): Help, this message
  -a (-as-sets): A (comma separeted list of) AS set(s) to lookup
  -l (-asns): A (comma separated list of) AS numbers to lookup
  -o (-output): The output file to write
  -y (-class): The Y-line class to put the I line in (default: 200)"""
  pass

def asn_to_route(_asn, _whois_server = "whois.ripe.net",
		 _prerequest_sleep_time = 1):
	list = []
	time.sleep(_prerequest_sleep_time)

	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	try:
		s.connect((_whois_server, 43))
	except:
		return list
	ifile = s.makefile('w')
	ifile.write('-i origin AS'+_asn+'\r\n')
	ifile.flush()
	print("Fetching route/route6 for AS%s from %s" % (_asn, _whois_server))
	while True:
		line = ifile.readline()
		if not line:
			break
		matches = re.search('^route6?:[\s]*([0-9a-fA-F.:/]+)', line)
		if not matches:
			continue
		list.append(matches.group(1))
	ifile.close()
	print("%d route/route6 object(s) found for AS%s" % (len(list), _asn))
	return list

def asset_to_asn(_asset, _whois_server = "whois.ripe.net",
		 _prerequest_sleep_time = 1):
	list = []
	time.sleep(_prerequest_sleep_time)

	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	try:
		s.connect((_whois_server, 43))
	except:
		return list
	ifile = s.makefile('w')
	ifile.write(_asset+'\r\n')
	ifile.flush()
	print("Fetching %s from %s" % (_asset, _whois_server))
	
	while True:
		line = ifile.readline()
		if not line:
			break
		matches = re.search('^members:.*AS([0-9]+)', line)
		if not matches:
			continue
		list.append(matches.group(1))
	ifile.close()
	print("%d AS numbers found for %s" % (len(list), _asset))
	return list
	

def main():
	try:
		opts, args = getopt.getopt(sys.argv[1:], "ha:l:o:y:",
			["help", "assets=", "asns=", "output=", "class="])
	except getopt.GetoptError, err:
		print str(err) # will print something like "option -foo not recognized"
		usage()
		sys.exit(2)

	_assets = ["AS-IP-MAN-PEERING-TIX", "AS-IP-MAN-PEERING-CIXP",
		   "AS-IP-MAN-PEERING-SWISSIX"]
	_class = 200
	_output = None
	_asns = [20932]

	for o,a in opts:
		if o == "-h" or o == "-help":
			usage()
			sys.exit(2)
		elif o == "-a" or o == "-as-sets":
			if a == '':
				_assets = []
			else:
				_assets = a.split(',')
		elif o == "-l" or o == "-asns":
			if a == '':
				_asns = []
			else:
				_asns = a.split(',')
		elif o == "-o" or o == "-output":
			_output = a
		elif o == "-y" or o == "-class":
			_class = int(a)
		else:
			assert False, "unhandled option(s)"

	# Check input args a bit
	if _output == None:
		usage()
		assert False, "-o (-output) Must set output file"
	if type(_class) != types.IntType:
		usage()
		assert False, "-y (-class) must be an integer"
	if len(_assets) < 1 and len(_asns) < 1:
		usage()
		assert False,("-a (-as-sets) or -l (-asns) must be a comma "
			      "separated list")
	
	try:
		ofile = open(_output, "w")
	except:
		assert False, "Coult not open output file"

	_data = {'as-set': {}, 'asn': {}}
	for _asset in _assets:
		asn_list = asset_to_asn (_asset)
		_data['as-set'][_asset] = asn_list
		for _asn in asn_list:
			if _asn in _data['asn']:
				continue
			_data['asn'][_asn] = []

	_data['as-set']['LOCAL'] = _asns
	for _asn in _data['as-set']['LOCAL']:
		if _asn in _data['asn']:
			continue
		_data['asn'][_asn] = []

	all_route_list = []
	for _asn in _data['asn'].keys():
		route_list = asn_to_route (_asn)
		for _route in route_list:
			if _route in all_route_list:
				continue
			_data['asn'][_asn].append(_route)
		all_route_list = list(set(all_route_list + route_list))

	# print(_data)
	print("Objects found: %d route/route6, %d ASn, %d as-set" %
	      (len(all_route_list), len(_data['asn']), len(_assets)))

	ofile.write("# File generated on %s by %s@%s\n" %
                    (time.asctime(time.localtime(time.time())),
		     getpass.getuser(), socket.gethostname()))
#	TODO(pim): Make these lines wrap at ~80char because the ircd config
#		parser cannot take long lines :(
#	ofile.write("# Commandline: %s\n" % ' '.join(sys.argv))
#	ofile.write("# assets=%s asns=%s output=%s class=%s\n" %
#		    (','.join(_assets), ','.join(_asns), _output, _class))

	ofile.write("# Objects found: %d route/route6, %d ASn, %d as-set\n\n" %
		    (len(all_route_list), len(_data['asn']), len(_assets)))

#	for _asset in _data['as-set']:
#		ofile.write("# %s: %s\n" %
#			    (_asset, ', '.join(_data['as-set'][_asset])))
	ofile.write("#\n\n")

	output_linecount = 0
	for _asn in _data['asn']:
		ofile.write("# AS%s (%d lines)\n" %
		   	    (_asn, len(_data['asn'][_asn])))
		for _route in _data['asn'][_asn]:
			ofile.write("I%%*@%s%%%%%%%%%d%%%%\n" %
                                    (_route, _class))
			output_linecount = output_linecount + 1
		ofile.write("\n")
	ofile.write("# Output %d I-lines\n" % (output_linecount))

	ofile.close()

if __name__ == "__main__":
	main()