| # |
| # Copyright 2016-present Ciena Corporation |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| import json |
| import requests |
| import os,sys,time |
| from nose.tools import * |
| from scapy.all import * |
| from OnosCtrl import OnosCtrl |
| import fcntl, socket, struct |
| |
| def get_mac(iface = 'ovsbr0', pad = 4): |
| s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
| try: |
| info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', iface[:15])) |
| except: |
| info = ['0'] * 24 |
| s.close() |
| sep = '' |
| if pad == 0: |
| sep = ':' |
| return '0'*pad + sep.join(['%02x' %ord(char) for char in info[18:24]]) |
| |
| class OnosFlowCtrl: |
| |
| auth = ('karaf', 'karaf') |
| controller = os.getenv('ONOS_CONTROLLER_IP') or 'localhost' |
| cfg_url = 'http://%s:8181/onos/v1/flows/' %(controller) |
| |
| def __init__( self, |
| deviceId, |
| appId=0, |
| ingressPort="", |
| egressPort="", |
| ethType="", |
| ethSrc="", |
| ethDst="", |
| vlan="", |
| ipProto="", |
| ipSrc=(), |
| ipDst=(), |
| tcpSrc="", |
| tcpDst="", |
| udpDst="", |
| udpSrc="", |
| mpls="", |
| dscp="", |
| icmpv4_type="", |
| icmpv4_code="", |
| icmpv6_type="", |
| icmpv6_code="", |
| ipv6flow_label="", |
| ecn="", |
| ipv6_target="", |
| ipv6_sll="", |
| ipv6_tll="", |
| ipv6_extension=""): |
| self.deviceId = deviceId |
| self.appId = appId |
| self.ingressPort = ingressPort |
| self.egressPort = egressPort |
| self.ethType = ethType |
| self.ethSrc = ethSrc |
| self.ethDst = ethDst |
| self.vlan = vlan |
| self.ipProto = ipProto |
| self.ipSrc = ipSrc |
| self.ipDst = ipDst |
| self.tcpSrc = tcpSrc |
| self.tcpDst = tcpDst |
| self.udpDst = udpDst |
| self.udpSrc = udpSrc |
| self.mpls = mpls |
| self.dscp = dscp |
| self.icmpv4_type = icmpv4_type |
| self.icmpv4_code = icmpv4_code |
| self.icmpv6_type = icmpv6_type |
| self.icmpv6_code = icmpv6_code |
| self.ipv6flow_label = ipv6flow_label |
| self.ecn = ecn |
| self.ipv6_target = ipv6_target |
| self.ipv6_sll = ipv6_sll |
| self.ipv6_tll = ipv6_tll |
| self.ipv6_extension = ipv6_extension |
| |
| @classmethod |
| def get_flows(cls, device_id): |
| return OnosCtrl.get_flows(device_id) |
| |
| def addFlow(self): |
| """ |
| Description: |
| Creates a single flow in the specified device |
| Required: |
| * deviceId: id of the device |
| Optional: |
| * ingressPort: port ingress device |
| * egressPort: port of egress device |
| * ethType: specify ethType |
| * ethSrc: specify ethSrc ( i.e. src mac addr ) |
| * ethDst: specify ethDst ( i.e. dst mac addr ) |
| * ipProto: specify ip protocol |
| * ipSrc: specify ip source address with mask eg. ip#/24 |
| as a tuple (type, ip#) |
| * ipDst: specify ip destination address eg. ip#/24 |
| as a tuple (type, ip#) |
| * tcpSrc: specify tcp source port |
| * tcpDst: specify tcp destination port |
| Returns: |
| True for successful requests; |
| False for failure/error on requests |
| """ |
| flowJson = { "priority":100, |
| "isPermanent":"true", |
| "timeout":0, |
| "deviceId":self.deviceId, |
| "treatment":{"instructions":[]}, |
| "selector": {"criteria":[]}} |
| if self.appId: |
| flowJson[ "appId" ] = self.appId |
| |
| if self.egressPort: |
| flowJson[ 'treatment' ][ 'instructions' ].append( { |
| "type":"OUTPUT", |
| "port":self.egressPort } ) |
| if self.ingressPort: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":"IN_PORT", |
| "port":self.ingressPort } ) |
| if self.ethType: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":"ETH_TYPE", |
| "ethType":self.ethType } ) |
| if self.ethSrc: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":"ETH_SRC", |
| "mac":self.ethSrc } ) |
| if self.ethDst: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":"ETH_DST", |
| "mac":self.ethDst } ) |
| if self.vlan: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":"VLAN_VID", |
| "vlanId":self.vlan } ) |
| if self.mpls: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":"MPLS_LABEL", |
| "label":self.mpls } ) |
| if self.ipSrc: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":self.ipSrc[0], |
| "ip":self.ipSrc[1] } ) |
| if self.ipDst: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":self.ipDst[0], |
| "ip":self.ipDst[1] } ) |
| if self.tcpSrc: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":"TCP_SRC", |
| "tcpPort": self.tcpSrc } ) |
| if self.tcpDst: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":"TCP_DST", |
| "tcpPort": self.tcpDst } ) |
| if self.udpSrc: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":"UDP_SRC", |
| "udpPort": self.udpSrc } ) |
| if self.udpDst: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":"UDP_DST", |
| "udpPort": self.udpDst } ) |
| if self.ipProto: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":"IP_PROTO", |
| "protocol": self.ipProto } ) |
| if self.dscp: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":"IP_DSCP", |
| "ipDscp": self.dscp } ) |
| |
| if self.icmpv4_type: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":'ICMPV4_TYPE', |
| "icmpType":self.icmpv4_type } ) |
| |
| if self.icmpv6_type: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":'ICMPV6_TYPE', |
| "icmpv6Type":self.icmpv6_type } ) |
| |
| if self.icmpv4_code: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":'ICMPV4_CODE', |
| "icmpCode": self.icmpv4_code } ) |
| |
| if self.icmpv6_code: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":'ICMPV6_CODE', |
| "icmpv6Code": self.icmpv6_code } ) |
| |
| if self.ipv6flow_label: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":'IPV6_FLABEL', |
| "flowLabel": self.ipv6flow_label } ) |
| |
| if self.ecn: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":"IP_ECN", |
| "ipEcn": self.ecn } ) |
| |
| if self.ipv6_target: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":'IPV6_ND_TARGET', |
| "targetAddress": self.ipv6_target } ) |
| |
| if self.ipv6_sll: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":'IPV6_ND_SLL', |
| "mac": self.ipv6_sll } ) |
| |
| if self.ipv6_tll: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":'IPV6_ND_TLL', |
| "mac": self.ipv6_tll } ) |
| |
| |
| if self.ipv6_extension: |
| flowJson[ 'selector' ][ 'criteria' ].append( { |
| "type":'IPV6_EXTHDR', |
| "exthdrFlags": self.ipv6_extension } ) |
| |
| |
| |
| |
| return self.sendFlow( deviceId=self.deviceId, flowJson=flowJson) |
| |
| def removeFlow(self, deviceId, flowId): |
| """ |
| Description: |
| Remove specific device flow |
| Required: |
| str deviceId - id of the device |
| str flowId - id of the flow |
| Return: |
| Returns True if successfully deletes flows, otherwise False |
| """ |
| # NOTE: REST url requires the intent id to be in decimal form |
| query = self.cfg_url + str( deviceId ) + '/' + str( int( flowId ) ) |
| response = requests.delete(query, auth = self.auth) |
| if response: |
| if 200 <= response.status_code <= 299: |
| return True |
| else: |
| return False |
| |
| return True |
| |
| def findFlow(self, deviceId, **criterias): |
| flows = self.get_flows(deviceId) |
| match_keys = criterias.keys() |
| matches = len(match_keys) |
| num_matched = 0 |
| for f in flows: |
| criteria = f['selector']['criteria'] |
| for c in criteria: |
| if c['type'] not in match_keys: |
| continue |
| match_key, match_val = criterias.get(c['type']) |
| val = c[match_key] |
| if val == match_val: |
| num_matched += 1 |
| if num_matched == matches: |
| return f['id'] |
| return None |
| |
| def sendFlow(self, deviceId, flowJson): |
| """ |
| Description: |
| Sends a single flow to the specified device. This function exists |
| so you can bypass the addFLow driver and send your own custom flow. |
| Required: |
| * The flow in json |
| * the device id to add the flow to |
| Returns: |
| True for successful requests |
| False for error on requests; |
| """ |
| url = self.cfg_url + str(deviceId) |
| response = requests.post(url, auth = self.auth, data = json.dumps(flowJson) ) |
| if response.ok: |
| if response.status_code in [200, 201]: |
| log.info('Successfully POSTED flow for device %s' %str(deviceId)) |
| return True |
| else: |
| log.info('Post flow for device %s failed with status %d' %(str(deviceId), |
| response.status_code)) |
| return False |
| else: |
| log.error('Flow post request returned with status %d' %response.status_code) |
| |
| return False |
| |