#!/usr/bin/python """ Script to add LISP mappings to ODL. Currently it only supports the RPC interface. Support for RESTCONF is planned in a future release. Use `./mapping_blaster.py --help` to see options Code inspired from `flow_config_blaster.py` by Jan Medved """ import argparse import copy import json import netaddr import requests __author__ = "Lori Jakab" __copyright__ = "Copyright (c) 2014, Cisco Systems, Inc." __credits__ = ["Jan Medved"] __license__ = "New-style BSD" __email__ = "lojakab@cisco.com" __version__ = "0.0.3" class MappingRPCBlaster(object): putheaders = {"Content-type": "application/json"} getheaders = {"Accept": "application/json"} RPC_URL_LI = "restconf/operations/lfm-mapping-database:" RPC_URL_BE = "restconf/operations/odl-mappingservice:" TIMEOUT = 10 # Template for adding mappings add_mapping_template = { "input": { "mapping-record": { "recordTtl": 60, "action": "NoAction", "authoritative": True, "eid": { "address-type": "ietf-lisp-address-types:ipv4-prefix-afi", "ipv4-prefix": "10.0.0.0/32", }, "LocatorRecord": [ { "locator-id": "ipv4:172.16.0.0", "priority": 1, "weight": 1, "multicastPriority": 255, "multicastWeight": 0, "localLocator": True, "rlocProbed": False, "routed": True, "rloc": { "address-type": "ietf-lisp-address-types:ipv4-afi", "ipv4": "172.16.0.0", }, } ], } } } # Template for getting mappings get_mapping_template = {"input": {"eid": {"ipv4-prefix": "10.0.0.0"}}} def __init__(self, host, port, start_eid, mask, start_rloc, nmappings, v): """ Args: :param host: The host running ODL where we want to send the RPCs :param port: The RESTCONF port on the ODL host :param start_eid: The starting EID for adding mappings as an IPv4 literal :param mask: The network mask for the EID prefixes to be added :param start_rloc: The starting RLOC for the locators in the mappings as an IPv4 literal :param nmappings: The number of mappings to be generated :param v: Version of the ODL instance (since the RPC URL changed from Lithium to Beryllium """ self.session = requests.Session() self.host = host self.port = port self.start_eid = netaddr.IPAddress(start_eid) self.mask = mask self.start_rloc = netaddr.IPAddress(start_rloc) self.nmappings = nmappings if v == "Li" or v == "li": print("Using the Lithium RPC URL") rpc_url = self.RPC_URL_LI else: print("Using the Beryllium and later RPC URL") rpc_url = self.RPC_URL_BE self.post_url_template = "http://" + self.host + ":" + self.port + "/" + rpc_url def mapping_from_tpl(self, eid, mask, rloc): """Create an add-mapping RPC input dictionary from the mapping template Args: :param eid: Replace the default EID in the template with this one :param mask: Replace the default mask in the template with this one :param rloc: Replace the default RLOC in the template with this one Returns: :return dict: mapping - template modified with the arguments """ mapping = copy.deepcopy(self.add_mapping_template["input"]["mapping-record"]) mapping["eid"]["ipv4-prefix"] = str(netaddr.IPAddress(eid)) + "/" + mask mapping["LocatorRecord"][0]["locator-id"] = "ipv4:" + str( netaddr.IPAddress(rloc) ) mapping["LocatorRecord"][0]["rloc"]["ipv4"] = str(netaddr.IPAddress(rloc)) return mapping def send_rpc(self, session, method, body): """Send an HTTP POST to the RPC URL and return the status code Args: :param session: The HTTP session handle :param method: "add" or "del"ete mapping :param body: the JSON body to be sent Returns: :return int: status_code - HTTP status code """ rpc_url = self.post_url_template + method r = session.post( rpc_url, data=body, headers=self.putheaders, stream=False, auth=("admin", "admin"), timeout=self.TIMEOUT, ) return r.status_code def add_n_mappings(self): """Add self.nmappings mappings to ODL""" rpc = dict(self.add_mapping_template) increment = pow(2, 32 - int(self.mask)) for i in range(self.nmappings): rpc["input"]["mapping-record"] = self.mapping_from_tpl( self.start_eid + i * increment, self.mask, self.start_rloc + i ) rpc_json = json.dumps(rpc) self.send_rpc(self.session, "add-mapping", rpc_json) self.session.close() def get_n_mappings(self): """Retrieve self.nmappings mappings from ODL""" rpc = dict(self.get_mapping_template) increment = pow(2, 32 - int(self.mask)) for i in range(self.nmappings): eid = self.start_eid + i * increment rpc["input"]["eid"]["ipv4-prefix"] = ( str(netaddr.IPAddress(eid)) + "/" + self.mask ) rpc_json = json.dumps(rpc) self.send_rpc(self.session, "get-mapping", rpc_json) self.session.close() if __name__ == "__main__": parser = argparse.ArgumentParser( description="Add simple IPv4 \ prefix-to-IPv4 locator LISP mappings to OpenDaylight" ) parser.add_argument( "--mode", default="add", help='Operating mode, can be "add" or "get" \ (default is "add")', ) parser.add_argument( "--host", default="127.0.0.1", help="Host where ODL controller is running (default \ is 127.0.0.1)", ) parser.add_argument( "--port", default="8181", help="Port on which ODL's RESTCONF is listening \ (default is 8181)", ) parser.add_argument( "--start-eid", default="10.0.0.0", help="Start incrementing EID from this address \ (default is 10.0.0.0)", ) parser.add_argument( "--mask", default="32", help="Network mask for the IPv4 EID prefixes \ (default is 32)", ) parser.add_argument( "--start-rloc", default="172.16.0.0", help='Start incrementing RLOC from this address \ (default is 172.16.0.0, ignored for "get")', ) parser.add_argument( "--mappings", type=int, default=1, help="Number of mappings to add/get (default 1)", ) parser.add_argument( "--odl-version", default="Be", help='OpenDaylight version, can be "Li" or "Be" \ (default is "Be")', ) in_args = parser.parse_args() mapping_rpc_blaster = MappingRPCBlaster( in_args.host, in_args.port, in_args.start_eid, in_args.mask, in_args.start_rloc, in_args.mappings, in_args.odl_version, ) if in_args.mode == "add": mapping_rpc_blaster.add_n_mappings() elif in_args.mode == "get": mapping_rpc_blaster.get_n_mappings() else: print("Unsupported mode:", in_args.mode)