class VPN(Resource): def __init__(self, name, vs): Resource.__init__(self, name, "net.es.netshell.api.Resource") self.vpnService = vs self.vid = 0 self.name = name self.pops = {} # pop name -> pop self.vpnsites = {} # site name -> site self.vpnsitevlans = {} # site name -> vlan tag for attachment self.popflows = {} # pop name -> (FlowHandle) self.siteflows = {} # site name -> (FlowHandle) self.hostflows = {} # host name -> (FlowHandle) self.entryfanoutflows = {} # host name -> FlowHandle self.exitfanoutflows = { } # exit pop name -> (entry pop name, FlowHandle) self.hostsites = {} # MAC address / host -> site name self.priority = "low" self.meter = 3 self.lock = threading.Lock() self.mat = MAT(sid=self.vpnService.sid, vid=self.vid) self.logger = logging.getLogger("VPN") self.logger.setLevel(logging.INFO) self.setResourceType("VPN") def saveVPN(self): self.properties['vid'] = self.vid self.properties['priority'] = self.priority self.properties['meter'] = self.meter self.properties['mat'] = str(self.mat.serialize()) if not 'pops' in self.properties: self.properties['pops'] = {} for p in self.pops: self.properties['pops'][p] = p #self.properties['pops'] = tmp self.properties['vpnsites'] = str(self.vpnsites) self.properties['vpnsitevlans'] = str(self.vpnsitevlans) self.properties['entryfanoutflows'] = str(self.entryfanoutflows) self.properties['exitfanoutflows'] = str(self.exitfanoutflows) self.properties['hostsites'] = str(self.hostsites) MultiPointVPNServiceFactory.getVpnService().saveResource(self) def loadVPN(self, mpvpn): stored = mpvpn.loadResource(self.getResourceName()) mapResource(obj=self, resource=stored) self.name = self.getResourceName() self.vid = self.properties['vid'] self.priority = self.properties['priority'] self.meter = self.properties['meter'] self.pops = {} for (n, p) in self.properties['pops'].items(): self.pops[n] = mpvpn.topology.loadResource(n) self.vpnsites = eval(self.properties['vpnsites']) self.vpnsitevlans = eval(self.properties['vpnsitevlans']) self.exitfanoutflows = eval(self.properties['exitfanoutflows']) self.entryfanoutflows = eval(self.properties['entryfanoutflows']) self.hostsites = eval(self.properties['hostsites']) self.mat = MAT.deserialize(self.properties['mat']) self.vpnService = vpnService # global def interconnect(self, site1, site2): # XXX core topo! """ Given two sites, return a GRI to be used for VPN traffic between them. Assumes that the first applicable GRI is the one to be used. :param site1: :param site2: :return: Link (Resource) """ pop1 = site1['pop'] pop2 = site2['pop'] return self.interconnectpops(pop1, pop2) def interconnectpops(self, pop1, pop2): # XXX core topo! """ Given two POPs, return a GRI to be used to VPN traffic between them. Assumes that the first applicable GRI is the one to be used. :param pop1: Name of POP :param pop2: Name of POP :return: Link (Resource) """ vc = MultiPointVPNServiceFactory.getVpnService( ).coretopology.loadResource(pop1 + "--" + pop2) return vc def makeentryfanoutflows(self, localpop, hostmac, hostvlan, hostsite): # Create entry fanout flow on the software switch. This flow fans out traffic # from the host/site to other hosts/sites on the same POP, as well as to all the # other POPs. # XXX need to re-run this part if we add another host/site to this POP or add # another POP forwards = [] broadcast_mat = "FF:FF:FF:FF:FF:FF" if MultiPointVPNServiceFactory.getVpnService().properties['mat']: broadcast_mat = self.generateBroadcastMAC() # Locate all other sites on this POP. For each of these, make a forwarding entry to it. for (othersitename, othersite) in self.vpnsites.items(): if othersite[ 'pop'] == localpop.resourceName and othersitename != hostsite[ 'name']: fwd = SdnControllerClientL2Forward() fwd.outPort = "0" # to be filled in by connectentryfanout fwd.vlan = int(self.vpnsitevlans[othersitename]) fwd.dstMac = broadcast_mat forwards.append(fwd) # Locate other POPs for (otherpopname, otherpop) in self.pops.items(): if otherpopname != localpop.resourceName: vc = self.interconnectpops(localpop.resourceName, otherpopname) core = Container.fromAnchor(localpop.properties['CoreRouter']) coreresname = core.resourceName (corename, coredom, coreport, corevlan) = getvcnode(vc, coreresname) fwd = SdnControllerClientL2Forward() fwd.outPort = "0" # to be filled in by connectentryfanout fwd.vlan = int(corevlan) fwd.dstMac = broadcast_mat forwards.append(fwd) # If we've already made an entry fanout flow for this host, then delete it. if hostmac in self.entryfanoutflows: oldfh = self.entryfanoutflows[hostmac] if oldfh.isValid(): deleteforward(oldfh) del self.entryfanoutflows[hostmac] oldfh.invalidate() fh = connectentryfanoutmac(localpop=localpop, hostmac=hostmac, hostvlan=hostvlan, forwards=forwards, meter=self.meter, mac=broadcast_mat) if fh != None: self.entryfanoutflows[hostmac] = fh return fh def addpop(self, pop): rc = True # See if we've already added the POP if pop.resourceName in self.pops: return False with self.lock: fhlist = [] # List of FlowHandles for this POP # We need to make sure that meter(s) are set correctly on the switches in the POP. # In particular we need to do this on Corsas before pushing flows to it that reference # any of the meters we use here. This code is a bit of a hard-coded hack, but it'll # have to do until we can figure out what's the desired behavior. Note that we can # set a meter multiple times. It is however a requirement that the driver needs to # have a set a meter before a flow references it; in particular we cannot use an # external mechanism (e.g. CLI) to set the meter and then try to have the driver # push a flow that references it. sw = Container.fromAnchor(pop.properties['HwSwitch']) meter = self.meter rc = setmeter(sw, meter, 0, 0, 0, 0) # if not rc: # Iterate over the existing POPs to set up entries to handle broadcast traffic # between the POP being added and the existing POPs. These all go in the # hardware switches. broadcastmac = "FF:FF:FF:FF:FF:FF" broadcastmac_mat = broadcastmac if MultiPointVPNServiceFactory.getVpnService().properties['mat']: broadcastmac_mat = self.generateBroadcastMAC() for (remotepopname, remotepop) in self.pops.items(): vc = self.interconnectpops(pop.getResourceName(), remotepop.getResourceName()) fhs = swconnect(pop, remotepop, broadcastmac_mat, vc, self.meter) self.popflows[remotepop.getResourceName()].extend(fhs) fhlist.extend(fhs) # This POP is now added. self.pops[pop.resourceName] = pop self.popflows[pop.resourceName] = fhlist # Regenerate entry fanout flows for various hosts because they need to learn # about a new POP for (hostmac, hostsitename) in self.hostsites.items(): hostsite = self.vpnsites[hostsitename] hostvlan = self.vpnsitevlans[hostsitename] localpop = self.pops[hostsite['pop']] fh = self.makeentryfanoutflows(localpop=localpop, hostmac=hostmac, hostvlan=hostvlan, hostsite=hostsite) if fh != None: self.hostflows[hostmac].append(fh) # print "addpop ending with pop flow handles", self.popflows[pop.name] return rc def delpop(self, pop): # Ideally we would undo the meter setting that was done in addpop. But at this point # we don't have a mechanism for handling the fact that a meter might be in use by # flows for multiple VPNs. If we indiscriminantly delete a meter, we might blow away # flows in use by some other, unrelated VPNs. This is solely an issue on the Corsas # at this point. if pop.resourceName in self.popflows.keys(): self.deletefhs(self.popflows[pop.resourceName]) del self.popflows[pop.resourceName] del self.pops[pop.resourceName] # Regenerate entry fanout flows for various hosts because they don't need to fanout # to this POP anymore for (hostmac, hostsitename) in self.hostsites.items(): hostsite = self.vpnsites[hostsitename] hostvlan = self.vpnsitevlans[hostsitename] localpop = self.pops[hostsite['pop']] fh = self.makeentryfanoutflows(localpop=localpop, hostmac=hostmac, hostvlan=hostvlan, hostsite=hostsite) if fh != None: self.hostflows[hostmac].append(fh) return True def addsite(self, site, vlan): # If we've already added this site, don't do it again. # Note this is actually not a real requirement. In theory it should be possible # to put multiple attachments to a single site, on different VLANs. However # our implementation doesn't support that at this point, primarily because # we have some assumptions that each site only attaches once to a VPN. This check # here is mostly to avoid violating these assumptions and getting us in trouble. if site['name'] in self.vpnsites: return False # Make sure the pop to which the site is attached is already a part of the VPN. # In theory we could implicitly add a POP whenever we add a site that needs it. if site['pop'].lower() not in self.pops: return False self.vpnsites[site['name']] = site self.vpnsitevlans[site['name']] = vlan self.siteflows[site['name']] = [] broadcast_mat = "FF:FF:FF:FF:FF:FF" if MultiPointVPNServiceFactory.getVpnService().properties['mat']: broadcast_mat = self.generateBroadcastMAC() # Add flows to get broadcast traffic between the site and the software switch. pop = self.pops[site['pop']] fhs = connecthostbroadcast(localpop=pop, hwport_tosite=site['hwport'], sitevlan=vlan, meter=self.meter, broadcast_rewritemac=broadcast_mat) if fhs == None: return False self.siteflows[site['name']].extend(fhs) # Create exit fanout flows on the software switch. This flow fans out traffic # from other POPs, to hosts/sites on this POP. # XXX need to re-run this part if we add another host/site to this POP or # add another POP # XXX Note that for a given port, the exit fanout flows are all identical, # except that their match VLAN numbers are different, corresponding to the VLAN # of the core OSCARS circuits. We might possibly be able to collapse these down # to a single flow rule if we don't try to match on the VLAN tag. It's not clear # if we want to do this or not, there might be some security and/or reliability # implications to doing this change. localpop = pop localpopname = localpop.resourceName forwards = [] if localpopname in self.exitfanoutflows: exitfanoutflows = self.exitfanoutflows[localpopname] else: exitfanoutflows = {} self.exitfanoutflows[localpopname] = exitfanoutflows # Locate all sites on this POP, including the one we're adding now. # Be ready to forward to their for (site2name, site2) in self.vpnsites.items(): if (site2['pop'] == site['pop']): fwd = SdnControllerClientL2Forward() fwd.outPort = "0" # to be filled in by connectexitfanout fwd.vlan = int(self.vpnsitevlans[site2name]) fwd.dstMac = broadcast_mat forwards.append(fwd) # Iterate over source POPs for (srcpopname, srcpop) in self.pops.items(): if srcpopname != localpopname: # Get the VLAN coming from the core for this source POP vc = self.interconnectpops(localpopname, srcpopname) core = Container.fromAnchor(localpop.properties['CoreRouter']) coreresname = core.resourceName (corename, coredom, coreport, corevlan) = getvcnode(vc, coreresname) # If we already made an exit fanout flow for this source POP, delete it if srcpopname in exitfanoutflows: oldfh = exitfanoutflows[srcpopname] if oldfh.isValid(): deleteforward(oldfh) del exitfanoutflows[srcpopname] oldfh.invalidate() fh = connectexitfanout(localpop=pop, corevlan=corevlan, forwards=forwards, meter=self.meter, mac=broadcast_mat) if fh != None: exitfanoutflows[srcpopname] = fh self.popflows[srcpopname].append(fh) # Regenerate entry fanout flows for various hosts on this POP because they just # learned a new site. for (hostmac, hostsitename) in self.hostsites.items(): hostsite = self.vpnsites[hostsitename] if hostsite['pop'] == localpopname: hostvlan = self.vpnsitevlans[hostsitename] localpop = self.pops[hostsite['pop']] fh = self.makeentryfanoutflows(localpop=localpop, hostmac=hostmac, hostvlan=hostvlan, hostsite=hostsite) if fh != None: self.hostflows[hostmac].append(fh) return True def delsite(self, site): localpop = self.pops[site['pop']] localpopname = localpop.resourceName if site['name'] in self.siteflows.keys(): self.deletefhs(self.siteflows[site['name']]) del self.siteflows[site['name']] del self.vpnsites[site['name']] del self.vpnsitevlans[site['name']] # Regenerate entry fanout flows for various hosts on this POP because they need # to forget this site for (hostmac, hostsitename) in self.hostsites.items(): hostsite = self.vpnsites[hostsitename] if hostsite['pop'] == localpopname: hostvlan = self.vpnsitevlans[hostsitename] localpop = self.pops[hostsite['pop']] fh = self.makeentryfanoutflows(localpop=localpop, hostmac=hostmac, hostvlan=hostvlan, hostsite=hostsite) if fh != None: self.hostflows[hostmac].append(fh) return True def generateMAC2(self, mac): """ Translate a MAC address. :param mac: MAC address to translate (intended to be a string but can be any valid type to MACaddress constructor :return: String """ return str(self.mat.translate(mac)) def generateBroadcastMAC(self): return str(self.mat.translate("FF:FF:FF:FF:FF:FF")) def deletefhs(self, fhs): for f in fhs: if f.isValid(): deleteforward(f) f.invalidate() def addhostbymac(self, hostsite, hostmac): """ Add a host by its MAC address :param hostsite: site descriptor structure (see global sites) :param hostmac: MAC address (string) :return: boolean True if successful """ # Normalize MAC address to lower-case hostmac = hostmac.lower() with self.lock: # See if the host already exists if hostmac in self.hostsites.keys(): # See if we're trying to add to the same site. If so that might indicate a problem # with flows, since we shouldn't be getting PACKET_IN events for a host after it's # been added. # # If the host shows up at a different site, maybe it moved? Not sure how to handle this # but for now take this as a failure. We could try to delete the host from the old site # and add it at the new site. # # XXX This condition might change after we add NFV support if hostsite['name'] == self.hostsites[hostmac]: self.logger.info("Host " + hostmac + " already exists at site " + hostsite['name']) return True else: self.logger.info("Host " + hostmac + " moved from site " + self.hostsites[hostmac] + " to site " + hostsite['name']) return False vlan = self.vpnsitevlans[ hostsite['name']] # get VLAN from the site attachment localpopname = hostsite['pop'] localpop = self.pops[localpopname] host_mat = None broadcast_mat = "FF:FF:FF:FF:FF:FF" vpnService = MultiPointVPNServiceFactory.getVpnService() if vpnService.properties['mat']: host_mat = self.generateMAC2(hostmac) self.hostflows[hostmac] = [] # Install flows for unicast traffic # Iterate over all other hosts in the VPN, set up pairwise flows to new host for (remotehostmac, remotesitename) in self.hostsites.items(): # If the host is at this site, don't need to do anything if remotesitename == hostsite['name']: continue remotesite = sites[remotesitename] vc = self.interconnect(hostsite, remotesite) remotevlan = self.vpnsitevlans[remotesite['name']] remotepop = self.pops[remotesite['pop']] remotehost_mat = None if vpnService.properties['mat']: remotehost_mat = self.generateMAC2(remotehostmac) fhlist = [] # Add flows coming from other site/host fhs = connectgrimac(topology=vpnService.topology, hostmac=hostmac, siteport=hostsite['hwport'], sitevlan=vlan, sitepop=localpop, remotesiteport=remotesite['hwport'], remotesitevlan=remotevlan, vc=vc, meter=self.meter, host_rewritemac=host_mat) if fhs != None: fhlist.extend(fhs) # Add flows going to other site/host fhs = connectgrimac(topology=vpnService.topology, hostmac=remotehostmac, siteport=remotesite['hwport'], sitevlan=remotevlan, sitepop=remotepop, remotesiteport=hostsite['hwport'], remotesitevlan=vlan, vc=vc, meter=self.meter, host_rewritemac=remotehost_mat) if fhs != None: fhlist.extend(fhs) self.hostflows[hostmac].extend(fhlist) self.hostflows[remotehostmac].extend(fhlist) fh = self.makeentryfanoutflows(localpop, hostmac, vlan, hostsite) if fh != None: self.hostsites[hostmac] = hostsite['name'] self.hostflows[hostmac].append(fh) # print "addhost ending with host flow handles", self.hostflows[host['name']] return True def delhostbymac(self, hostmac): # Normalize MAC address to lower-case hostmac = hostmac.lower() if hostmac in self.hostsites.keys(): del self.hostsites[hostmac] else: return False if hostmac in self.hostflows.keys(): self.deletefhs(self.hostflows[hostmac]) del self.hostflows[hostmac] return True def setpriority(self, priority): """ Set the priority for this VPN's flows. Can be either "high" or low, but in the current implementation this must be set before any POPs, sites, or hosts are added. """ self.priority = priority if priority == 'high': self.meter = 5 else: self.meter = 3 def getpriority(self): return self.priority
class VPN(Resource): def __init__(self,name,vs): Resource.__init__(self,name,"net.es.netshell.api.Resource") self.vpnService = vs self.vid = 0 self.name = name self.pops = {} # pop name -> pop self.vpnsites = {} # site name -> site self.vpnsitevlans = {} # site name -> vlan tag for attachment self.popflows = {} # pop name -> (FlowHandle) self.siteflows = {} # site name -> (FlowHandle) self.hostflows = {} # host name -> (FlowHandle) self.entryfanoutflows = {} # host name -> FlowHandle self.exitfanoutflows = {} # exit pop name -> (entry pop name, FlowHandle) self.hostsites = {} # MAC address / host -> site name self.priority = "low" self.meter = 3 self.lock = threading.Lock() self.mat = MAT(sid=self.vpnService.sid, vid=self.vid) self.logger = logging.getLogger("VPN") self.logger.setLevel(logging.INFO) self.setResourceType("VPN") def saveVPN(self): self.properties['vid'] = self. vid self.properties['priority'] = self.priority self.properties['meter'] = self.meter self.properties['mat'] = str(self.mat.serialize()) if not 'pops' in self.properties: self.properties['pops'] = {} for p in self.pops: self.properties['pops'][p] = p #self.properties['pops'] = tmp self.properties['vpnsites'] = str(self.vpnsites) self.properties['vpnsitevlans'] = str(self.vpnsitevlans) self.properties['entryfanoutflows'] = str(self.entryfanoutflows) self.properties['exitfanoutflows'] = str(self.exitfanoutflows) self.properties['hostsites'] = str(self.hostsites) MultiPointVPNServiceFactory.getVpnService().saveResource(self) def loadVPN(self,mpvpn): stored = mpvpn.loadResource(self.getResourceName()) mapResource(obj=self,resource=stored) self.name = self.getResourceName() self.vid = self.properties['vid'] self.priority = self.properties['priority'] self.meter = self.properties['meter'] self.pops={} for (n,p) in self.properties['pops'].items(): self.pops[n] = mpvpn.topology.loadResource(n) self.vpnsites = eval (self.properties['vpnsites']) self.vpnsitevlans = eval (self.properties['vpnsitevlans']) self.exitfanoutflows = eval (self.properties['exitfanoutflows']) self.entryfanoutflows = eval (self.properties['entryfanoutflows']) self.hostsites = eval (self.properties['hostsites']) self.mat = MAT.deserialize(self.properties['mat']) self.vpnService = vpnService # global def interconnect(self, site1,site2): # XXX core topo! """ Given two sites, return a GRI to be used for VPN traffic between them. Assumes that the first applicable GRI is the one to be used. :param site1: :param site2: :return: Link (Resource) """ pop1 = site1['pop'] pop2 = site2['pop'] return self.interconnectpops(pop1,pop2) def interconnectpops(self, pop1,pop2): # XXX core topo! """ Given two POPs, return a GRI to be used to VPN traffic between them. Assumes that the first applicable GRI is the one to be used. :param pop1: Name of POP :param pop2: Name of POP :return: Link (Resource) """ vc = MultiPointVPNServiceFactory.getVpnService().coretopology.loadResource(pop1 + "--" + pop2) return vc def makeentryfanoutflows(self, localpop, hostmac, hostvlan, hostsite): # Create entry fanout flow on the software switch. This flow fans out traffic # from the host/site to other hosts/sites on the same POP, as well as to all the # other POPs. # XXX need to re-run this part if we add another host/site to this POP or add # another POP forwards = [] broadcast_mat = "FF:FF:FF:FF:FF:FF" if MultiPointVPNServiceFactory.getVpnService().properties['mat']: broadcast_mat = self.generateBroadcastMAC() # Locate all other sites on this POP. For each of these, make a forwarding entry to it. for (othersitename, othersite) in self.vpnsites.items(): if othersite['pop'] == localpop.resourceName and othersitename != hostsite['name']: fwd = SdnControllerClientL2Forward() fwd.outPort = "0" # to be filled in by connectentryfanout fwd.vlan = int(self.vpnsitevlans[othersitename]) fwd.dstMac = broadcast_mat forwards.append(fwd) # Locate other POPs for (otherpopname, otherpop) in self.pops.items(): if otherpopname != localpop.resourceName: vc = self.interconnectpops(localpop.resourceName, otherpopname) core = Container.fromAnchor(localpop.properties['CoreRouter']) coreresname = core.resourceName (corename, coredom, coreport, corevlan) = getvcnode(vc, coreresname) fwd = SdnControllerClientL2Forward() fwd.outPort = "0" # to be filled in by connectentryfanout fwd.vlan = int(corevlan) fwd.dstMac = broadcast_mat forwards.append(fwd) # If we've already made an entry fanout flow for this host, then delete it. if hostmac in self.entryfanoutflows: oldfh = self.entryfanoutflows[hostmac] if oldfh.isValid(): deleteforward(oldfh) del self.entryfanoutflows[hostmac] oldfh.invalidate() fh = connectentryfanoutmac(localpop= localpop, hostmac= hostmac, hostvlan= hostvlan, forwards= forwards, meter= self.meter, mac= broadcast_mat) if fh != None: self.entryfanoutflows[hostmac] = fh return fh def addpop(self,pop): rc = True # See if we've already added the POP if pop.resourceName in self.pops: return False with self.lock: fhlist = [] # List of FlowHandles for this POP # We need to make sure that meter(s) are set correctly on the switches in the POP. # In particular we need to do this on Corsas before pushing flows to it that reference # any of the meters we use here. This code is a bit of a hard-coded hack, but it'll # have to do until we can figure out what's the desired behavior. Note that we can # set a meter multiple times. It is however a requirement that the driver needs to # have a set a meter before a flow references it; in particular we cannot use an # external mechanism (e.g. CLI) to set the meter and then try to have the driver # push a flow that references it. sw = Container.fromAnchor(pop.properties['HwSwitch']) meter = self.meter rc = setmeter(sw, meter, 0, 0, 0, 0) # if not rc: # Iterate over the existing POPs to set up entries to handle broadcast traffic # between the POP being added and the existing POPs. These all go in the # hardware switches. broadcastmac = "FF:FF:FF:FF:FF:FF" broadcastmac_mat = broadcastmac if MultiPointVPNServiceFactory.getVpnService().properties['mat']: broadcastmac_mat = self.generateBroadcastMAC() for (remotepopname, remotepop) in self.pops.items(): vc = self.interconnectpops(pop.getResourceName(), remotepop.getResourceName()) fhs = swconnect(pop, remotepop, broadcastmac_mat, vc, self.meter) self.popflows[remotepop.getResourceName()].extend(fhs) fhlist.extend(fhs) # This POP is now added. self.pops[pop.resourceName] = pop self.popflows[pop.resourceName] = fhlist # Regenerate entry fanout flows for various hosts because they need to learn # about a new POP for (hostmac, hostsitename) in self.hostsites.items(): hostsite = self.vpnsites[hostsitename] hostvlan = self.vpnsitevlans[hostsitename] localpop = self.pops[hostsite['pop']] fh = self.makeentryfanoutflows(localpop= localpop, hostmac= hostmac, hostvlan= hostvlan, hostsite= hostsite) if fh != None: self.hostflows[hostmac].append(fh) # print "addpop ending with pop flow handles", self.popflows[pop.name] return rc def delpop(self,pop): # Ideally we would undo the meter setting that was done in addpop. But at this point # we don't have a mechanism for handling the fact that a meter might be in use by # flows for multiple VPNs. If we indiscriminantly delete a meter, we might blow away # flows in use by some other, unrelated VPNs. This is solely an issue on the Corsas # at this point. if pop.resourceName in self.popflows.keys(): self.deletefhs(self.popflows[pop.resourceName]) del self.popflows[pop.resourceName] del self.pops[pop.resourceName] # Regenerate entry fanout flows for various hosts because they don't need to fanout # to this POP anymore for (hostmac, hostsitename) in self.hostsites.items(): hostsite = self.vpnsites[hostsitename] hostvlan = self.vpnsitevlans[hostsitename] localpop = self.pops[hostsite['pop']] fh = self.makeentryfanoutflows(localpop= localpop, hostmac= hostmac, hostvlan= hostvlan, hostsite= hostsite) if fh != None: self.hostflows[hostmac].append(fh) return True def addsite(self,site,vlan): # If we've already added this site, don't do it again. # Note this is actually not a real requirement. In theory it should be possible # to put multiple attachments to a single site, on different VLANs. However # our implementation doesn't support that at this point, primarily because # we have some assumptions that each site only attaches once to a VPN. This check # here is mostly to avoid violating these assumptions and getting us in trouble. if site['name'] in self.vpnsites: return False # Make sure the pop to which the site is attached is already a part of the VPN. # In theory we could implicitly add a POP whenever we add a site that needs it. if site['pop'].lower() not in self.pops: return False self.vpnsites[site['name']] = site self.vpnsitevlans[site['name']] = vlan self.siteflows[site['name']] = [] broadcast_mat = "FF:FF:FF:FF:FF:FF" if MultiPointVPNServiceFactory.getVpnService().properties['mat']: broadcast_mat = self.generateBroadcastMAC() # Add flows to get broadcast traffic between the site and the software switch. pop = self.pops[site['pop']] fhs = connecthostbroadcast(localpop= pop, hwport_tosite= site['hwport'], sitevlan= vlan, meter= self.meter, broadcast_rewritemac= broadcast_mat) if fhs == None: return False self.siteflows[site['name']].extend(fhs) # Create exit fanout flows on the software switch. This flow fans out traffic # from other POPs, to hosts/sites on this POP. # XXX need to re-run this part if we add another host/site to this POP or # add another POP # XXX Note that for a given port, the exit fanout flows are all identical, # except that their match VLAN numbers are different, corresponding to the VLAN # of the core OSCARS circuits. We might possibly be able to collapse these down # to a single flow rule if we don't try to match on the VLAN tag. It's not clear # if we want to do this or not, there might be some security and/or reliability # implications to doing this change. localpop = pop localpopname = localpop.resourceName forwards = [] if localpopname in self.exitfanoutflows: exitfanoutflows = self.exitfanoutflows[localpopname] else: exitfanoutflows = {} self.exitfanoutflows[localpopname] = exitfanoutflows # Locate all sites on this POP, including the one we're adding now. # Be ready to forward to their for (site2name, site2) in self.vpnsites.items(): if (site2['pop'] == site['pop']): fwd = SdnControllerClientL2Forward() fwd.outPort = "0" # to be filled in by connectexitfanout fwd.vlan = int(self.vpnsitevlans[site2name]) fwd.dstMac = broadcast_mat forwards.append(fwd) # Iterate over source POPs for (srcpopname, srcpop) in self.pops.items(): if srcpopname != localpopname: # Get the VLAN coming from the core for this source POP vc = self.interconnectpops(localpopname, srcpopname) core = Container.fromAnchor(localpop.properties['CoreRouter']) coreresname = core.resourceName (corename, coredom, coreport, corevlan) = getvcnode(vc, coreresname) # If we already made an exit fanout flow for this source POP, delete it if srcpopname in exitfanoutflows: oldfh = exitfanoutflows[srcpopname] if oldfh.isValid(): deleteforward(oldfh) del exitfanoutflows[srcpopname] oldfh.invalidate() fh = connectexitfanout(localpop= pop, corevlan= corevlan, forwards= forwards, meter= self.meter, mac= broadcast_mat) if fh != None: exitfanoutflows[srcpopname] = fh self.popflows[srcpopname].append(fh) # Regenerate entry fanout flows for various hosts on this POP because they just # learned a new site. for (hostmac, hostsitename) in self.hostsites.items(): hostsite = self.vpnsites[hostsitename] if hostsite['pop'] == localpopname: hostvlan = self.vpnsitevlans[hostsitename] localpop = self.pops[hostsite['pop']] fh = self.makeentryfanoutflows(localpop= localpop, hostmac= hostmac, hostvlan= hostvlan, hostsite= hostsite) if fh != None: self.hostflows[hostmac].append(fh) return True def delsite(self,site): localpop = self.pops[site['pop']] localpopname = localpop.resourceName if site['name'] in self.siteflows.keys(): self.deletefhs(self.siteflows[site['name']]) del self.siteflows[site['name']] del self.vpnsites[site['name']] del self.vpnsitevlans[site['name']] # Regenerate entry fanout flows for various hosts on this POP because they need # to forget this site for (hostmac, hostsitename) in self.hostsites.items(): hostsite = self.vpnsites[hostsitename] if hostsite['pop'] == localpopname: hostvlan = self.vpnsitevlans[hostsitename] localpop = self.pops[hostsite['pop']] fh = self.makeentryfanoutflows(localpop= localpop, hostmac= hostmac, hostvlan= hostvlan, hostsite= hostsite) if fh != None: self.hostflows[hostmac].append(fh) return True def generateMAC2(self,mac): """ Translate a MAC address. :param mac: MAC address to translate (intended to be a string but can be any valid type to MACaddress constructor :return: String """ return str(self.mat.translate(mac)) def generateBroadcastMAC(self): return str(self.mat.translate("FF:FF:FF:FF:FF:FF")) def deletefhs(self, fhs): for f in fhs: if f.isValid(): deleteforward(f) f.invalidate() def addhostbymac(self,hostsite,hostmac): """ Add a host by its MAC address :param hostsite: site descriptor structure (see global sites) :param hostmac: MAC address (string) :return: boolean True if successful """ # Normalize MAC address to lower-case hostmac = hostmac.lower() with self.lock: # See if the host already exists if hostmac in self.hostsites.keys(): # See if we're trying to add to the same site. If so that might indicate a problem # with flows, since we shouldn't be getting PACKET_IN events for a host after it's # been added. # # If the host shows up at a different site, maybe it moved? Not sure how to handle this # but for now take this as a failure. We could try to delete the host from the old site # and add it at the new site. # # XXX This condition might change after we add NFV support if hostsite['name'] == self.hostsites[hostmac]: self.logger.info("Host " + hostmac + " already exists at site " + hostsite['name']) return True else: self.logger.info("Host " + hostmac + " moved from site " + self.hostsites[hostmac] + " to site " + hostsite['name']) return False vlan = self.vpnsitevlans[hostsite['name']] # get VLAN from the site attachment localpopname = hostsite['pop'] localpop = self.pops[localpopname] host_mat = None broadcast_mat = "FF:FF:FF:FF:FF:FF" vpnService = MultiPointVPNServiceFactory.getVpnService(); if vpnService.properties['mat']: host_mat = self.generateMAC2(hostmac) self.hostflows[hostmac] = [] # Install flows for unicast traffic # Iterate over all other hosts in the VPN, set up pairwise flows to new host for (remotehostmac, remotesitename) in self.hostsites.items(): # If the host is at this site, don't need to do anything if remotesitename == hostsite['name']: continue remotesite = sites[remotesitename] vc = self.interconnect(hostsite, remotesite) remotevlan = self.vpnsitevlans[remotesite['name']] remotepop = self.pops[remotesite['pop']] remotehost_mat = None if vpnService.properties['mat']: remotehost_mat = self.generateMAC2(remotehostmac) fhlist = [] # Add flows coming from other site/host fhs = connectgrimac(topology= vpnService.topology, hostmac= hostmac, siteport= hostsite['hwport'], sitevlan= vlan, sitepop= localpop, remotesiteport= remotesite['hwport'], remotesitevlan= remotevlan, vc= vc, meter= self.meter, host_rewritemac= host_mat) if fhs != None: fhlist.extend(fhs) # Add flows going to other site/host fhs = connectgrimac(topology= vpnService.topology, hostmac= remotehostmac, siteport= remotesite['hwport'], sitevlan= remotevlan, sitepop= remotepop, remotesiteport= hostsite['hwport'], remotesitevlan= vlan, vc= vc, meter= self.meter, host_rewritemac= remotehost_mat) if fhs != None: fhlist.extend(fhs) self.hostflows[hostmac].extend(fhlist) self.hostflows[remotehostmac].extend(fhlist) fh = self.makeentryfanoutflows(localpop, hostmac, vlan, hostsite) if fh != None: self.hostsites[hostmac] = hostsite['name'] self.hostflows[hostmac].append(fh) # print "addhost ending with host flow handles", self.hostflows[host['name']] return True def delhostbymac(self,hostmac): # Normalize MAC address to lower-case hostmac = hostmac.lower() if hostmac in self.hostsites.keys(): del self.hostsites[hostmac] else: return False if hostmac in self.hostflows.keys(): self.deletefhs(self.hostflows[hostmac]) del self.hostflows[hostmac] return True def setpriority(self,priority): """ Set the priority for this VPN's flows. Can be either "high" or low, but in the current implementation this must be set before any POPs, sites, or hosts are added. """ self.priority = priority if priority == 'high': self.meter = 5 else: self.meter = 3 def getpriority(self): return self.priority