#! /usr/bin/python """ Copyright 2013, Big Switch Networks, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. circuitpusher utilizes floodlight rest APIs to create a bidirectional circuit, i.e., permanent flow entry, on all switches in route between two devices based on IP addresses with specified priority. Notes: 1. The circuit pusher currently only creates circuit with two IP end points 2. Prior to sending restAPI requests to the circuit pusher, the specified end points must already been known to the controller (i.e., already have sent packets on the network, easy way to assure this is to do a ping (to any target) from the two hosts. 3. The current supported command syntax format is: a) circuitpusher.py --controller={IP}:{rest port} --type ip --src {IP} --dst {IP} --add --name {circuit-name} adds a new circuit between src and dst devices Currently ip circuit is supported. ARP is automatically supported. Currently a simple circuit record storage is provided in a text file circuits.json in the working directory. The file is not protected and does not clean itself between controller restarts. The file is needed for correct operation and the user should make sure deleting the file when floodlight controller is restarted. b) circuitpusher.py --controller={IP}:{rest port} --delete --name {circuit-name} deletes a created circuit (as recorded in circuits.json) using the previously given name @author kcwang """ import os import sys import subprocess import json import argparse import io import time # parse circuit options. Currently supports add and delete actions. # Syntax: # circuitpusher --controller {IP:REST_PORT} --add --name {CIRCUIT_NAME} --type ip --src {IP} --dst {IP} # circuitpusher --controller {IP:REST_PORT} --delete --name {CIRCUIT_NAME} parser = argparse.ArgumentParser(description='Circuit Pusher') parser.add_argument('--controller', dest='controllerRestIp', action='store', default='localhost:8080', help='controller IP:RESTport, e.g., localhost:8080 or A.B.C.D:8080') parser.add_argument('--add', dest='action', action='store_const', const='add', default='add', help='action: add, delete') parser.add_argument('--delete', dest='action', action='store_const', const='delete', default='add', help='action: add, delete') parser.add_argument('--type', dest='type', action='store', default='ip', help='valid types: ip') parser.add_argument('--src', dest='srcAddress', action='store', default='0.0.0.0', help='source address: if type=ip, A.B.C.D') parser.add_argument('--dst', dest='dstAddress', action='store', default='0.0.0.0', help='destination address: if type=ip, A.B.C.D') parser.add_argument('--name', dest='circuitName', action='store', default='circuit-1', help='name for circuit, e.g., circuit-1') args = parser.parse_args() print args controllerRestIp = args.controllerRestIp # first check if a local file exists, which needs to be updated after add/delete if os.path.exists('./circuits.json'): circuitDb = open('./circuits.json','r') lines = circuitDb.readlines() circuitDb.close() else: lines={} if args.action=='add': circuitDb = open('./circuits.json','a') for line in lines: data = json.loads(line) if data['name']==(args.circuitName): print "Circuit %s exists already. Use new name to create." % args.circuitName sys.exit() else: circuitExists = False # retrieve source and destination device attachment points # using DeviceManager rest API command = "curl -s http://%s/wm/device/?ipv4=%s" % (args.controllerRestIp, args.srcAddress) result = os.popen(command).read() parsedResult = json.loads(result) print command+"\n" try: sourceSwitch = parsedResult[0]['attachmentPoint'][0]['switchDPID'] except IndexError: print "ERROR : the specified end point (%s) must already been known to the controller (i.e., already have sent packets on the network, easy way to assure this is to do a ping (to any target) from the two hosts." % (args.srcAddress) sys.exit() sourcePort = parsedResult[0]['attachmentPoint'][0]['port'] command = "curl -s http://%s/wm/device/?ipv4=%s" % (args.controllerRestIp, args.dstAddress) result = os.popen(command).read() parsedResult = json.loads(result) print command+"\n" try: destSwitch = parsedResult[0]['attachmentPoint'][0]['switchDPID'] except IndexError: print "ERROR : the specified end point (%s) must already been known to the controller (i.e., already have sent packets on the network, easy way to assure this is to do a ping (to any target) from the two hosts." % (args.dstAddress) sys.exit() destPort = parsedResult[0]['attachmentPoint'][0]['port'] print "Creating circuit:" print "from source device at switch %s port %s" % (sourceSwitch,sourcePort) print "to destination device at switch %s port %s"% (destSwitch,destPort) # retrieving route from source to destination # using Routing rest API command = "curl -s http://%s/wm/topology/route/%s/%s/%s/%s/json" % (controllerRestIp, sourceSwitch, sourcePort['shortPortNumber'], destSwitch, destPort['shortPortNumber']) result = os.popen(command).read() print result+"\n" print command+"\n" parsedResult = json.loads(result) for i in range(len(parsedResult)): if i % 2 == 0: ap1Dpid = parsedResult[i]['switch'] ap1Port = parsedResult[i]['port'] print ap1Dpid, ap1Port else: ap2Dpid = parsedResult[i]['switch'] ap2Port = parsedResult[i]['port'] print ap2Dpid, ap2Port # send one flow mod per pair of APs in route # using StaticFlowPusher rest API # IMPORTANT NOTE: current Floodlight StaticflowPusher # assumes all flow entries to have unique name across all switches # this will most possibly be relaxed later, but for now we # encode each flow entry's name with both switch dpid, user # specified name, and flow type (f: forward, r: reverse, farp/rarp: arp) command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"ipv4_src\":\"%s\", \"ipv4_dst\":\"%s\", \"eth_type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"in_port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowpusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".f", args.srcAddress, args.dstAddress, "0x800", ap1Port['shortPortNumber'], ap2Port['shortPortNumber'], controllerRestIp) result = os.popen(command).read() print command command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"arp_spa\":\"%s\", \"arp_tpa\":\"%s\", \"eth_type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"in_port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowpusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".farp", args.srcAddress, args.dstAddress, "0x806", ap1Port['shortPortNumber'], ap2Port['shortPortNumber'], controllerRestIp) result = os.popen(command).read() print command command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"ipv4_src\":\"%s\", \"ipv4_dst\":\"%s\", \"eth_type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"in_port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowpusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".r", args.dstAddress, args.srcAddress, "0x800", ap2Port['shortPortNumber'], ap1Port['shortPortNumber'], controllerRestIp) result = os.popen(command).read() print command command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"arp_spa\":\"%s\", \"arp_tpa\":\"%s\", \"eth_type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"in_port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowpusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".rarp",args.dstAddress, args.srcAddress, "0x806", ap2Port['shortPortNumber'], ap1Port['shortPortNumber'], controllerRestIp) result = os.popen(command).read() print command # store created circuit attributes in local ./circuits.json datetime = time.asctime() circuitParams = {'name':args.circuitName, 'Dpid':ap1Dpid, 'inPort':ap1Port['shortPortNumber'], 'outPort':ap2Port['shortPortNumber'], 'datetime':datetime} str = json.dumps(circuitParams) circuitDb.write(str+"\n") # confirm successful circuit creation # using controller rest API command="curl -s http://%s/wm/core/switch/all/flow/json| python -mjson.tool" % (controllerRestIp) result = os.popen(command).read() print command + "\n" + result elif args.action=='delete': circuitDb = open('./circuits.json','w') # removing previously created flow from switches # using StaticFlowPusher rest API # currently, circuitpusher records created circuits in local file ./circuits.db # with circuit name and list of switches circuitExists = False for line in lines: data = json.loads(line) if data['name']==(args.circuitName): circuitExists = True sw = data['Dpid'] print data, sw command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowpusher/json" % (sw+"."+args.circuitName+".f", sw, controllerRestIp) result = os.popen(command).read() print command, result command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowpusher/json" % (sw+"."+args.circuitName+".farp", sw, controllerRestIp) result = os.popen(command).read() print command, result command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowpusher/json" % (sw+"."+args.circuitName+".r", sw, controllerRestIp) result = os.popen(command).read() print command, result command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowpusher/json" % (sw+"."+args.circuitName+".rarp", sw, controllerRestIp) result = os.popen(command).read() print command, result else: circuitDb.write(line) circuitDb.close() if not circuitExists: print "specified circuit does not exist" sys.exit()