diff --git a/mininet.sh b/mininet.sh old mode 100755 new mode 100644 index 856af448..f0d0fe02 --- a/mininet.sh +++ b/mininet.sh @@ -1,4 +1,105 @@ #!/bin/bash -sudo ~/pyretic/mininet/mn -c -sudo ~/pyretic/mininet/mn --custom $HOME/pyretic/mininet/extra-topos.py --controller remote --mac $@ +init() +{ + getPYRETICDIR + PYRETICMNDIR="${PYRETICDIR}/mininet" + PYRETICMN="${PYRETICMNDIR}/mn" + EXTRATOPS="${PYRETICMNDIR}/extra-topos.py" + PYRETIC_PY="pyretic.py" + PYRETICPY="${PYRETICDIR}/${PYRETIC_PY}" + CONTROLLER="--controller remote" + listeningPort="6633" + + if [ -f "${PYRETICPY}" -a "${PYRETICPY}" = "`which ${PYRETIC_PY}`" ] ; then + PYRETICISHERE=1 + fi +} + +getPYRETICDIR() +{ + HERE=${PWD} + PYRETICDIR=`dirname $0` + cd ${PYRETICDIR} + PYRETICDIR=${PWD} + cd ${HERE} +} + +getMN() +{ + if [ -x "${PYRETICMN}" ] ; then + MN="${PYRETICMN}" + else + MN="mn" + fi +} + +getCUSTOMTOPOS() +{ + CUSTOMTOPOS="" + + if [ -f "${EXTRATOPS}" ] ; then + CUSTOMTOPOS="--custom ${EXTRATOPS}" + fi +} + +validateNB() +{ + echo "NB: The $* you enter is not validated, please ensure that it is valid!" +} + +getLISTENINGPORT() +{ + validateNB "port" + echo -e "Please enter listening port[${listeningPort}]: \c" + read port + + if [ ! -z "${port}" ] ; then + listeningPort="${port}" + fi + + LISTENINGPORT="port=${listeningPort}" +} + +getLISTENINGIP() +{ + if [ ! "${PYRETICISHERE}" = "1" ] ; then + validateNB "IP" + echo -e "Please enter IP address of listener: \c" + read ip + + if [ ! -z "${ip}" ] ; then + LISTENINGIP="ip=${ip}" + fi + fi +} + +getCONTROLLER() +{ + getLISTENINGPORT + getLISTENINGIP + + CONTROLLER="${CONTROLLER},${LISTENINGPORT}" + + if [ ! -z "${LISTENINGIP}" ] ; then + CONTROLLER="${CONTROLLER},${LISTENINGIP}" + fi +} + +doIt() +{ + getCUSTOMTOPOS + getCONTROLLER + + OPTIONS="--mac ${CUSTOMTOPOS} ${CONTROLLER}" + getMN + + sudo ${MN} -c + + echo "sudo ${MN} ${OPTIONS} $@" + sudo ${MN} ${OPTIONS} $@ +} + +init $* +doIt $* + diff --git a/mininet/miniedit-2.0.0.3.1.py b/mininet/miniedit-2.0.0.3.1.py new file mode 100644 index 00000000..9d7a13e5 --- /dev/null +++ b/mininet/miniedit-2.0.0.3.1.py @@ -0,0 +1,1219 @@ +#!/usr/bin/python + +""" +MiniEdit: a simple network editor for Mininet + +This is a simple demonstration of how one might build a +GUI application using Mininet as the network model. + +Development version - not entirely functional! + +Bob Lantz, April 2010 + +Version 2.0.0.1 +Updates by Gregory Gee +- Added Save and Load of topologies +- Choosing Reference or Remote controller +Version 2.0.0.2 +Updates by Gregory Gee +- Added Link properties. Double click on link and set TCLink parameters for 'bw' and 'delay' +Version 2.0.0.3 +Updates by Gregory Gee +- Hosts and switches are now numbered separately +- From Edit->Preferences, you can set the IP Base +- You can export your topology to a mininet custom topology py file. + +""" + +from optparse import OptionParser +from Tkinter import * +#from Tkinter import Frame, Button, Label, Scrollbar, Canvas, Entry, OptionMenu +#from Tkinter import Menu, BitmapImage, PhotoImage, Wm, Toplevel +import tkFileDialog +import tkSimpleDialog +import csv + +# someday: from ttk import * + +from mininet.log import setLogLevel +from mininet.net import Mininet +from mininet.util import ipStr, netParse, ipAdd +from mininet.term import makeTerm, cleanUpScreens +from mininet.node import Controller, RemoteController, NOX, OVSController +from mininet.link import TCLink +from mininet.cli import CLI + + +CONTROLLERDEF = 'ref' +CONTROLLERS = { 'ref': Controller, + 'ovsc': OVSController, + 'nox': NOX, + 'remote': RemoteController, + 'none': lambda name: None } + +class PrefsDialog(tkSimpleDialog.Dialog): + + def __init__(self, parent, title, prefDefaults): + + self.prefValues = prefDefaults + + tkSimpleDialog.Dialog.__init__(self, parent, title) + + def body(self, master): + + self.var = StringVar(master) + Label(master, text="IP Base:").grid(row=0) + + self.e1 = Entry(master) + + self.e1.grid(row=0, column=1) + + ipBase = self.prefValues['ipBase'] + self.e1.insert(0, ipBase) + + return self.e1 # initial focus + + def apply(self): + ipBase = self.e1.get() + print 'Dialog='+ipBase + self.result = ipBase, '' + +class LinkDialog(tkSimpleDialog.Dialog): + + def __init__(self, parent, title, linkDefaults): + + self.linkValues = linkDefaults + + tkSimpleDialog.Dialog.__init__(self, parent, title) + + def body(self, master): + + self.var = StringVar(master) + Label(master, text="Bandwidth:").grid(row=0) + Label(master, text="Delay:").grid(row=1) + + self.e1 = Entry(master) + self.e2 = Entry(master) + + self.e1.grid(row=0, column=1) + self.e2.grid(row=1, column=1) + + bw = '' + delay = '' + if 'bw' in self.linkValues: + bw = str(self.linkValues['bw']) + if 'delay' in self.linkValues: + delay = self.linkValues['delay'] + self.e1.insert(0, bw) + self.e2.insert(0, delay) + + return self.e1 # initial focus + + def apply(self): + bw = self.e1.get() + delay = self.e2.get() + self.result = bw, delay + +class ControllerDialog(tkSimpleDialog.Dialog): + + def __init__(self, parent, title, ctrlrDefaults=None): + + if ctrlrDefaults: + self.ctrlrValues = ctrlrDefaults + + tkSimpleDialog.Dialog.__init__(self, parent, title) + + def body(self, master): + + self.var = StringVar(master) + Label(master, text="Controller Type:").grid(row=0) + Label(master, text="Remote IP:").grid(row=1) + Label(master, text="Remote Port:").grid(row=2) + + self.e1 = Entry(master) + self.e2 = Entry(master) + + controllerType = self.ctrlrValues['controllerType'] + self.o1 = OptionMenu(master, self.var, "remote", "ref").grid(row=0, column=1) + self.var.set(controllerType) + self.e1.grid(row=1, column=1) + self.e2.grid(row=2, column=1) + + self.e1.insert(0, self.ctrlrValues['remoteIP']) + self.e2.insert(0, self.ctrlrValues['remotePort']) + + return self.o1 # initial focus + + def apply(self): + controllerType = self.var.get() + if controllerType == 'remote': + first = self.e1.get() + second = int(self.e2.get()) + self.result = self.var.get(), first, second + else: + self.result = [self.var.get()] + +class MiniEdit( Frame ): + + "A simple network editor for Mininet." + + def __init__( self, parent=None, cheight=400, cwidth=800 ): + + self.defaultIpBase='10.0.0.0/8' + + self.minieditIpBase=self.defaultIpBase + Frame.__init__( self, parent ) + self.action = None + self.appName = 'MiniEdit' + + # Style + self.font = ( 'Geneva', 9 ) + self.smallFont = ( 'Geneva', 7 ) + self.bg = 'white' + + # Title + self.top = self.winfo_toplevel() + self.top.title( self.appName ) + + # Menu bar + self.createMenubar() + + # Editing canvas + self.cheight, self.cwidth = cheight, cwidth + self.cframe, self.canvas = self.createCanvas() + + # Toolbar + self.controllers = {} + self.controllerbar = self.createControllerBar() + + # Toolbar + self.images = miniEditImages() + self.buttons = {} + self.active = None + self.tools = ( 'Select', 'Host', 'Switch', 'Link' ) + self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' } + self.toolbar = self.createToolbar() + + # Layout + self.toolbar.grid( column=0, row=0, sticky='nsew') + self.cframe.grid( column=1, row=0 ) + self.columnconfigure( 1, weight=1 ) + self.rowconfigure( 0, weight=1 ) + self.controllerbar.grid( column=2, row=0, sticky='nsew' ) + self.pack( expand=True, fill='both' ) + + # About box + self.aboutBox = None + + # Initialize node data + self.nodeBindings = self.createNodeBindings() + self.nodePrefixes = { 'Switch': 's', 'Host': 'h' } + self.widgetToItem = {} + self.itemToWidget = {} + + # Initialize link tool + self.link = self.linkWidget = None + + # Selection support + self.selection = None + + # Keyboard bindings + self.bind( '', lambda event: self.quit() ) + self.bind( '', self.deleteSelection ) + self.bind( '', self.deleteSelection ) + self.focus() + + # Event handling initalization + self.linkx = self.linky = self.linkItem = None + self.lastSelection = None + + # Model initialization + self.links = {} + self.hostCount = 0 + self.switchCount = 0 + self.net = None + + # Close window gracefully + Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit ) + + def quit( self ): + "Stop our network, if any, then quit." + self.stop() + Frame.quit( self ) + + def createMenubar( self ): + "Create our menu bar." + + font = self.font + + mbar = Menu( self.top, font=font ) + self.top.configure( menu=mbar ) + + + fileMenu = Menu( mbar, tearoff=False ) + mbar.add_cascade( label="File", font=font, menu=fileMenu ) + fileMenu.add_command( label="New", font=font, command=self.newTopology ) + fileMenu.add_command( label="Open", font=font, command=self.loadTopology ) + fileMenu.add_command( label="Save", font=font, command=self.saveTopology ) + fileMenu.add_command( label="Export", font=font, command=self.exportTopology ) + fileMenu.add_separator() + fileMenu.add_command( label='Quit', command=self.quit, font=font ) + #fileMenu.add_separator() + #fileMenu.add_command( label="Print", font=font ) + + editMenu = Menu( mbar, tearoff=False ) + mbar.add_cascade( label="Edit", font=font, menu=editMenu ) + editMenu.add_command( label="Cut", font=font, + command=lambda: self.deleteSelection( None ) ) + editMenu.add_command( label="Preferences", font=font, command=self.prefDetails) + + runMenu = Menu( mbar, tearoff=False ) + mbar.add_cascade( label="Run", font=font, menu=runMenu ) + runMenu.add_command( label="Run", font=font, command=self.doRun ) + runMenu.add_command( label="Stop", font=font, command=self.doStop ) + runMenu.add_separator() + runMenu.add_command( label='Xterm', font=font, command=self.xterm ) + + # Application menu + appMenu = Menu( mbar, tearoff=False ) + mbar.add_cascade( label="Help", font=font, menu=appMenu ) + appMenu.add_command( label='About MiniEdit', command=self.about, + font=font) + # Canvas + + def createCanvas( self ): + "Create and return our scrolling canvas frame." + f = Frame( self ) + + canvas = Canvas( f, width=self.cwidth, height=self.cheight, + bg=self.bg ) + + # Scroll bars + xbar = Scrollbar( f, orient='horizontal', command=canvas.xview ) + ybar = Scrollbar( f, orient='vertical', command=canvas.yview ) + canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set ) + + # Resize box + resize = Label( f, bg='white' ) + + # Layout + canvas.grid( row=0, column=1, sticky='nsew') + ybar.grid( row=0, column=2, sticky='ns') + xbar.grid( row=1, column=1, sticky='ew' ) + resize.grid( row=1, column=2, sticky='nsew' ) + + # Resize behavior + f.rowconfigure( 0, weight=1 ) + f.columnconfigure( 1, weight=1 ) + f.grid( row=0, column=0, sticky='nsew' ) + f.bind( '', lambda event: self.updateScrollRegion() ) + + # Mouse bindings + canvas.bind( '', self.clickCanvas ) + canvas.bind( '', self.dragCanvas ) + canvas.bind( '', self.releaseCanvas ) + + return f, canvas + + def updateScrollRegion( self ): + "Update canvas scroll region to hold everything." + bbox = self.canvas.bbox( 'all' ) + if bbox is not None: + self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ], + bbox[ 3 ] ) ) + + def canvasx( self, x_root ): + "Convert root x coordinate to canvas coordinate." + c = self.canvas + return c.canvasx( x_root ) - c.winfo_rootx() + + def canvasy( self, y_root ): + "Convert root y coordinate to canvas coordinate." + c = self.canvas + return c.canvasy( y_root ) - c.winfo_rooty() + + # Toolbar + + def activate( self, toolName ): + "Activate a tool and press its button." + # Adjust button appearance + if self.active: + self.buttons[ self.active ].configure( relief='raised' ) + self.buttons[ toolName ].configure( relief='sunken' ) + # Activate dynamic bindings + self.active = toolName + + def createControllerBar( self ): + "Create and return our Controller Bar frame." + + controllerbar = Frame( self ) + Label( controllerbar, text='Controllers' ).pack() + b = Button( controllerbar, text='c0', font=self.smallFont, command=self.controllerDetails) + b.pack( fill='x' ) + + ctrlr = { 'controllerType': 'ref', + 'remoteIP': '127.0.0.1', + 'remotePort': 6633} + + # controllerType = remote|default|nox + + self.controllers['c0'] = ctrlr + + return controllerbar + + def createToolbar( self ): + "Create and return our toolbar frame." + + toolbar = Frame( self ) + + # Tools + for tool in self.tools: + cmd = ( lambda t=tool: self.activate( t ) ) + b = Button( toolbar, text=tool, font=self.smallFont, command=cmd) + if tool in self.images: + b.config( height=35, image=self.images[ tool ] ) + # b.config( compound='top' ) + b.pack( fill='x' ) + self.buttons[ tool ] = b + self.activate( self.tools[ 0 ] ) + + # Spacer + Label( toolbar, text='' ).pack() + + # Commands + for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]: + doCmd = getattr( self, 'do' + cmd ) + b = Button( toolbar, text=cmd, font=self.smallFont, + fg=color, command=doCmd ) + b.pack( fill='x', side='bottom' ) + + return toolbar + + def doRun( self ): + "Run command." + self.activate( 'Select' ) + for tool in self.tools: + self.buttons[ tool ].config( state='disabled' ) + self.start() + + def doStop( self ): + "Stop command." + self.stop() + for tool in self.tools: + self.buttons[ tool ].config( state='normal' ) + + def addNode( self, node, nodeNum, x, y): + "Add a new node to our canvas." + if 'Switch' == node: + self.switchCount += 1 + if 'Host' == node: + self.hostCount += 1 + name = self.nodePrefixes[ node ] + nodeNum + self.addNamedNode(node, name, x, y) + + def addNamedNode( self, node, name, x, y): + "Add a new node to our canvas." + c = self.canvas + icon = self.nodeIcon( node, name ) + item = self.canvas.create_window( x, y, anchor='c', window=icon, + tags=node ) + self.widgetToItem[ icon ] = item + self.itemToWidget[ item ] = icon + icon.links = {} + + def loadTopology( self ): + "Load command." + self.newTopology() + + myFormats = [ + ('Mininet Topology','*.mn'), + ('All Files','*'), + ] + fin = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb') + csvreader = csv.reader(fin) + for row in csvreader: + if row[0] == 'a': + self.minieditIpBase = row[1] + if row[0] == 'c': + controllerType = row[1] + self.controllers['c0']['controllerType'] = controllerType + if controllerType == 'remote': + self.controllers['c0']['remoteIP'] = row[2] + self.controllers['c0']['remotePort'] = int(row[3]) + if row[0] == 'h': + nodeNum = row[1] + x = row[2] + y = row[3] + self.addNode('Host', nodeNum, float(x), float(y)) + if row[0] == 's': + nodeNum = row[1] + x = row[2] + y = row[3] + self.addNode('Switch', nodeNum, float(x), float(y)) + if row[0] == 'l': + srcNode = row[1] + src = self.findWidgetByName(srcNode) + sx, sy = self.canvas.coords( self.widgetToItem[ src ] ) + + destNode = row[2] + dest = self.findWidgetByName(destNode) + dx, dy = self.canvas.coords( self.widgetToItem[ dest] ) + + self.link = self.canvas.create_line( sx, sy, dx, dy, width=4, + fill='blue', tag='link' ) + bw = '' + delay = '' + linkopts = {} + if len(row) > 3: + bw = row[3] + delay = row[4] + if len(bw) > 0: + linkopts['bw'] = int(bw) + if len(delay) > 0: + linkopts['delay'] = delay + + self.addLink( src, dest, linkopts=linkopts ) + self.createLinkBindings() + + + def findWidgetByName( self, name ): + for widget in self.widgetToItem: + if name == widget[ 'text' ]: + return widget + + def newTopology( self ): + "New command." + for widget in self.widgetToItem.keys(): + self.deleteItem( self.widgetToItem[ widget ] ) + self.hostCount = 0 + self.switchCount = 0 + self.minieditIpBase = self.defaultIpBase + + def printInfo( self ): + "print nodes and links." + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] ) + nodeNum = int( name[ 1: ] ) + if 'Switch' in tags: + print "Switch "+name+" at "+str(x1)+"/"+str(y1)+"." + elif 'Host' in tags: + ipBaseNum, prefixLen = netParse( self.minieditIpBase ) + print 'ipBaseNum='+str(ipBaseNum) + print 'prefixLen='+str(prefixLen) + ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) + print "Host "+name+" with IP "+ip+" at "+str(x1)+"/"+str(y1)+"." + else: + raise Exception( "Cannot create mystery node: " + name ) + + for link in self.links.values(): + ( src, dst, linkopts ) = link + srcName, dstName = src[ 'text' ], dst[ 'text' ] + print "Link from "+srcName+" to "+dstName+"." + + def saveTopology( self ): + "Save command." + myFormats = [ + ('Mininet Topology','*.mn'), + ('All Files','*'), + ] + + fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Save the topology as...") + if len(fileName ) > 0: + #print "Now saving under %s" % fileName + f = open(fileName, 'wb') + fout = csv.writer(f) + + fout.writerow(["a",self.minieditIpBase]) + + # Save Switches and Hosts + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] ) + nodeNum = int( name[ 1: ] ) + if 'Switch' in tags: + fout.writerow(["s",str(nodeNum),str(x1),str(y1)]) + #print "Save Switch "+name+" at "+str(x1)+"/"+str(y1)+"." + elif 'Host' in tags: + fout.writerow(["h",str(nodeNum),str(x1),str(y1)]) + #print "Save Host "+name+" with IP "+ip+" at "+str(x1)+"/"+str(y1)+"." + else: + raise Exception( "Cannot create mystery node: " + name ) + + # Save Links + for link in self.links.values(): + ( src, dst, linkopts ) = link + srcName, dstName = src[ 'text' ], dst[ 'text' ] + bw = '' + delay = '' + if 'bw' in linkopts: + bw = linkopts['bw'] + if 'delay' in linkopts: + delay = linkopts['delay'] + + fout.writerow(["l",srcName,dstName, bw, delay]) + #print "Save Link from "+srcName+" to "+dstName+"." + + # Save Controller + controllerType = self.controllers['c0']['controllerType'] + if controllerType == 'remote': + controllerIP = self.controllers['c0']['remoteIP'] + controllerPort = self.controllers['c0']['remotePort'] + fout.writerow(["c",controllerType, controllerIP, str(controllerPort)]) + elif controllerType == 'nox': + fout.writerow(["c",controllerType]) + else: + fout.writerow(["c",controllerType]) + + f.close() + + def exportTopology( self ): + "Export command." + myFormats = [ + ('Mininet Custom Topology','*.py'), + ('All Files','*'), + ] + + fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Export the topology as...") + if len(fileName ) > 0: + #print "Now saving under %s" % fileName + f = open(fileName, 'wb') + + f.write("from mininet.topo import Topo\n") + f.write("\n") + f.write("class MyTopo(Topo):\n") + f.write("\n") + f.write(" def __init__( self ):\n") + f.write("\n") + f.write(" # Initialize topology and default options\n") + f.write(" Topo.__init__(self)\n") + f.write("\n") + + + # Save Switches and Hosts + f.write(" # Add hosts and switches\n") + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] ) + nodeNum = int( name[ 1: ] ) + if 'Switch' in tags: + f.write(" "+name+" = self.addSwitch('"+name+"')\n") + elif 'Host' in tags: + f.write(" "+name+" = self.addHost('"+name+"')\n") + else: + raise Exception( "Cannot create mystery node: " + name ) + f.write("\n") + + # Save Links + f.write(" # Add links\n") + for link in self.links.values(): + ( src, dst, linkopts ) = link + srcName, dstName = src[ 'text' ], dst[ 'text' ] + bw = '' + delay = '' + optsExist = False + linkOpts = "{" + if 'bw' in linkopts: + bw = linkopts['bw'] + linkOpts = linkOpts + "'bw':"+str(bw) + optsExist = True + if 'delay' in linkopts: + delay = linkopts['delay'] + if optsExist: + linkOpts = linkOpts + "," + linkOpts = linkOpts + "'delay':'"+delay+"'" + optsExist = True + linkOpts = linkOpts + "}" + if optsExist: + f.write(" "+srcName+dstName+" = "+linkOpts+"\n") + #linkopts1 = {'bw':50, 'delay':'5ms'} + f.write(" self.addLink("+srcName+", "+dstName) + if optsExist: + f.write(", **"+srcName+dstName) + f.write(")\n") + + f.write("\n") + f.write("topos = { 'mytopo': ( lambda: MyTopo() ) }\n") + f.write("\n") + f.write("#NOTE: Start Mininet as the following\n") + f.write("# sudo mn --custom "+fileName+" --topo mytopo") + if optsExist: + f.write(" --link tc") + f.write("\n") + f.write("\n") + + + f.close() + + + # Generic canvas handler + # + # We could have used bindtags, as in nodeIcon, but + # the dynamic approach used here + # may actually require less code. In any case, it's an + # interesting introspection-based alternative to bindtags. + + def canvasHandle( self, eventName, event ): + "Generic canvas event handler" + if self.active is None: + return + toolName = self.active + handler = getattr( self, eventName + toolName, None ) + if handler is not None: + handler( event ) + + def clickCanvas( self, event ): + "Canvas click handler." + self.canvasHandle( 'click', event ) + + def dragCanvas( self, event ): + "Canvas drag handler." + self.canvasHandle( 'drag', event ) + + def releaseCanvas( self, event ): + "Canvas mouse up handler." + self.canvasHandle( 'release', event ) + + # Currently the only items we can select directly are + # links. Nodes are handled by bindings in the node icon. + + def findItem( self, x, y ): + "Find items at a location in our canvas." + items = self.canvas.find_overlapping( x, y, x, y ) + if len( items ) == 0: + return None + else: + return items[ 0 ] + + # Canvas bindings for Select, Host, Switch and Link tools + + def clickSelect( self, event ): + "Select an item." + self.selectItem( self.findItem( event.x, event.y ) ) + + def deleteItem( self, item ): + "Delete an item." + # Don't delete while network is running + if self.buttons[ 'Select' ][ 'state' ] == 'disabled': + return + # Delete from model + if item in self.links: + self.deleteLink( item ) + if item in self.itemToWidget: + self.deleteNode( item ) + # Delete from view + self.canvas.delete( item ) + + def deleteSelection( self, _event ): + "Delete the selected item." + if self.selection is not None: + self.deleteItem( self.selection ) + self.selectItem( None ) + + def nodeIcon( self, node, name ): + "Create a new node icon." + icon = Button( self.canvas, image=self.images[ node ], + text=name, compound='top' ) + # Unfortunately bindtags wants a tuple + bindtags = [ str( self.nodeBindings ) ] + bindtags += list( icon.bindtags() ) + icon.bindtags( tuple( bindtags ) ) + return icon + + def newNode( self, node, event ): + "Add a new node to our canvas." + c = self.canvas + x, y = c.canvasx( event.x ), c.canvasy( event.y ) + name = self.nodePrefixes[ node ] + if 'Switch' == node: + self.switchCount += 1 + name = self.nodePrefixes[ node ] + str( self.switchCount ) + if 'Host' == node: + self.hostCount += 1 + name = self.nodePrefixes[ node ] + str( self.hostCount ) + + icon = self.nodeIcon( node, name ) + item = self.canvas.create_window( x, y, anchor='c', window=icon, + tags=node ) + self.widgetToItem[ icon ] = item + self.itemToWidget[ item ] = icon + self.selectItem( item ) + icon.links = {} + + def clickHost( self, event ): + "Add a new host to our canvas." + self.newNode( 'Host', event ) + + def clickSwitch( self, event ): + "Add a new switch to our canvas." + self.newNode( 'Switch', event ) + + def dragLink( self, event ): + "Drag a link's endpoint to another node." + if self.link is None: + return + # Since drag starts in widget, we use root coords + x = self.canvasx( event.x_root ) + y = self.canvasy( event.y_root ) + c = self.canvas + c.coords( self.link, self.linkx, self.linky, x, y ) + + def releaseLink( self, _event ): + "Give up on the current link." + if self.link is not None: + self.canvas.delete( self.link ) + self.linkWidget = self.linkItem = self.link = None + + # Generic node handlers + + def createNodeBindings( self ): + "Create a set of bindings for nodes." + bindings = { + '': self.clickNode, + '': self.dragNode, + '': self.releaseNode, + '': self.enterNode, + '': self.leaveNode, + '': self.xterm + } + l = Label() # lightweight-ish owner for bindings + for event, binding in bindings.items(): + l.bind( event, binding ) + return l + + def selectItem( self, item ): + "Select an item and remember old selection." + self.lastSelection = self.selection + self.selection = item + + def enterNode( self, event ): + "Select node on entry." + self.selectNode( event ) + + def leaveNode( self, _event ): + "Restore old selection on exit." + self.selectItem( self.lastSelection ) + + def clickNode( self, event ): + "Node click handler." + if self.active is 'Link': + self.startLink( event ) + else: + self.selectNode( event ) + return 'break' + + def dragNode( self, event ): + "Node drag handler." + if self.active is 'Link': + self.dragLink( event ) + else: + self.dragNodeAround( event ) + + def releaseNode( self, event ): + "Node release handler." + if self.active is 'Link': + self.finishLink( event ) + + # Specific node handlers + + def selectNode( self, event ): + "Select the node that was clicked on." + item = self.widgetToItem.get( event.widget, None ) + self.selectItem( item ) + + def dragNodeAround( self, event ): + "Drag a node around on the canvas." + c = self.canvas + # Convert global to local coordinates; + # Necessary since x, y are widget-relative + x = self.canvasx( event.x_root ) + y = self.canvasy( event.y_root ) + w = event.widget + # Adjust node position + item = self.widgetToItem[ w ] + c.coords( item, x, y ) + # Adjust link positions + for dest in w.links: + link = w.links[ dest ] + item = self.widgetToItem[ dest ] + x1, y1 = c.coords( item ) + c.coords( link, x, y, x1, y1 ) + + def createLinkBindings( self ): + "Create a set of bindings for nodes." + # Link bindings + # Selection still needs a bit of work overall + # Callbacks ignore event + + def select( _event, link=self.link ): + "Select item on mouse entry." + self.selectItem( link ) + + def highlight( _event, link=self.link ): + "Highlight item on mouse entry." + # self.selectItem( link ) + self.canvas.itemconfig( link, fill='green' ) + + def unhighlight( _event, link=self.link ): + "Unhighlight item on mouse exit." + self.canvas.itemconfig( link, fill='blue' ) + # self.selectItem( None ) + + def linkDetails( _event, link=self.link ): + "Link Details." + self.linkDetails(link) + + self.canvas.tag_bind( self.link, '', highlight ) + self.canvas.tag_bind( self.link, '', unhighlight ) + self.canvas.tag_bind( self.link, '', select ) + self.canvas.tag_bind( self.link, '', linkDetails ) + + def startLink( self, event ): + "Start a new link." + if event.widget not in self.widgetToItem: + # Didn't click on a node + return + w = event.widget + item = self.widgetToItem[ w ] + x, y = self.canvas.coords( item ) + self.link = self.canvas.create_line( x, y, x, y, width=4, + fill='blue', tag='link' ) + self.linkx, self.linky = x, y + self.linkWidget = w + self.linkItem = item + + self.createLinkBindings() + + def finishLink( self, event ): + "Finish creating a link" + if self.link is None: + return + source = self.linkWidget + c = self.canvas + # Since we dragged from the widget, use root coords + x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root ) + target = self.findItem( x, y ) + dest = self.itemToWidget.get( target, None ) + if ( source is None or dest is None or source == dest + or dest in source.links or source in dest.links ): + self.releaseLink( event ) + return + # For now, don't allow hosts to be directly linked + stags = self.canvas.gettags( self.widgetToItem[ source ] ) + dtags = self.canvas.gettags( target ) + if 'Host' in stags and 'Host' in dtags: + self.releaseLink( event ) + return + x, y = c.coords( target ) + c.coords( self.link, self.linkx, self.linky, x, y ) + self.addLink( source, dest ) + # We're done + self.link = self.linkWidget = None + + # Menu handlers + + def about( self ): + "Display about box." + about = self.aboutBox + if about is None: + bg = 'white' + about = Toplevel( bg='white' ) + about.title( 'About' ) + info = self.appName + ': a simple network editor for MiniNet' + warning = 'Development version - not entirely functional!' + author = 'Bob Lantz , April 2010' + line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg ) + line2 = Label( about, text=warning, font='Helvetica 9', bg=bg ) + line3 = Label( about, text=author, font='Helvetica 9', bg=bg ) + line1.pack( padx=20, pady=10 ) + line2.pack(pady=10 ) + line3.pack(pady=10 ) + hide = ( lambda about=about: about.withdraw() ) + self.aboutBox = about + # Hide on close rather than destroying window + Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide ) + # Show (existing) window + about.deiconify() + + def createToolImages( self ): + "Create toolbar (and icon) images." + + def linkDetails( self, link ): + ( src, dst, linkopts ) = self.links[link] + linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts) + if linkBox.result: + #print 'Link is ' + #print ' BW=' + linkBox.result[0] + #print ' BW length =' + str(len(linkBox.result[0])) + newLinkOpts = {} + if len(linkBox.result[0]) > 0: + newLinkOpts['bw'] = int(linkBox.result[0]) + #print ' Delay=' + linkBox.result[1] + #print ' Delay length =' + str(len(linkBox.result[1])) + if len(linkBox.result[1]) > 0: + newLinkOpts['delay'] = linkBox.result[1] + self.links[link] = ( src, dst, newLinkOpts ) + + def prefDetails( self ): + prefDefaults = {'ipBase':self.minieditIpBase} + prefBox = PrefsDialog(self, title='Preferences', prefDefaults=prefDefaults) + if prefBox.result: + self.minieditIpBase = prefBox.result[0] + + def controllerDetails( self ): + ctrlrBox = ControllerDialog(self, title='Controller Details', ctrlrDefaults=self.controllers['c0']) + if ctrlrBox.result: + #print 'Controller is ' + ctrlrBox.result[0] + self.controllers['c0']['controllerType'] = ctrlrBox.result[0] + if ctrlrBox.result[0] == 'remote': + self.controllers['c0']['remoteIP'] = ctrlrBox.result[1] + self.controllers['c0']['remotePort'] = ctrlrBox.result[2] + + + # Model interface + # + # Ultimately we will either want to use a topo or + # mininet object here, probably. + + def addLink( self, source, dest, linkopts={} ): + "Add link to model." + source.links[ dest ] = self.link + dest.links[ source ] = self.link + self.links[ self.link ] = ( source, dest, linkopts) + + def deleteLink( self, link ): + "Delete link from model." + pair = self.links.get( link, None ) + if pair is not None: + source, dest, linkopts = pair + del source.links[ dest ] + del dest.links[ source ] + if link is not None: + del self.links[ link ] + + def deleteNode( self, item ): + "Delete node (and its links) from model." + widget = self.itemToWidget[ item ] + for link in widget.links.values(): + # Delete from view and model + self.deleteItem( link ) + del self.itemToWidget[ item ] + del self.widgetToItem[ widget ] + + def addControllers( self ): + "Add Controllers" + + # Get controller info from panel + controllerType = self.controllers['c0']['controllerType'] + + c0 = None + + # Make controller + print 'Getting controller selection:' + if controllerType == 'remote': + print ' Remote controller chosen' + print ' Remote IP:'+self.controllers['c0']['remoteIP'] + print ' Remote Port:'+str(self.controllers['c0']['remotePort']) + controllerIP = self.controllers['c0']['remoteIP'] + controllerPort = self.controllers['c0']['remotePort'] + c0 = RemoteController('c0', ip=controllerIP, port=controllerPort ) + elif controllerType == 'nox': + print ' NOX controller chosen' + c0 = NOX('c0', noxArgs='') + else: + print ' Reference controller chosen' + c0 = Controller('c0') + + return [c0] + + def build( self ): + print "Build network based on our topology." + + net = Mininet( topo=None, build=False, link=TCLink, ipBase=self.minieditIpBase ) + + net.controllers = self.addControllers() + + # Make nodes + print "Getting Hosts and Switches." + for widget in self.widgetToItem: + name = widget[ 'text' ] + tags = self.canvas.gettags( self.widgetToItem[ widget ] ) + nodeNum = int( name[ 1: ] ) + if 'Switch' in tags: + net.addSwitch( name ) + elif 'Host' in tags: + ipBaseNum, prefixLen = netParse( self.minieditIpBase ) + ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) + net.addHost( name, ip=ip ) + else: + raise Exception( "Cannot create mystery node: " + name ) + + # Make links + print "Getting Links." + for link in self.links.values(): + ( src, dst, linkopts ) = link + srcName, dstName = src[ 'text' ], dst[ 'text' ] + src, dst = net.nameToNode[ srcName ], net.nameToNode[ dstName ] + net.addLink(src, dst, **linkopts) + + self.printInfo() + # Build network (we have to do this separately at the moment ) + net.build() + + return net + + def start( self ): + "Start network." + if self.net is None: + self.net = self.build() + self.net.start() + CLI(self.net) + + def stop( self ): + "Stop network." + if self.net is not None: + self.net.stop() + cleanUpScreens() + self.net = None + + def xterm( self, _ignore=None ): + "Make an xterm when a button is pressed." + if ( self.selection is None or + self.net is None or + self.selection not in self.itemToWidget ): + return + name = self.itemToWidget[ self.selection ][ 'text' ] + if name not in self.net.nameToNode: + return + term = makeTerm( self.net.nameToNode[ name ], 'Host' ) + self.net.terms.append( term ) + + def iperf( self, _ignore=None ): + "Make an xterm when a button is pressed." + if ( self.selection is None or + self.net is None or + self.selection not in self.itemToWidget ): + return + name = self.itemToWidget[ self.selection ][ 'text' ] + if name not in self.net.nameToNode: + return + self.net.nameToNode[ name ].cmd( 'iperf -s -p 5001 &' ) + +def miniEditImages(): + "Create and return images for MiniEdit." + + # Image data. Git will be unhappy. However, the alternative + # is to keep track of separate binary files, which is also + # unappealing. + + return { + 'Select': BitmapImage( + file='/usr/include/X11/bitmaps/left_ptr' ), + + 'Host': PhotoImage( data=r""" + R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M + mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m + Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A + M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM + AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz + /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ + zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ + mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz + ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ + M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ + AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA + /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM + zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm + mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA + ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM + MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm + AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A + ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI + AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA + RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA + ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw + BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2 + HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO + p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/ + C8cSBBAQADs= + """ ), + + 'Switch': PhotoImage( data=r""" + R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M + mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m + Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A + M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM + AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz + /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ + zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ + mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz + ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ + M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ + AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA + /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM + zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm + mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA + ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM + MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm + AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A + ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI + AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA + RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA + ACH5BAEAAAAALAAAAAAgABgAAAhwAAEIHEiwoMGDCBMqXMiwocOH + ECNKnEixosWB3zJq3Mixo0eNAL7xG0mypMmTKPl9Cznyn8uWL/m5 + /AeTpsyYI1eKlBnO5r+eLYHy9Ck0J8ubPmPOrMmUpM6UUKMa/Ui1 + 6saLWLNq3cq1q9evYB0GBAA7 + """ ), + + 'Link': PhotoImage( data=r""" + R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M + mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m + Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A + M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM + AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz + /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/ + zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ + mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz + ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/ + M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ + AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA + /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM + zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm + mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA + ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM + MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm + AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A + ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI + AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA + RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA + ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG + Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy + lBmxI8mSNknm1Dnx5sCAADs= + """ ) + } + +def addDictOption( opts, choicesDict, default, name, helpStr=None ): + """Convenience function to add choices dicts to OptionParser. + opts: OptionParser instance + choicesDict: dictionary of valid choices, must include default + default: default choice key + name: long option name + help: string""" + if default not in choicesDict: + raise Exception( 'Invalid default %s for choices dict: %s' % + ( default, name ) ) + if not helpStr: + helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) + + '[,param=value...]' ) + opts.add_option( '--' + name, + type='string', + default = default, + help = helpStr ) + +if __name__ == '__main__': + setLogLevel( 'info' ) + app = MiniEdit() + app.mainloop() diff --git a/mypyretic.py b/mypyretic.py new file mode 100644 index 00000000..64179d53 --- /dev/null +++ b/mypyretic.py @@ -0,0 +1,279 @@ +#!/usr/bin/python + +################################################################################ +# The Pyretic Project # +# frenetic-lang.org/pyretic # +# author: Joshua Reich (jreich@cs.princeton.edu) # +################################################################################ +# Licensed to the Pyretic Project by one or more contributors. See the # +# NOTICES file distributed with this work for additional information # +# regarding copyright and ownership. The Pyretic Project licenses this # +# file to you under the following license. # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided the following conditions are met: # +# - Redistributions of source code must retain the above copyright # +# notice, this list of conditions and the following disclaimer. # +# - Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in # +# the documentation or other materials provided with the distribution. # +# - The names of the copyright holds and contributors may not be used to # +# endorse or promote products derived from this work without specific # +# prior written permission. # +# # +# 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 file distributed with this work for specific language governing # +# permissions and limitations under the License. # +################################################################################ + + +import sys +import threading +import signal +import subprocess +from importlib import import_module +from optparse import OptionParser +import re +import os +import datetime + +of_client = None + +def signal_handler(signal, frame): + print '\n----starting pyretic shutdown------' +# for thread in threading.enumerate(): +# print (thread,thread.isAlive()) + if of_client != None: + print "attempting to kill of_client" + of_client.kill() + print "attempting get output of of_client:" + output = of_client.communicate()[0] + print output + + print "pyretic.py done" + sys.exit(0) + +def buildOptions(): + desc = ( 'Pyretic runtime' ) + usage = ( '%prog [options]\n' + '(type %prog -h for details)' ) + op = OptionParser( description=desc, usage=usage ) + op.add_option( '--frontend-only', '-f', action="store_true", + dest="frontend_only", help = 'only start the frontend' ) + op.add_option( '--mode', '-m', type='choice', + choices=['interpreted','i','reactive0','r0'], + help = '|'.join( ['interpreted/i','reactive0/r0'] ) ) + op.add_option( '--verbosity', '-v', type='choice', + choices=['low','normal','high'], default = 'low', + help = '|'.join( ['quiet','high'] ) ) + op.add_option('--port', '-p', action = 'store', type = 'int', + dest = "listenPort", default= 6633, + help = 'set listenPort') + + from tools.comm import BACKEND_PORT + + op.add_option('--backendPort', '-P', action = 'store', type = 'int', + dest = "backendPort", default= BACKEND_PORT, + help = 'set backendPort') + + localHost = '127.0.0.1' + + op.add_option('--listenIP', '-i', action = 'store', type = 'string', + dest = "listenIP", default= '0.0.0.0', + help = 'set listenIP') + op.add_option('--backendIP', '-I', action = 'store', type = 'string', + dest = "backendIP", default= localHost, + help = 'set backendIP') + + op.add_option('--client', '-c', action = 'store', type = 'string', + dest = "client", default= 'pox_client', + help = 'use this OF client') + + op.add_option('--logDirName', '-d', action = 'store', type = 'string', + dest = "logDirName", default= 'pyretic', + help = 'set log dir name') + + op.add_option('--logLevel', '-l', action = 'store', type = 'int', + dest = "logLevel", default= 100, + help = 'set log level') + + op.add_option( '--ofclient-only', '-o', action="store_true", + dest="ofclient_only", help = 'only start the OF client' ) + + op.add_option( '--echoServer', '-e', action="store_true", + dest="useEchoServer", help = 'use EchoServer for testing' ) + + op.set_defaults(frontend_only=False,mode='reactive0') + options, args = op.parse_args() + + return op, options, args + +def parseArgs(): + """Parse command-line args and return options object. + returns: opts parse options dict""" + + end_args = 0 + for arg in sys.argv[1:]: + if not re.match('-',arg): + end_args = sys.argv.index(arg) + kwargs_to_pass = None + if end_args > 0: + kwargs_to_pass = sys.argv[end_args+1:] + sys.argv = sys.argv[:end_args+1] + + op, options, args = buildOptions() + + if options.mode == 'i': + options.mode = 'interpreted' + elif options.mode == 'r0': + options.mode = 'reactive0' + + + return (op, options, args, kwargs_to_pass) + +def getPaths(): + try: + output = subprocess.check_output('echo $PYTHONPATH',shell=True).strip() + + except: + print 'Error: Unable to obtain PYTHONPATH' + sys.exit(1) + + poxpath = None + pyreticpath = None + mininetpath = None + + for p in output.split(':'): + if re.match('.*pox/?$',p): + poxpath = os.path.abspath(p) + + elif re.match('.*pyretic/?$',p): + pyreticpath = os.path.abspath(p) + + elif re.match('.*mininet/?$',p): + mininetpath = os.path.abspath(p) + + # print("poxpath=%s pyreticpath=%s mininetpath=%s" %(poxpath, pyreticpath, mininetpath)) + + return (poxpath, pyreticpath, mininetpath) + +def emptyLogDir(logDir): + for the_file in os.listdir(logDir): + file_path = os.path.join(logDir, the_file) + + try: + if os.path.isfile(file_path): + os.unlink(file_path) + + except Exception, e: + print e + +def setPyreticEnv(pyreticpath, options): + + datetimeNow = datetime.datetime.now() + dateNow = datetimeNow.strftime("%Y-%m-%d") + timeNow = datetimeNow.strftime("%H:%M:%S.%f") + + pyreticLogsDir = "%s/%s" % (pyreticpath, "logs") + logDirPathName = "%s/%s/%s" % (pyreticLogsDir, dateNow, options.logDirName) + + # print("logDirPathName=%s logLevel=%s" % (logDirPathName, options.logLevel)) + + if not os.path.isdir(logDirPathName): + os.makedirs(logDirPathName) + + else: + emptyLogDir(logDirPathName) + + os.environ["PYRETICLOGDIR"] = logDirPathName + os.environ["PYRETICLOGLEVEL"] = str(options.logLevel) + +def getModuleName(op, args): + try: + return(args[0]) + + except IndexError: + print 'Module must be specified' + print '' + op.print_usage() + sys.exit(1) + +def getMainModule(op, args): + module_name = getModuleName(op, args) + + try: + module = import_module(module_name) + return module.main + + except ImportError: + print 'Must be a valid python module' + print 'e.g, full module name,' + print ' no .py suffix,' + print ' located on the system PYTHONPATH' + print '' + op.print_usage() + sys.exit(1) + +def startRuntime(op, options, args, kwargs_to_pass): + main = getMainModule(op, args) + kwargs = { k : v for [k,v] in [ i.lstrip('--').split('=') for i in kwargs_to_pass ]} + + sys.setrecursionlimit(1500) #INCREASE THIS IF "maximum recursion depth exceeded" + + from pyretic.backend.backend import Backend + from pyretic.backend.BackendServer import BackendServer + + if options.useEchoServer: + backEnd = BackendServer(port=options.backendPort) + options.frontend_only = True + + else: + backEnd = Backend(port=options.backendPort) + + from pyretic.core.runtime import Runtime + runtime = Runtime(backEnd, main,kwargs, options.mode, options.verbosity, False, False) + +def startOFClient(poxpath, options): + + if poxpath is None: + print 'Error: pox not found in PYTHONPATH' + sys.exit(1) + + pox_exec = os.path.join(poxpath,'pox.py') + python=sys.executable + + poxClient = 'of_client.%s' % options.client + backendIP = '--ip=%s' % options.backendIP + backendPort = '--port=%d' % options.backendPort + + OF = 'openflow.of_01' + listenIP = '--address=%s' % options.listenIP + listenPort = '--port=%d' % options.listenPort + + procCmd = [python, pox_exec, poxClient, backendIP, backendPort, OF, listenIP, listenPort] + + global of_client + + of_client = subprocess.Popen(procCmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + +def main(): + global of_client + (op, options, args, kwargs_to_pass) = parseArgs() + + (poxpath, pyreticpath, mininetpath) = getPaths() + + setPyreticEnv(pyreticpath, options) + + if not options.ofclient_only: + startRuntime(op=op, options=options, args=args, kwargs_to_pass=kwargs_to_pass) + + if not options.frontend_only: + startOFClient(poxpath, options) + + signal.signal(signal.SIGINT, signal_handler) + signal.pause() + +if __name__ == '__main__': + main() diff --git a/mysendy_json.py b/mysendy_json.py new file mode 100644 index 00000000..84fd52e6 --- /dev/null +++ b/mysendy_json.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python + + +''' +Coursera: +- Software Defined Networking (SDN) course +-- Module 7 Programming Assignment + +Professor: Nick Feamster +Teaching Assistant: Muhammad Shahbaz +''' + +################################################################################ +# Resonance Project # +# Resonance implemented with Pyretic platform # +# author: Hyojoon Kim (joonk@gatech.edu) # +# author: Nick Feamster (feamster@cc.gatech.edu) # +################################################################################ + +import socket +import sys +import struct +import json +from optparse import OptionParser + + +CTRL_ADDR = '127.0.0.1' +CONN_PORT = 50001 + +eventTypes = {'auth': 0, 'ids': 1, 'lb': 2} + +def main(): + + desc = ('Send JSON Events') + usage = ('%prog [options]\n' + '(type %prog -h for details)') + + op = OptionParser(description=desc, usage=usage) + op.add_option('--host-IP', '-i', action="store", + dest="hostIP", help = 'the host IP for which a state change happens') + + op.add_option('--event-type', '-e', type='choice', + dest="eventType", choices=['auth','ids', 'lb'], + help = '|'.join( ['auth','ids','lb'] )) + + + op.add_option('--event-value', '-V', action="store", + dest="eventValue", help = 'the host IP for which a state change happens') + + op.add_option('--ctrl-addr', '-a', action="store", default=CTRL_ADDR, + dest="controlIP", help = 'the controller IP') + + op.add_option('--ctrl-port', '-p', action="store", default=CONN_PORT, + dest="controlPort", help = 'the controller port') + + options, args = op.parse_args() + eventnum = eventTypes[options.eventType] + controlIP = options.controlIP + controlPort = options.controlPort + + print options.hostIP + + sender=dict(sender_id=1, description=1, ip_addr=1, mac_addr=1) + + data=dict(data_type=eventnum, data=options.hostIP, value=options.eventValue) + + transition=dict(prev=1, next=1) + + event = dict(event_id=1, event_type=eventnum, event_code=1, description=1, sender=sender, data=data, transition=transition) + + data = dict(event=event) + + # create socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # connect to server + s.connect((controlIP, controlPort)) + + + bufsize = len(data) + + # send data + totalsent = 0 + s.send(json.dumps(data)) + s.close() + +### START ### + +if __name__ == '__main__': + main() + +### end of function ### diff --git a/of_client/POXClient.py b/of_client/POXClient.py new file mode 100644 index 00000000..78c5d933 --- /dev/null +++ b/of_client/POXClient.py @@ -0,0 +1,708 @@ + +################################################################################ +# The Pyretic Project # +# frenetic-lang.org/pyretic # +# author: Joshua Reich (jreich@cs.princeton.edu) # +# author: Christopher Monsanto (chris@monsan.to) # +################################################################################ +# Licensed to the Pyretic Project by one or more contributors. See the # +# NOTICES file distributed with this work for additional information # +# regarding copyright and ownership. The Pyretic Project licenses this # +# file to you under the following license. # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided the following conditions are met: # +# - Redistributions of source code must retain the above copyright # +# notice, this list of conditions and the following disclaimer. # +# - Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in # +# the documentation or other materials provided with the distribution. # +# - The names of the copyright holds and contributors may not be used to # +# endorse or promote products derived from this work without specific # +# prior written permission. # +# # +# 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 file distributed with this work for specific language governing # +# permissions and limitations under the License. # +################################################################################ + +import threading +import asyncore + +import pox.openflow.libopenflow_01 as of +from pox.core import core +from pox.lib import revent, addresses as packetaddr, packet as packetlib +from pox.lib.packet.ethernet import ethernet +from pox.lib.packet.ethernet import LLDP_MULTICAST, NDP_MULTICAST +from pox.lib.packet.lldp import lldp, chassis_id, port_id, end_tlv +from pox.lib.packet.lldp import ttl, system_description + +from tools.comm import * +from tools.logger import simpleLogger + +import datetime + +OFCLIENTLOGLEVEL = 5 +useThreading = True + +def inport_value_hack(outport): + if outport > 1: + return 1 + else: + return 2 + + +class BackendChannel(asynchat.async_chat): + """Sends messages to the server and receives responses. + """ + def __init__(self, host, port, of_client): + self.of_client = of_client + self.trace = of_client.trace + self.trace("BackendChannel host=%s port=%s of_client=%s\n" % (host, port, of_client), timeStamped=True) + self.received_data = [] + asynchat.async_chat.__init__(self) + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.connect((host, port)) + self.set_terminator(TERM_CHAR) + return + + def handle_connect(self): + self.trace("Connected to pyretic frontend.") + + def collect_incoming_data(self, data): + """Read an incoming message from the client and put it into our outgoing queue.""" + self.trace("collect_incoming_data data=%s\n" % data, timeStamped=True) + + with self.of_client.channel_lock: + self.received_data.append(data) + + def dict2OF(self,d): + def convert(h,val): + if h in ['srcmac','dstmac']: + return packetaddr.EthAddr(val) + elif h in ['srcip','dstip']: + return packetaddr.IPAddr(val) + elif h in ['vlan_id','vlan_pcp'] and val == 'None': + return None + else: + return val + return { h : convert(h,val) for (h, val) in d.items()} + + def found_terminator(self): + """The end of a command or message has been seen.""" + + with self.of_client.channel_lock: + msg = deserialize(self.received_data) + + msg0 = msg[0] + self.trace("found_terminator: %s\n" % msg, timeStamped=True) + + # USE DESERIALIZED MSG + if msg[0] == 'inject_discovery_packet': + switch = msg[1] + port = msg[2] + self.of_client.inject_discovery_packet(switch,port) + elif msg[0] == 'packet': + packet = self.dict2OF(msg[1]) + self.of_client.send_to_switch(packet) + elif msg[0] == 'install': + pred = self.dict2OF(msg[1]) + actions = map(self.dict2OF,msg[2]) + self.of_client.install_flow(pred,actions) + elif msg[0] == 'clear_all': + self.of_client.clear_all() + else: + print "ERROR: Unknown msg from frontend %s" % msg + + +class POXClient(revent.EventMixin): + # NOT **kwargs + def __init__(self,show_traces=False,debug_packet_in=False,ip='127.0.0.1',port=BACKEND_PORT): + + logFileName = "%s.log" % __name__.split('.')[1] + self.logger = simpleLogger(baseName=logFileName, logLevel=OFCLIENTLOGLEVEL) + # self.trace = logger.write + self.debug = True + + self.trace("POXClient ip=%s port=%s\n" % (ip, port), timeStamped=True) + self.switches = {} + self.show_traces = show_traces + self.debug_packet_in = debug_packet_in + self.packetno = 0 + self.channel_lock = threading.Lock() + + if core.hasComponent("openflow"): + self.listenTo(core.openflow) + + self.backend_channel = BackendChannel(ip, port, self) + self.adjacency = {} # From Link to time.time() stamp + + def trace(self, logLine, timeStamped=False): + if not self.debug: + return + + self.logger.write(logLine, timeStamped) + + def packet_from_network(self, switch, inport, raw): + h = {} + h["switch"] = switch + h["inport"] = inport + + p = packetlib.ethernet(raw) + h["header_len"] = p.hdr_len + h["payload_len"] = p.payload_len + h["srcmac"] = p.src.toRaw() + h["dstmac"] = p.dst.toRaw() + h["ethtype"] = p.type + + p = p.next + if isinstance(p, packetlib.vlan): + h['vlan_id'] = p.id + h['vlan_pcp'] = p.pcp + h["ethtype"] = p.eth_type + p = p.next + + if isinstance(p, packetlib.ipv4): + h["srcip"] = p.srcip.toRaw() + h["dstip"] = p.dstip.toRaw() + h["protocol"] = p.protocol + h["tos"] = p.tos + p = p.next + + if isinstance(p, packetlib.udp) or isinstance(p, packetlib.tcp): + h["srcport"] = p.srcport + h["dstport"] = p.dstport + elif isinstance(p, packetlib.icmp): + h["srcport"] = p.type + h["dstport"] = p.code + elif isinstance(p, packetlib.arp): + if p.opcode <= 255: + h["ethtype"] = packetlib.ethernet.ARP_TYPE + h["protocol"] = p.opcode + h["srcip"] = p.protosrc.toRaw() + h["dstip"] = p.protodst.toRaw() + + h["raw"] = raw + self.trace("packet_from_network: h=%s\n" % h, timeStamped=True) + + return h + + + def make_arp(self, packet): + p = packetlib.ethernet() + p.src = packet["srcmac"] + p.dst = packet["dstmac"] + + p.type = packetlib.ethernet.ARP_TYPE + p.next = packetlib.arp(prev=p) + + p.next.hwsrc = packet["srcmac"] + p.next.hwdst = packet["dstmac"] + p.next.protosrc = packet["srcip"] + p.next.protodst = packet["dstip"] + p.next.opcode = packet['protocol'] + + return p + + + def packet_to_network(self, packet): + self.trace("packet_to_network: packet=%s\n" % packet, timeStamped=True) + + if len(packet["raw"]) == 0: + if packet["ethtype"] == packetlib.ethernet.ARP_TYPE: + p_begin = p = self.make_arp(packet) + else: # BLANK PACKET FOR NOW - MAY NEED TO SUPPORT OTHER PACKETS LATER + p_begin = p = packetlib.ethernet() + else: + p_begin = p = packetlib.ethernet(packet["raw"]) + + # ETHERNET PACKET IS OUTERMOST + p.src = packet["srcmac"] + p.dst = packet["dstmac"] + + if 'vlan_id' in packet: + if isinstance(p.next, packetlib.vlan): + p = p.next + else: + # Make a vlan header + old_eth_type = p.type + p.type = 0x8100 + p.next = packetlib.vlan(next=p.next) + p = p.next + p.eth_type = old_eth_type + p.id = packet['vlan_id'] + p.pcp = packet['vlan_pcp'] + else: + if isinstance(p.next, packetlib.vlan): + p.type = p.next.eth_type # Restore encapsulated eth type + p.next = p.next.next # Remove vlan from header + + # GET PACKET INSIDE ETHERNET/VLAN + p = p.next + if isinstance(p, packetlib.ipv4): + p.srcip = packet["srcip"] + p.dstip = packet["dstip"] + p.protocol = packet["protocol"] + p.tos = packet["tos"] + + p = p.next + if isinstance(p, packetlib.udp) or isinstance(p, packetlib.tcp): + p.srcport = packet["srcport"] + p.dstport = packet["dstport"] + elif isinstance(p, packetlib.icmp): + p.type = packet["srcport"] + p.code = packet["dstport"] + + elif isinstance(p, packetlib.arp): + if 'vlan_id' in packet: + p.opcode = packet["protocol"] + p.protosrc = packet["srcip"] + p.protodst = packet["dstip"] + else: + p_begin = self.make_arp(packet) + + return p_begin.pack() + + def _handle_ComponentRegistered (self, event): + if event.name == "openflow": + self.listenTo(core.openflow) + return EventRemove # We don't need this listener anymore + + def active_ofp_port_config(self,configs): + active = [] + for (config,bit) in of.ofp_port_config_rev_map.items(): + if configs & bit: + active.append(config) + return active + + def active_ofp_port_state(self,states): + """get active ofp port state values + NOTE: POX's doesn't match ofp_port_state_rev_map""" + active = [] + for (state,bit) in of.ofp_port_state_rev_map.items(): + if states & bit: + active.append(state) + return active + + def active_ofp_port_features(self,features): + active = [] + for (feature,bit) in of.ofp_port_features_rev_map.items(): + if features & bit: + active.append(feature) + return active + + def inspect_ofp_phy_port(self,port,prefix=""): + print "%sport_no: " % prefix, + port_id = port.port_no + for name,port_no in of.ofp_port_rev_map.iteritems(): + if port.port_no == port_no: + port_id = name + print port_id + print "%shw_addr: " % prefix, + print port.hw_addr + print "%sname: " % prefix, + print port.name + print "%sconfig: " % prefix, + print self.active_ofp_port_config(port.config) + print "%sstate: " % prefix, + print self.active_ofp_port_state(port.state) + print "%scurr: " % prefix, + print self.active_ofp_port_features(port.curr) + print "%sadvertised: " % prefix, + print self.active_ofp_port_features(port.advertised) + print "%ssupported: " % prefix, + print self.active_ofp_port_features(port.supported) + print "%speer: " % prefix, + print self.active_ofp_port_features(port.peer) + + + def create_discovery_packet (self, dpid, port_num, port_addr): + """ + Build discovery packet + """ + import pox.lib.packet as pkt + chassis_id = pkt.chassis_id(subtype=pkt.chassis_id.SUB_LOCAL) + chassis_id.id = bytes('dpid:' + hex(long(dpid))[2:-1]) + + port_id = pkt.port_id(subtype=pkt.port_id.SUB_PORT, id=str(port_num)) + + ttl = pkt.ttl(ttl = 120) + + sysdesc = pkt.system_description() + sysdesc.payload = bytes('dpid:' + hex(long(dpid))[2:-1]) + + discovery_packet = pkt.lldp() + discovery_packet.tlvs.append(chassis_id) + discovery_packet.tlvs.append(port_id) + discovery_packet.tlvs.append(ttl) + discovery_packet.tlvs.append(sysdesc) + discovery_packet.tlvs.append(pkt.end_tlv()) + + eth = pkt.ethernet(type=pkt.ethernet.LLDP_TYPE) + eth.src = port_addr + eth.dst = pkt.ETHERNET.NDP_MULTICAST + eth.payload = discovery_packet + + po = of.ofp_packet_out(action = of.ofp_action_output(port=port_num)) + po.data = eth.pack() + return po.pack() + + + def inject_discovery_packet(self,switch, port): + self.trace("inject_discovery_packet: switch=%s port=%s\n" % (switch, port)) + try: + hw_addr = self.switches[switch]['ports'][port] + packet = self.create_discovery_packet(switch, port, hw_addr) + core.openflow.sendToDPID(switch, packet) + except KeyError: + pass + + + def send_to_pyretic(self,msg): + self.trace("send_to_pyretic: %s\n" % msg, timeStamped=True) + + serialized_msg = serialize(msg) + try: + with self.channel_lock: + self.backend_channel.push(serialized_msg) + except IndexError as e: + print "ERROR PUSHING MESSAGE %s" % msg + pass + + + def send_to_switch(self,packet): + switch = packet["switch"] + outport = packet["outport"] + try: + inport = packet["inport"] + if inport == -1 or inport == outport: + inport = inport_value_hack(outport) + except KeyError: + inport = inport_value_hack(outport) + + msg = of.ofp_packet_out() + msg.in_port = inport + msg.data = self.packet_to_network(packet) + msg.actions.append(of.ofp_action_output(port = outport)) + + thisConnection = self.switches[switch]['connection'] + + #if self.show_traces: + if self.trace != None: + self.trace("========= POX/OF SEND ================", timeStamped=True) + self.trace(msg) + # If the following line is un-commented, 'pingall' fails + # Tested with BADIP2IP and BADIP3IP + + # self.trace(packetlib.ethernet(msg._get_data())) + self.trace("thisConnection=%s\n" % thisConnection) + self.trace("") + + ## HANDLE PACKETS SEND ON LINKS THAT HAVE TIMED OUT + try: + # self.switches[switch]['connection'].send(msg) + thisConnection.send(msg) + + except Runtimerror, e: + print "ERROR:send_to_switch: %s to switch %d" % (str(e),switch) + # TODO - ATTEMPT TO RECONNECT SOCKET + except KeyError, e: + print "ERROR:send_to_switch: No connection to switch %d available" % switch + # TODO - IF SOCKET RECONNECTION, THEN WAIT AND RETRY + + + def install_flow(self,pred,action_list): + self.trace("install_flow: pred=%s action_list=%s\n" % (pred,action_list), timeStamped=True) + + switch = pred['switch'] + + ### BUILD OF MATCH + inport = pred['inport'] + match = of.ofp_match() + match.in_port = pred['inport'] + match.dl_src = pred['srcmac'] + match.dl_dst = pred['dstmac'] + match.dl_type = pred['ethtype'] + if 'vlan_id' in pred: + match.dl_vlan = pred['vlan_id'] + else: + match.dl_vlan = 0xffff + if 'vlan_pcp' in pred: + match.dl_vlan_pcp = pred['vlan_pcp'] + else: + match.dl_vlan_pcp = 0 + match.nw_proto = pred['protocol'] + if 'srcip' in pred: + match.nw_src = pred['srcip'] + if 'dstip' in pred: + match.nw_dst = pred['dstip'] + if 'tos' in pred: + match.nw_tos = pred['tos'] + if 'srcport' in pred: + match.tp_src = pred['srcport'] + if 'dstport' in pred: + match.tp_dst = pred['dstport'] + + ### BUILD OF ACTIONS + of_actions = [] + for actions in action_list: + outport = actions['outport'] + del actions['outport'] + if 'srcmac' in actions: + of_actions.append(of.ofp_action_dl_addr.set_src(actions['srcmac'])) + if 'dstmac' in actions: + of_actions.append(of.ofp_action_dl_addr.set_dst(actions['dstmac'])) + if 'srcip' in actions: + of_actions.append(of.ofp_action_nw_addr.set_src(actions['srcip'])) + if 'dstip' in actions: + of_actions.append(of.ofp_action_nw_addr.set_dst(actions['dstip'])) + if 'vlan_id' in actions: + if actions['vlan_id'] is None: + of_actions.append(of.ofp_action_strip_vlan()) + else: + of_actions.append(of.ofp_action_vlan_vid(vlan_vid=actions['vlan_id'])) + if 'vlan_pcp' in actions: + if actions['vlan_pcp'] is None: + if not actions['vlan_id'] is None: + raise RuntimeError("vlan_id and vlan_pcp must be set together!") + pass + else: + of_actions.append(of.ofp_action_vlan_pcp(vlan_pcp=actions['vlan_pcp'])) + if outport == inport: + of_actions.append(of.ofp_action_output(port=of.OFPP_IN_PORT)) + else: + of_actions.append(of.ofp_action_output(port=outport)) + + msg = of.ofp_flow_mod(command=of.OFPFC_ADD, + idle_timeout=of.OFP_FLOW_PERMANENT, + hard_timeout=of.OFP_FLOW_PERMANENT, + match=match, + actions=of_actions) + thisConnection = self.switches[switch]['connection'] + + try: + #self.switches[switch]['connection'].send(msg) + thisConnection.send(msg) + except RuntimeError, e: + print "ERROR:install_flow: %s to switch %d" % (str(e),switch) + except KeyError, e: + print "ERROR:install_flow: No connection to switch %d available" % switch + + + def send_all_flows_to_controller(self,switch): + thisConnection = self.switches[switch]['connection'] + self.trace("send_all_flows_to_controller: switch=%s\n" % switch, timeStamped=True) + + msg = of.ofp_flow_mod(match = of.ofp_match()) + msg.actions.append(of.ofp_action_output(port = of.OFPP_CONTROLLER)) + # self.switches[switch]['connection'].send(msg) + thisConnection.send(msg) + + def clear_switch(self,switch): + thisConnection = self.switches[switch]['connection'] + self.trace("clear_switch: thisConnection=%s\n" % thisConnection, timeStamped=True) + + d = of.ofp_flow_mod(command = of.OFPFC_DELETE) + # self.switches[switch]['connection'].send(d) + thisConnection.send(d) + + def clear_all(self): + for switch in self.switches.keys(): + self.clear_switch(switch) + self.send_all_flows_to_controller(switch) + + + def _handle_ConnectionUp(self, event): + assert event.dpid not in self.switches + + self.switches[event.dpid] = {} + self.switches[event.dpid]['connection'] = event.connection + self.switches[event.dpid]['ports'] = {} + + self.send_all_flows_to_controller(event.dpid) + + self.send_to_pyretic(['switch','join',event.dpid,'BEGIN']) + + # port type is ofp_phy_port + for port in event.ofp.ports: + if port.port_no <= of.OFPP_MAX: + self.switches[event.dpid]['ports'][port.port_no] = port.hw_addr + CONF_UP = not 'OFPPC_PORT_DOWN' in self.active_ofp_port_config(port.config) + STAT_UP = not 'OFPPS_LINK_DOWN' in self.active_ofp_port_state(port.state) + self.send_to_pyretic(['port','join',event.dpid, port.port_no, CONF_UP, STAT_UP]) + + self.send_to_pyretic(['switch','join',event.dpid,'END']) + + + def _handle_ConnectionDown(self, event): + assert event.dpid in self.switches + + del self.switches[event.dpid] + self.send_to_pyretic(['switch','part',event.dpid]) + + + def _handle_PortStatus(self, event): + port = event.ofp.desc + if event.port <= of.OFPP_MAX: + if event.added: + self.switches[event.dpid]['ports'][event.port] = event.ofp.desc.hw_addr + #self.runtime.network.port_joins.signal((event.dpid, event.port)) + CONF_UP = not 'OFPPC_PORT_DOWN' in self.active_ofp_port_config(port.config) + STAT_UP = not 'OFPPS_LINK_DOWN' in self.active_ofp_port_state(port.state) + self.send_to_pyretic(['port','join',event.dpid, port.port_no, CONF_UP, STAT_UP]) + elif event.deleted: + try: + del self.switches[event.dpid]['ports'][event.port] + except KeyError: + pass # SWITCH ALREADY DELETED + self.send_to_pyretic(['port','part',event.dpid,event.port]) + elif event.modified: + CONF_UP = not 'OFPPC_PORT_DOWN' in self.active_ofp_port_config(port.config) + STAT_UP = not 'OFPPS_LINK_DOWN' in self.active_ofp_port_state(port.state) + self.send_to_pyretic(['port','mod',event.dpid, event.port, CONF_UP, STAT_UP]) + else: + raise RuntimeException("Unknown port status event") + + + def handle_lldp(self,packet,event): + import pox.lib.packet as pkt + from pox.openflow.discovery import Discovery, LinkEvent + import time + + lldph = packet.find(pkt.lldp) + if lldph is None or not lldph.parsed: + return + if len(lldph.tlvs) < 3: + return + if lldph.tlvs[0].tlv_type != pkt.lldp.CHASSIS_ID_TLV: + return + if lldph.tlvs[1].tlv_type != pkt.lldp.PORT_ID_TLV: + return + if lldph.tlvs[2].tlv_type != pkt.lldp.TTL_TLV: + return + + def lookInSysDesc (): + r = None + for t in lldph.tlvs[3:]: + if t.tlv_type == pkt.lldp.SYSTEM_DESC_TLV: + # This is our favored way... + for line in t.payload.split('\n'): + if line.startswith('dpid:'): + try: + return int(line[5:], 16) + except: + pass + if len(t.payload) == 8: + # Maybe it's a FlowVisor LLDP... + # Do these still exist? + try: + return struct.unpack("!Q", t.payload)[0] + except: + pass + return None + + originatorDPID = lookInSysDesc() + + if originatorDPID == None: + # We'll look in the CHASSIS ID + if lldph.tlvs[0].subtype == pkt.chassis_id.SUB_LOCAL: + if lldph.tlvs[0].id.startswith('dpid:'): + # This is how NOX does it at the time of writing + try: + originatorDPID = int(lldph.tlvs[0].id[5:], 16) + except: + pass + if originatorDPID == None: + if lldph.tlvs[0].subtype == pkt.chassis_id.SUB_MAC: + # Last ditch effort -- we'll hope the DPID was small enough + # to fit into an ethernet address + if len(lldph.tlvs[0].id) == 6: + try: + s = lldph.tlvs[0].id + originatorDPID = struct.unpack("!Q",'\x00\x00' + s)[0] + except: + pass + + if originatorDPID == None: + return + + if originatorDPID not in core.openflow.connections: + return + + # Get port number from port TLV + if lldph.tlvs[1].subtype != pkt.port_id.SUB_PORT: + return + originatorPort = None + if lldph.tlvs[1].id.isdigit(): + # We expect it to be a decimal value + originatorPort = int(lldph.tlvs[1].id) + elif len(lldph.tlvs[1].id) == 2: + # Maybe it's a 16 bit port number... + try: + originatorPort = struct.unpack("!H", lldph.tlvs[1].id)[0] + except: + pass + if originatorPort is None: + return + + if (event.dpid, event.port) == (originatorDPID, originatorPort): + return + + link = Discovery.Link(originatorDPID, originatorPort, event.dpid, + event.port) + + if link not in self.adjacency: + self.adjacency[link] = time.time() + self.raiseEventNoErrors(LinkEvent, True, link) + else: + # Just update timestamp + self.adjacency[link] = time.time() + + self.send_to_pyretic(['link',originatorDPID, originatorPort, event.dpid, event.port]) + return # Probably nobody else needs this event + + + def _handle_PacketIn(self, event): + packet = event.parsed + if packet.type == ethernet.LLDP_TYPE: + self.handle_lldp(packet,event) + return + elif packet.type == 0x86dd: # IGNORE IPV6 + return + + # if self.show_traces: + if self.trace != None: + self.packetno += 1 + self.trace("\n-------- POX/OF RECV %d ---------------" % self.packetno, timeStamped=True) + self.trace(event.connection) + self.trace(event.ofp) + self.trace("port\t%s" % event.port) + self.trace("data\t%s" % packetlib.ethernet(event.data)) + self.trace("dpid\t%s" % event.dpid) + self.trace("") + + received = self.packet_from_network(event.dpid, event.ofp.in_port, event.data) + self.send_to_pyretic(['packet',received]) + + +def launch(ip='127.0.0.1', port=BACKEND_PORT): + + class asyncore_loop(threading.Thread): + def run(self): + asyncore.loop() + + POXClient(ip=ip, port=int(port)) + + if useThreading: + al = asyncore_loop() + al.start() + + else: + asyncore.loop() + + + + + diff --git a/of_client/pox_client.py b/of_client/pox_client.py index acbae750..f0e62aa0 100644 --- a/of_client/pox_client.py +++ b/of_client/pox_client.py @@ -40,6 +40,10 @@ from pyretic.backend.comm import * +import datetime +from pyretic.core.logger import simpleLogger + +OFCLIENTLOGLEVEL = 5 def inport_value_hack(outport): if outport > 1: @@ -53,6 +57,7 @@ class BackendChannel(asynchat.async_chat): """ def __init__(self, host, port, of_client): self.of_client = of_client + self.trace = of_client.trace self.received_data = [] asynchat.async_chat.__init__(self) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) @@ -106,6 +111,11 @@ def found_terminator(self): class POXClient(revent.EventMixin): # NOT **kwargs def __init__(self,show_traces=False,debug_packet_in=False,ip='127.0.0.1',port=BACKEND_PORT): + + logFileName = "%s.log" % __name__.split('.')[1] + logger = simpleLogger(baseName=logFileName, logLevel=OFCLIENTLOGLEVEL) + self.trace = logger.write + self.switches = {} self.show_traces = show_traces self.debug_packet_in = debug_packet_in @@ -353,11 +363,15 @@ def send_to_switch(self,packet): msg.data = self.packet_to_network(packet) msg.actions.append(of.ofp_action_output(port = outport)) - if self.show_traces: - print "========= POX/OF SEND ================" - print msg - print packetlib.ethernet(msg._get_data()) - print + #if self.show_traces: + if self.trace != None: + self.trace("========= POX/OF SEND ================") + self.trace(msg) + # If the following line is un-commented, 'pingall' fails + # Tested with BADIP2IP and BADIP3IP + + # self.trace(packetlib.ethernet(msg._get_data())) + self.trace("") ## HANDLE PACKETS SEND ON LINKS THAT HAVE TIMED OUT try: @@ -618,27 +632,29 @@ def _handle_PacketIn(self, event): elif packet.type == 0x86dd: # IGNORE IPV6 return - if self.show_traces: + # if self.show_traces: + if self.trace != None: self.packetno += 1 - print "-------- POX/OF RECV %d ---------------" % self.packetno - print event.connection - print event.ofp - print "port\t%s" % event.port - print "data\t%s" % packetlib.ethernet(event.data) - print "dpid\t%s" % event.dpid - print + self.trace("-------- POX/OF RECV %d ---------------" % self.packetno) + self.trace(event.connection) + self.trace(event.ofp) + self.trace("port\t%s" % event.port) + self.trace("data\t%s" % packetlib.ethernet(event.data)) + self.trace("dpid\t%s" % event.dpid) + self.trace("") received = self.packet_from_network(event.dpid, event.ofp.in_port, event.data) self.send_to_pyretic(['packet',received]) -def launch(): +def launch(ip='127.0.0.1', port=BACKEND_PORT): class asyncore_loop(threading.Thread): def run(self): asyncore.loop() - - POXClient() + + POXClient(ip=ip, port=int(port)) + al = asyncore_loop() al.start() diff --git a/pyretic.py b/pyretic.py index e74bc3e5..a7f9ba34 100755 --- a/pyretic.py +++ b/pyretic.py @@ -30,6 +30,8 @@ from pyretic.core.runtime import Runtime from pyretic.backend.backend import Backend +from pyretic.backend.comm import BACKEND_PORT + import sys import threading import signal @@ -38,6 +40,7 @@ from optparse import OptionParser import re import os +import datetime of_client = None @@ -79,16 +82,105 @@ def parseArgs(): op.add_option( '--verbosity', '-v', type='choice', choices=['low','normal','high'], default = 'low', help = '|'.join( ['quiet','high'] ) ) + op.add_option('--port', '-p', action = 'store', type = 'int', + dest = "listenPort", default= 6633, + help = 'set listenPort') + op.add_option('--backendPort', '-P', action = 'store', type = 'int', + dest = "backendPort", default= BACKEND_PORT, + help = 'set backendPort') + + localHost = '127.0.0.1' + + op.add_option('--listenIP', '-i', action = 'store', type = 'string', + dest = "listenIP", default= '0.0.0.0', + help = 'set listenIP') + op.add_option('--backendIP', '-I', action = 'store', type = 'string', + dest = "backendIP", default= localHost, + help = 'set backendIP') + + op.add_option('--client', '-c', action = 'store', type = 'string', + dest = "client", default= 'pox_client', + help = 'use this OF client') + + op.add_option('--logDirName', '-d', action = 'store', type = 'string', + dest = "logDirName", default= 'pyretic', + help = 'set log dir name') + + op.add_option('--logLevel', '-l', action = 'store', type = 'int', + dest = "logLevel", default= 100, + help = 'set log level') op.set_defaults(frontend_only=False,mode='reactive0') options, args = op.parse_args() return (op, options, args, kwargs_to_pass) +def getPaths(): + try: + output = subprocess.check_output('echo $PYTHONPATH',shell=True).strip() + + except: + print 'Error: Unable to obtain PYTHONPATH' + sys.exit(1) + + poxpath = None + pyreticpath = None + mininetpath = None + + for p in output.split(':'): + if re.match('.*pox/?$',p): + poxpath = os.path.abspath(p) + + elif re.match('.*pyretic/?$',p): + pyreticpath = os.path.abspath(p) + + elif re.match('.*mininet/?$',p): + mininetpath = os.path.abspath(p) + + # print("poxpath=%s pyreticpath=%s mininetpath=%s" %(poxpath, pyreticpath, mininetpath)) + + return (poxpath, pyreticpath, mininetpath) + +def emptyLogDir(logDir): + for the_file in os.listdir(logDir): + file_path = os.path.join(logDir, the_file) + + try: + if os.path.isfile(file_path): + os.unlink(file_path) + + except Exception, e: + print e + +def setLogDirPathName(pyreticpath, logDirName, logLevel): + + datetimeNow = datetime.datetime.now() + dateNow = datetimeNow.strftime("%Y-%m-%d") + timeNow = datetimeNow.strftime("%H:%M:%S.%f") + + pyreticLogsDir = "%s/%s" % (pyreticpath, "logs") + logDirPathName = "%s/%s/%s" % (pyreticLogsDir, dateNow, logDirName) + + # print("logDirPathName=%s logLevel=%s" % (logDirPathName, logLevel)) + + if not os.path.isdir(logDirPathName): + os.makedirs(logDirPathName) + + else: + emptyLogDir(logDirPathName) + + os.environ["PYRETICLOGDIR"] = logDirPathName + os.environ["PYRETICLOGLEVEL"] = str(logLevel) + def main(): global of_client (op, options, args, kwargs_to_pass) = parseArgs() + + (poxpath, pyreticpath, mininetpath) = getPaths() + + setLogDirPathName(pyreticpath, options.logDirName, options.logLevel) + if options.mode == 'i': options.mode = 'interpreted' elif options.mode == 'r0': @@ -116,28 +208,38 @@ def main(): sys.setrecursionlimit(1500) #INCREASE THIS IF "maximum recursion depth exceeded" - runtime = Runtime(Backend(),main,kwargs,options.mode,options.verbosity,False,False) + runtime = Runtime(Backend(ip=options.backendIP, port=options.backendPort),main,kwargs,options.mode,options.verbosity,False,False) + if not options.frontend_only: - try: - output = subprocess.check_output('echo $PYTHONPATH',shell=True).strip() - except: - print 'Error: Unable to obtain PYTHONPATH' - sys.exit(1) - poxpath = None - for p in output.split(':'): - if re.match('.*pox/?$',p): - poxpath = os.path.abspath(p) - break + #try: + # output = subprocess.check_output('echo $PYTHONPATH',shell=True).strip() + #except: + # print 'Error: Unable to obtain PYTHONPATH' + # sys.exit(1) + #poxpath = None + #for p in output.split(':'): + # if re.match('.*pox/?$',p): + # poxpath = os.path.abspath(p) + # break + if poxpath is None: print 'Error: pox not found in PYTHONPATH' sys.exit(1) pox_exec = os.path.join(poxpath,'pox.py') python=sys.executable - of_client = subprocess.Popen([python, - pox_exec, - 'of_client.pox_client' ], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - + + poxClient = 'of_client.%s' % options.client + backendIP = '--ip=%s' % options.backendIP + backendPort = '--port=%d' % options.backendPort + + OF = 'openflow.of_01' + listenIP = '--address=%s' % options.listenIP + listenPort = '--port=%d' % options.listenPort + + procCmd = [python, pox_exec, poxClient, backendIP, backendPort, OF, listenIP, listenPort] + + of_client = subprocess.Popen(procCmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + signal.signal(signal.SIGINT, signal_handler) signal.pause() diff --git a/pyretic/backend/BackendServer.py b/pyretic/backend/BackendServer.py new file mode 100644 index 00000000..26829d2a --- /dev/null +++ b/pyretic/backend/BackendServer.py @@ -0,0 +1,202 @@ +import threading +from tools.comm import * +from pyretic.core.runtime import ConcretePacket +from tools.logger import simpleLogger + +useThreading = True + +class BackendChannel(asynchat.async_chat): + """Handles echoing messages from a single client. + """ + + def __init__(self, server, sock): + self.debug = True + self.trace("BackendChannel.init: sock=%s" % sock) + self.server = server + self.received_data = [] + asynchat.async_chat.__init__(self, sock) + self.set_terminator(TERM_CHAR) + + return + + def trace(self, logLine, timeStamped=False): + if not self.debug: + return + + simpleLogger.geTracePyretic()(logLine, timeStamped) + + def collect_incoming_data(self, data): + """Read an incoming message from the client and put it into our outgoing queue.""" + self.trace("BackendChannel.collect_incoming_data: \ndata=%s\n" % data) + with self.server.channel_lock: + self.received_data.append(data) + + def found_terminator(self): + """The end of a command or message has been seen.""" + self.trace("BackendChannel.found_terminator\n") + self.processOFdata() + + def processSwitchData(self, msg): + self.trace("processSwitchData: msg=%s" % msg) + + if msg[1] == 'join': + if msg[3] == 'BEGIN': + self.server.runtime.handle_switch_join(msg[2]) + + elif msg[1] == 'part': + self.server.runtime.handle_switch_part(msg[2]) + + else: + print "ERROR: Bad switch event" + + def processPortData(self, msg): + self.trace("processPortData: msg=%s" % msg) + + if msg[1] == 'join': + self.server.runtime.handle_port_join(msg[2],msg[3],msg[4],msg[5]) + + elif msg[1] == 'mod': + self.server.runtime.handle_port_mod(msg[2],msg[3],msg[4],msg[5]) + + elif msg[1] == 'part': + self.server.runtime.handle_port_part(msg[2],msg[3]) + + else: + print "ERROR: Bad port event" + + def processLinkData(self, msg): + self.trace("processLinkData: msg=%s" % msg) + self.server.runtime.handle_link_update(msg[1],msg[2],msg[3],msg[4]) + + def processPacketData(self, msg): + packet = ConcretePacket(msg[1]) + self.trace("processPacketData: packet=%s" % packet) + self.server.runtime.handle_packet_in(packet) + + def processOFdata(self): + with self.server.channel_lock: + msg = deserialize(self.received_data) + + self.trace("BackendChannel.processOFdata: msg=%s\n" % msg, timeStamped=True) + + msg0 = msg[0] + + # USE DESERIALIZED MSG + if msg0 == 'switch': + self.processSwitchData(msg) + + elif msg0 == 'port': + self.processPortData(msg) + + elif msg0 == 'link': + self.processLinkData(msg) + + elif msg0 == 'packet': + self.processPacketData(msg) + + else: + print 'ERROR: Unknown msg from server %s' % msg + + return + +class BackendServer(asyncore.dispatcher): + """Receives connections and establishes handlers for each client. + """ + class asyncore_loop(threading.Thread): + def run(self): + asyncore.loop() + + def __init__(self, port): + self.debug = True + self.trace("BackendServer port=%s\n" % (port), timeStamped=True) + self.echoChannel = None + self.runtime = None + self.channel_lock = threading.Lock() + + asyncore.dispatcher.__init__(self) + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.bind(('', port)) + self.address = self.socket.getsockname() + self.listen(1) + + if useThreading: + self.al = self.asyncore_loop() + self.al.daemon = True + self.al.start() + + else: + asyncore.loop() + + return + + def trace(self, logLine, timeStamped=False): + if not self.debug: + return + + simpleLogger.geTracePyretic()(logLine, timeStamped) + + def send_packet(self,packet): + self.trace("Backend.send_packet: %s\n" % packet, timeStamped=True) + self.send_to_OF_client(['packet',packet]) + + def send_install(self,pred,action_list): + self.trace("Backend.send_install: pred=%s action_list=%s\n" % (pred,action_list), timeStamped=True) + self.send_to_OF_client(['install',pred,action_list]) + + def send_clear_all(self): + self.trace("Backend.send_clear_all\n", timeStamped=True) + self.send_to_OF_client(['clear_all']) + + def inject_discovery_packet(self,dpid, port): + self.trace("Backend.inject_discovery_packet: dpid=%s port=%s\n" % (dpid, port), timeStamped=True) + self.send_to_OF_client(['inject_discovery_packet',dpid,port]) + + def send_to_OF_client(self,msg): + self.trace("Backend.send_to_OF_client: msg=%s\n" % msg, timeStamped=True) + serialized_msg = serialize(msg) + + with self.channel_lock: + self.echoChannel.push(serialized_msg) + + def handle_accept(self): + # Called when a client connects to our socket + client_info = self.accept() + + self.echoChannel = BackendChannel(self, sock=client_info[0]) + + # We only want to deal with one client at a time, + # so close as soon as we set up the handler. + # Normally you would not do this and the server + # would run forever or until it received instructions + # to stop. + self.handle_close() + return + + def handle_close(self): + self.close() + + +def buildOptions(): + desc = ( 'async chat echo server' ) + usage = ( '%prog [options]\n' + '(type %prog -h for details)' ) + + from optparse import OptionParser + op = OptionParser( description=desc, usage=usage ) + + op.add_option('--port', '-p', action = 'store', type = 'int', + dest = "port", default= BACKEND_PORT, + help = 'set echo server port') + + options, args = op.parse_args() + + return (options, args) + +def main(): + (options, args) = buildOptions() + print("port=%s" % options.port) + server = BackendServer(options.port) + +if __name__ == "__main__": + main() + \ No newline at end of file diff --git a/pyretic/backend/backend.py b/pyretic/backend/backend.py index 813ffed7..316f2431 100644 --- a/pyretic/backend/backend.py +++ b/pyretic/backend/backend.py @@ -28,117 +28,165 @@ ################################################################################ import threading -from pyretic.backend.comm import * +from tools.comm import * from pyretic.core.runtime import ConcretePacket +from tools.logger import simpleLogger + +# self.trace = simpleLogger.geTracePyretic() class BackendServer(asyncore.dispatcher): - """Receives connections and establishes handlers for each backend. - """ - def __init__(self, backend, address): - asyncore.dispatcher.__init__(self) - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - self.set_reuse_addr() - self.bind(address) - self.address = self.socket.getsockname() - self.listen(1) - self.backend = backend - return - - def handle_accept(self): - # Called when a backend connects to our socket - backend_info = self.accept() - self.backend.backend_channel = BackendChannel(self.backend,sock=backend_info[0]) - # We only want to deal with one backend at a time, - # so close as soon as we set up the handler. - # Normally you would not do this and the server - # would run forever or until it received instructions - # to stop. - self.handle_close() - return - - def handle_close(self): - self.close() + """Receives connections and establishes handlers for each backend. + """ + def __init__(self, backend, address): + self.debug = True + self.trace("BackendServer backend=%s address=%s\n" % (backend, address) , timeStamped=True) + asyncore.dispatcher.__init__(self) + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.set_reuse_addr() + self.bind(address) + self.address = self.socket.getsockname() + self.listen(1) + self.backend = backend + return + + def trace(self, logLine, timeStamped=False): + if not self.debug: + return + + simpleLogger.geTracePyretic()(logLine, timeStamped) + + def handle_accept(self): + self.trace("BackendServer.handle_accept\n", timeStamped=True) + # Called when a backend connects to our socket + backend_info = self.accept() + self.backend.backend_channel = BackendChannel(self.backend,sock=backend_info[0]) + # We only want to deal with one backend at a time, + # so close as soon as we set up the handler. + # Normally you would not do this and the server + # would run forever or until it received instructions + # to stop. + self.handle_close() + return + + def handle_close(self): + self.trace("BackendServer.handle_close\n", timeStamped=True) + self.close() class BackendChannel(asynchat.async_chat): - """Handles echoing messages from a single backend. - """ - def __init__(self, backend, sock): - self.backend = backend - self.received_data = [] - asynchat.async_chat.__init__(self, sock) - self.set_terminator(TERM_CHAR) - return - - def collect_incoming_data(self, data): - """Read an incoming message from the backend and put it into our outgoing queue.""" - with self.backend.channel_lock: - self.received_data.append(data) - - def found_terminator(self): - """The end of a command or message has been seen.""" - with self.backend.channel_lock: - msg = deserialize(self.received_data) - - # USE DESERIALIZED MSG - if msg[0] == 'switch': - if msg[1] == 'join': - if msg[3] == 'BEGIN': - self.backend.runtime.handle_switch_join(msg[2]) - elif msg[1] == 'part': - self.backend.runtime.handle_switch_part(msg[2]) - else: - print "ERROR: Bad switch event" - elif msg[0] == 'port': - if msg[1] == 'join': - self.backend.runtime.handle_port_join(msg[2],msg[3],msg[4],msg[5]) - elif msg[1] == 'mod': - self.backend.runtime.handle_port_mod(msg[2],msg[3],msg[4],msg[5]) - elif msg[1] == 'part': - self.backend.runtime.handle_port_part(msg[2],msg[3]) - else: - print "ERROR: Bad port event" - elif msg[0] == 'link': - self.backend.runtime.handle_link_update(msg[1],msg[2],msg[3],msg[4]) - elif msg[0] == 'packet': - packet = ConcretePacket(msg[1]) - self.backend.runtime.handle_packet_in(packet) - else: - print 'ERROR: Unknown msg from backend %s' % msg - return + """Handles echoing messages from a single backend. + """ + def __init__(self, backend, sock): + self.debug = True + self.trace("BackendChannel backend=%s sock=%s\n" % (backend, sock), timeStamped=True) + self.backend = backend + self.received_data = [] + asynchat.async_chat.__init__(self, sock) + self.set_terminator(TERM_CHAR) + return + + def trace(self, logLine, timeStamped=False): + if not self.debug: + return + + simpleLogger.geTracePyretic()(logLine, timeStamped) + + def collect_incoming_data(self, data): + """Read an incoming message from the backend and put it into our outgoing queue.""" + self.trace("BackendChannel.collect_incoming_data\n", timeStamped=True) + with self.backend.channel_lock: + self.received_data.append(data) + + def found_terminator(self): + """The end of a command or message has been seen.""" + with self.backend.channel_lock: + msg = deserialize(self.received_data) + + self.trace("BackendChannel.found_terminator: msg=%s\n" % msg, timeStamped=True) + + # USE DESERIALIZED MSG + if msg[0] == 'switch': + if msg[1] == 'join': + if msg[3] == 'BEGIN': + self.backend.runtime.handle_switch_join(msg[2]) + + elif msg[1] == 'part': + self.backend.runtime.handle_switch_part(msg[2]) + + else: + print "ERROR: Bad switch event" + + elif msg[0] == 'port': + if msg[1] == 'join': + self.backend.runtime.handle_port_join(msg[2],msg[3],msg[4],msg[5]) + + elif msg[1] == 'mod': + self.backend.runtime.handle_port_mod(msg[2],msg[3],msg[4],msg[5]) + + elif msg[1] == 'part': + self.backend.runtime.handle_port_part(msg[2],msg[3]) + + else: + print "ERROR: Bad port event" + + elif msg[0] == 'link': + self.backend.runtime.handle_link_update(msg[1],msg[2],msg[3],msg[4]) + + elif msg[0] == 'packet': + packet = ConcretePacket(msg[1]) + self.backend.runtime.handle_packet_in(packet) + + else: + print 'ERROR: Unknown msg from backend %s' % msg + + return class Backend(object): - class asyncore_loop(threading.Thread): - def run(self): - asyncore.loop() + class asyncore_loop(threading.Thread): + def run(self): + asyncore.loop() - def __init__(self): - self.backend_channel = None - self.runtime = None - self.channel_lock = threading.Lock() + def __init__(self, ip='127.0.0.1', port=BACKEND_PORT): + self.debug = True + self.trace("Backend ip=%s port=%s\n" % (ip, port), timeStamped=True) + self.backend_channel = None + self.runtime = None + self.channel_lock = threading.Lock() - address = ('localhost', BACKEND_PORT) # USE KNOWN PORT - self.backend_server = BackendServer(self,address) - - self.al = self.asyncore_loop() - self.al.daemon = True - self.al.start() - - def send_packet(self,packet): - self.send_to_OF_client(['packet',packet]) + address = (ip, port) # USE ANY IP, ANY PORT + self.backend_server = BackendServer(self,address) - def send_install(self,pred,action_list): - self.send_to_OF_client(['install',pred,action_list]) - - def send_clear_all(self): - self.send_to_OF_client(['clear_all']) + self.al = self.asyncore_loop() + self.al.daemon = True + self.al.start() + + def trace(self, logLine, timeStamped=False): + if not self.debug: + return + + simpleLogger.geTracePyretic()(logLine, timeStamped) - def inject_discovery_packet(self,dpid, port): - self.send_to_OF_client(['inject_discovery_packet',dpid,port]) + def send_packet(self,packet): + self.trace("Backend.send_packet: %s\n" % packet, timeStamped=True) + self.send_to_OF_client(['packet',packet]) - def send_to_OF_client(self,msg): - serialized_msg = serialize(msg) - with self.channel_lock: - self.backend_channel.push(serialized_msg) + def send_install(self,pred,action_list): + self.trace("Backend.send_install: pred=%s action_list=%s\n" % (pred,action_list), timeStamped=True) + self.send_to_OF_client(['install',pred,action_list]) + + def send_clear_all(self): + self.trace("Backend.send_clear_all\n", timeStamped=True) + self.send_to_OF_client(['clear_all']) + + def inject_discovery_packet(self,dpid, port): + self.trace("Backend.inject_discovery_packet: dpid=%s port=%s\n" % (dpid, port), timeStamped=True) + self.send_to_OF_client(['inject_discovery_packet',dpid,port]) + + def send_to_OF_client(self,msg): + self.trace("Backend.send_to_OF_client: msg=%s\n" % msg, timeStamped=True) + serialized_msg = serialize(msg) + + with self.channel_lock: + self.backend_channel.push(serialized_msg) diff --git a/pyretic/core/language.py b/pyretic/core/language.py index d5f75737..71866e2f 100644 --- a/pyretic/core/language.py +++ b/pyretic/core/language.py @@ -39,6 +39,16 @@ from pyretic.core.network import * from pyretic.core.util import frozendict, singleton +import datetime +import os +from tools.logger import simpleLogger + +useTraceFile = True +MATCHLOGLEVEL = 3 +MATCHPREFIXLOGLEVEL = 4 + +traceMatch = None +traceMatchIP = None ################################################################################ # Matching # @@ -66,6 +76,8 @@ def __repr__(self): class PrefixMatch(object): """Pattern type for IP prefix match""" def __init__(self, pattern): + #self.trace = simpleLogger.geTracePyretic() + self.masklen = 32 if isinstance(pattern, IP): # IP OBJECT self.pattern = pattern @@ -75,7 +87,8 @@ def __init__(self, pattern): if len(parts) == 2: self.masklen = int(parts[1]) self.prefix = self.pattern.to_bits()[:self.masklen] - + #self.trace("pattern=%s prefix=%s" % (self.pattern, self.prefix)) + def match(self, other): """Match by checking prefix equality""" return self.prefix == other.to_bits()[:self.masklen] @@ -92,6 +105,7 @@ def __repr__(self): else: return "%s/%d" % (repr(self.pattern),self.masklen) + ################################################################################ # Determine how each field will be matched # ################################################################################ @@ -107,7 +121,6 @@ def field_patterntype(field): register_field("srcip", PrefixMatch) register_field("dstip", PrefixMatch) - ################################################################################ # Policy Language # ################################################################################ @@ -234,8 +247,11 @@ def __repr__(self): class match(PrimitivePolicy): """A set of field matches on a packet (one per field).""" + ### init : List (String * FieldVal) -> List KeywordArg -> unit def __init__(self, *args, **kwargs): + #self.trace = simpleLogger.geTracePyretic() + init_map = {} for (k, v) in dict(*args, **kwargs).iteritems(): if v is not None: @@ -245,6 +261,7 @@ def __init__(self, *args, **kwargs): else: init_map[k] = None self.map = util.frozendict(init_map) + #self.trace("patterns to match: %s\n" % self.map, True) # hph super(match,self).__init__() ### hash : unit -> int @@ -259,14 +276,19 @@ def __eq__(self, other): return False def eval(self, pkt): + #self.trace("match eval pkt:\n%s\n" % {pkt}, True) # hph + for field, pattern in self.map.iteritems(): v = pkt.get_stack(field) + #self.trace("match eval: pattern=%s field=%s v=%s" % (pattern, field, v)) + if v: if pattern is None or not pattern.match(v[0]): return set() else: if pattern is not None: return set() + #self.trace("\nmatch eval return pkt\n") # hph return {pkt} def __repr__(self): diff --git a/pyretic/core/runtime.py b/pyretic/core/runtime.py index dadc5a54..707b00f0 100644 --- a/pyretic/core/runtime.py +++ b/pyretic/core/runtime.py @@ -31,6 +31,7 @@ import pyretic.core.util as util from pyretic.core.language import * from pyretic.core.network import * +from tools.logger import simpleLogger import threading try: @@ -44,7 +45,10 @@ class Runtime(object): def __init__(self, backend, main, kwargs, mode='interpreted', verbosity='normal', - show_traces=False, debug_packet_in=False): + show_traces=False, debug_packet_in=False): + + self.debug = True + #self.trace = simpleLogger.geTracePyretic() self.network = ConcreteNetwork(self) self.prev_network = self.network.copy() self.policy = main(**kwargs) @@ -88,6 +92,12 @@ def find_dynamic_sub_pols(policy,recursive_pols_seen): self.threads = set() self.in_update_network = False + def trace(self, logLine, timeStamped=False): + if not self.debug: + return + + simpleLogger.geTracePyretic()(logLine, timeStamped) + def update_network(self): if self.network.topology != self.prev_network.topology: self.in_update_network = True @@ -256,13 +266,13 @@ def reactive0(self,in_pkt,out_pkts,eval_trace): rule = self.match_on_all_fields_rule(in_pkt,out_pkts) if rule: self.install_rule(rule) - if self.verbosity == 'high': + if self.verbosity == 'high' or True: from datetime import datetime - print str(datetime.now()), - print " | install rule" - print rule[0] - print rule[1] - + self.trace(str(datetime.now())) + self.trace(" | install rule") + self.trace(rule[0]) + self.trace(rule[1]) + self.trace("") def handle_packet_in(self, concrete_pkt): pyretic_pkt = self.concrete2pyretic(concrete_pkt) @@ -286,13 +296,13 @@ def handle_packet_in(self, concrete_pkt): type, value, tb = sys.exc_info() traceback.print_exc() debugger.post_mortem(tb) - if self.show_traces: - print "<<<<<<<<< RECV <<<<<<<<<<<<<<<<<<<<<<<<<<" - print util.repr_plus([pyretic_pkt], sep="\n\n") - print - print ">>>>>>>>> SEND >>>>>>>>>>>>>>>>>>>>>>>>>>" - print util.repr_plus(output, sep="\n\n") - print + if self.show_traces or True: + self.trace("<<<<<<<<< RECV <<<<<<<<<<<<<<<<<<<<<<<<<<", timeStamped=True) + self.trace(util.repr_plus([pyretic_pkt], sep="\n\n")) + self.trace("") + self.trace(">>>>>>>>> SEND >>>>>>>>>>>>>>>>>>>>>>>>>>", timeStamped=True) + self.trace(util.repr_plus(output, sep="\n\n")) + self.trace("") map(self.send_packet,output) def pyretic2concrete(self,packet): @@ -344,11 +354,12 @@ def install_rule(self,(pred,action_list)): def clear_all(self): self.backend.send_clear_all() - if self.verbosity == 'high': + if self.verbosity == 'high' or True: from datetime import datetime - print str(datetime.now()), - print " | clear_all" - + self.trace(str(datetime.now())) + self.trace(" | clear_all") + self.trace("") + def inject_discovery_packet(self,dpid, port): self.backend.inject_discovery_packet(dpid,port) @@ -399,9 +410,10 @@ def extended_values_from(packet): class ConcretePacket(dict): pass -DEBUG_TOPO_DISCOVERY = False +DEBUG_TOPO_DISCOVERY = True class ConcreteNetwork(Network): def __init__(self,runtime=None): + self.trace = simpleLogger.geTracePyretic() super(ConcreteNetwork,self).__init__() self.runtime = runtime @@ -419,11 +431,11 @@ def inject_discovery_packet(self, dpid, port_no): self.runtime.inject_discovery_packet(dpid, port_no) def handle_switch_join(self, switch): - if DEBUG_TOPO_DISCOVERY: print "handle_switch_joins" + if DEBUG_TOPO_DISCOVERY: self.trace("handle_switch_joins\n", timeStamped=True) ## PROBABLY SHOULD CHECK TO SEE IF SWITCH ALREADY IN TOPOLOGY self.topology.add_switch(switch) - print "OpenFlow switch %s connected" % switch - if DEBUG_TOPO_DISCOVERY: print self.topology + print("OpenFlow switch %s connected" % switch) + if DEBUG_TOPO_DISCOVERY: self.trace("\n%s\n" % self.topology, timeStamped=True) self.update_network() def remove_associated_link(self,location): @@ -443,41 +455,41 @@ def remove_associated_link(self,location): self.topology.node[location.switch]["ports"][location.port_no].linked_to = None def handle_switch_part(self, switch): - print "OpenFlow switch %s disconnected" % switch - if DEBUG_TOPO_DISCOVERY: print "handle_switch_parts" + print("OpenFlow switch %s disconnected" % switch) + if DEBUG_TOPO_DISCOVERY: self.trace("handle_switch_parts\n", timeStamped=True) # REMOVE ALL ASSOCIATED LINKS for port_no in self.topology.node[switch]["ports"].keys(): self.remove_associated_link(Location(switch,port_no)) self.topology.remove_node(switch) - if DEBUG_TOPO_DISCOVERY: print self.topology + if DEBUG_TOPO_DISCOVERY: self.trace("\n%s\n" % self.topology, timeStamped=True) self.update_network() def handle_port_join(self, switch, port_no, config, status): - if DEBUG_TOPO_DISCOVERY: print "handle_port_joins %s:%s:%s:%s" % (switch, port_no, config, status) + if DEBUG_TOPO_DISCOVERY: self.trace("handle_port_joins %s:%s:%s:%s\n" % (switch, port_no, config, status), timeStamped=True) self.topology.add_port(switch,port_no,config,status) if config or status: self.inject_discovery_packet(switch,port_no) - if DEBUG_TOPO_DISCOVERY: print self.topology + if DEBUG_TOPO_DISCOVERY: self.trace("\n%s\n" % self.topology, timeStamped=True) self.update_network() def handle_port_part(self, switch, port_no): - if DEBUG_TOPO_DISCOVERY: print "handle_port_parts" + if DEBUG_TOPO_DISCOVERY: self.trace("handle_port_parts\n", timeStamped=True) try: self.remove_associated_link(Location(switch,port_no)) del self.topology.node[switch]["ports"][port_no] - if DEBUG_TOPO_DISCOVERY: print self.topology + if DEBUG_TOPO_DISCOVERY: self.trace("\n%s\n" % self.topology, timeStamped=True) self.update_network() except KeyError: pass # THE SWITCH HAS ALREADY BEEN REMOVED BY handle_switch_parts def handle_port_mod(self, switch, port_no, config, status): - if DEBUG_TOPO_DISCOVERY: print "handle_port_mods %s:%s:%s:%s" % (switch, port_no, config, status) + if DEBUG_TOPO_DISCOVERY: self.trace("handle_port_mods %s:%s:%s:%s\n" % (switch, port_no, config, status), timeStamped=True) # GET PREV VALUES try: prev_config = self.topology.node[switch]["ports"][port_no].config prev_status = self.topology.node[switch]["ports"][port_no].status except KeyError: - print "KeyError CASE!!!!!!!!" + print("KeyError CASE!!!!!!!!") self.port_down(switch, port_no) return @@ -495,28 +507,28 @@ def handle_port_mod(self, switch, port_no, config, status): self.port_up(switch, port_no) def port_up(self, switch, port_no): - if DEBUG_TOPO_DISCOVERY: print "port_up %s:%s" % (switch,port_no) + if DEBUG_TOPO_DISCOVERY: self.trace("port_up %s:%s\n" % (switch,port_no), timeStamped=True) self.inject_discovery_packet(switch,port_no) - if DEBUG_TOPO_DISCOVERY: print self.topology + if DEBUG_TOPO_DISCOVERY: self.trace("\n%s\n" % self.topology, timeStamped=True) self.update_network() def port_down(self, switch, port_no, double_check=False): - if DEBUG_TOPO_DISCOVERY: print "port_down %s:%s:double_check=%s" % (switch,port_no,double_check) + if DEBUG_TOPO_DISCOVERY: self.trace("port_down %s:%s:double_check=%s\n" % (switch,port_no,double_check), timeStamped=True) try: self.remove_associated_link(Location(switch,port_no)) - if DEBUG_TOPO_DISCOVERY: print self.topology + if DEBUG_TOPO_DISCOVERY: self.trace("\n%s\n" % self.topology, timeStamped=True) self.update_network() if double_check: self.inject_discovery_packet(switch,port_no) except KeyError: pass # THE SWITCH HAS ALREADY BEEN REMOVED BY handle_switch_parts def handle_link_update(self, s1, p_no1, s2, p_no2): - if DEBUG_TOPO_DISCOVERY: print "handle_link_updates" + if DEBUG_TOPO_DISCOVERY: self.trace("handle_link_updates\n", timeStamped=True) try: p1 = self.topology.node[s1]["ports"][p_no1] p2 = self.topology.node[s2]["ports"][p_no2] except KeyError: - if DEBUG_TOPO_DISCOVERY: print "node doesn't yet exist" + if DEBUG_TOPO_DISCOVERY: self.trace("node doesn't yet exist\n", timeStamped=True) return # at least one of these ports isn't (yet) in the topology # LINK ALREADY EXISTS @@ -526,7 +538,7 @@ def handle_link_update(self, s1, p_no1, s2, p_no2): # LINK ON SAME PORT PAIR if link[s1] == p_no1 and link[s2] == p_no2: if p1.possibly_up() and p2.possibly_up(): - if DEBUG_TOPO_DISCOVERY: print "nothing to do" + if DEBUG_TOPO_DISCOVERY: self.trace("nothing to do\n", timeStamped=True) return # NOTHING TO DO else: # ELSE RAISE AN ERROR - SOMETHING WEIRD IS HAPPENING raise RuntimeError('Link update w/ bad port status %s,%s' % (p1,p2)) @@ -549,6 +561,6 @@ def handle_link_update(self, s1, p_no1, s2, p_no2): self.topology.add_edge(s1, s2, {s1: p_no1, s2: p_no2}) # IF REACHED, WE'VE REMOVED AN EDGE, OR ADDED ONE, OR BOTH - if DEBUG_TOPO_DISCOVERY: print self.topology + if DEBUG_TOPO_DISCOVERY: self.trace("\n%s\n" % self.topology, timeStamped=True) self.update_network() diff --git a/pyretic/examples/fw0.py b/pyretic/examples/fw0.py new file mode 100644 index 00000000..99cedb2e --- /dev/null +++ b/pyretic/examples/fw0.py @@ -0,0 +1,132 @@ +################################################################################ +# SETUP # +# ------------------------------------------------------------------- # +# mininet: mininet.sh --topo=clique,5,5 (or other single subnet network) # +# test: pingall. odd nodes should reach odd nodes w/ higher IP, # +# likewise for even ones # +# controller prints one message # +# e.g., "punching hole for reverse traffic [IP1]:[IP2]" # +# for each pair where IP1> hub + +def askyn(prompt, retries=4, complaint='Yes or no, please!'): + while True: + ok = raw_input(prompt) + + if ok in ('y', 'ye', 'yes'): + return True + + if ok in ('n', 'no', 'nop', 'nope'): + return False + + retries = retries - 1 + + if retries < 0: + raise IOError('Time out after %s retries' % retries) + + print complaint + +def getOptions(): + global useIP + global usestr + global verbose + + print("\nPlease answer (y)es or (n)o for the following options;") + + useIP = askyn(" useIP (y/n): ") + usestr = askyn(" usestr (y/n): ") + verbose = askyn(" verbose (y/n): ") + + +def main(): + + getOptions() + + return static_firewall_example() diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyretic/backend/comm.py b/tools/comm.py similarity index 95% rename from pyretic/backend/comm.py rename to tools/comm.py index e577e89e..3ecda73a 100644 --- a/pyretic/backend/comm.py +++ b/tools/comm.py @@ -32,17 +32,20 @@ import socket import json +from tools.logger import simpleLogger BACKEND_PORT=41414 TERM_CHAR='\n' def serialize(msg): + # simpleLogger.geTracePyretic()("comm.serialize", timeStamped=True) jsonable_msg = to_jsonable_format(msg) jsoned_msg = json.dumps(jsonable_msg) serialized_msg = jsoned_msg + TERM_CHAR return serialized_msg def deserialize(serialized_msgs): + # simpleLogger.geTracePyretic()("comm.deserialize", timeStamped=True) def json2python(item): if isinstance(item, unicode): return item.encode('ascii') diff --git a/tools/logger.py b/tools/logger.py new file mode 100644 index 00000000..d92c57ea --- /dev/null +++ b/tools/logger.py @@ -0,0 +1,73 @@ +import datetime +import os + +tracePyretic = None +STATICLOGLEVEL = 50 +useTraceFile = True +BASENAME = "pyretic.log" + +class simpleLogger(object): + @staticmethod + def geTracePyretic(): + global tracePyretic + + if useTraceFile: + baseName = BASENAME + + else: + baseName = None + + if tracePyretic == None: + tracePyretic = simpleLogger(baseName=baseName, logLevel=STATICLOGLEVEL).write + + return(tracePyretic) + + def __init__(self, baseName=None, logLevel=0, timeStamped=True): + + logFile = None + LOGLEVEL = int(os.environ["PYRETICLOGLEVEL"]) + doLog = logLevel >= LOGLEVEL + self.doLog = doLog + + if baseName != None: + + if not doLog: + return + + datetimeNow = datetime.datetime.now() + timeNow = datetimeNow.strftime("%H:%M:%S.%f") + + logDirPathName = os.environ["PYRETICLOGDIR"].split(".")[0] + logFileName = baseName + + if timeStamped: + logFileName = "%s-%s" % (timeNow, baseName) + + logFilePathName = "%s/%s" % (logDirPathName, logFileName) + + logFile = open(logFilePathName, 'w', 1) + logFile.write("Created: %s\n" % timeNow) + + self.logFile = logFile + + def write(self, logLine, timeStamped=False): + + if not self.doLog: + return + + if self.logFile == None: + from time import sleep + sleep(0.1) + print(logLine) + + else: + if timeStamped: + timeNow = datetime.datetime.now().strftime("%H:%M:%S.%f") + thisLine = "\n%s: %s" % (timeNow, logLine) + + else: + thisLine = "\n%s" % logLine + + self.logFile.write(thisLine) + +