1 # Copyright (C) 2014 SDN Hub
3 # Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.
4 # You may not use this file except in compliance with this License.
5 # You may obtain a copy of the License at
7 # http://www.gnu.org/licenses/gpl-3.0.txt
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17 from webob import Response
21 from ryu.base import app_manager
22 from ryu.controller import ofp_event
23 from ryu.controller import dpset
24 from ryu.controller.handler import MAIN_DISPATCHER
25 from ryu.controller.handler import set_ev_cls
26 from ryu.ofproto import ofproto_v1_0
27 from ryu.ofproto import ofproto_v1_3
28 from ryu.lib import ofctl_v1_0
29 from ryu.lib import ofctl_v1_3
30 from ryu.app.wsgi import ControllerBase, WSGIApplication
32 from ryu.ofproto import inet
34 LOG = logging.getLogger('ryu.app.sdnhub_apps.tap_rest')
38 ############# Configure tap
44 # POST /v1.0/tap/create
47 # DELETE /v1.0/tap/delete
52 if re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", x.lower()):
62 socket.inet_aton(y[0])
67 class TapController(ControllerBase):
68 def __init__(self, req, link, data, **config):
69 super(TapController, self).__init__(req, link, data, **config)
70 self.tap = data['tap']
71 self.tap.dpset = data['dpset']
73 def is_filter_data_valid(self, filter_data):
74 if 'sources' not in filter_data:
75 LOG.error('missing sources %s', filter_data)
78 for source in filter_data['sources']:
79 if 'dpid' not in source or 'port_no' not in source:
80 LOG.error('Invalid source description %s', source)
83 if 'sinks' not in filter_data:
84 LOG.error('missing sinks %s', filter_data)
87 for sink in filter_data['sinks']:
88 if 'dpid' not in sink or 'port_no' not in sink:
89 LOG.error('Invalid source description %s', sink)
92 if 'fields' in filter_data:
93 for key, val in filter_data['fields'].items():
94 if key == 'dl_src' or key == 'dl_dst' or key == 'dl_host':
95 if not is_mac_valid(val):
96 LOG.error('Invalid MAC address in filter field %s=%s', key, val)
98 if key == 'nw_src' or key == 'nw_dst' or key == 'nw_host':
99 if 'dl_type' not in filter_data['fields']:
100 LOG.error('Ethertype is not set, but IP fields specified')
102 if not is_ip_valid(val):
103 LOG.error('Invalid IP address in filter field %s=%s', key, val)
105 if key == 'tp_src' or key == 'tp_dst' or key == 'tp_port':
106 nw_proto = filter_data['fields']['nw_proto']
107 if nw_proto != inet.IPPROTO_TCP and nw_proto != inet.IPPROTO_UDP:
108 LOG.error('Non TCP/UDP packet specifies TP fields')
113 def create_tap(self, req, **_kwargs):
115 filter_data = eval(req.body)
117 if not self.is_filter_data_valid(filter_data):
118 return Response(status=400)
120 LOG.error('Invalid syntax %s', req.body)
121 return Response(status=400)
123 if self.tap.create_tap(filter_data):
124 return Response(status=200,content_type='application/json',
125 body=json.dumps({'status':'success'}))
127 LOG.error('Create tap failed')
128 return Response(status=501)
130 def delete_tap(self, req, **_kwargs):
132 filter_data = eval(req.body)
133 if not self.is_filter_data_valid(filter_data):
134 return Response(status=400)
136 LOG.error('Invalid syntax %s', req.body)
138 self.tap.delete_tap(filter_data)
139 return Response(status=200,content_type='application/json',
140 body=json.dumps({'status':'success'}))
142 class TapRestApi(app_manager.RyuApp):
143 OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION,
144 ofproto_v1_3.OFP_VERSION]
146 'dpset': dpset.DPSet,
147 'wsgi': WSGIApplication,
148 'tap': tap.StarterTap
151 def __init__(self, *args, **kwargs):
152 super(TapRestApi, self).__init__(*args, **kwargs)
153 self.dpset = kwargs['dpset']
155 wsgi = kwargs['wsgi']
158 self.data['dpset'] = self.dpset
159 self.data['waiters'] = self.waiters
160 self.data['tap'] = tap
162 wsgi.registory['TapController'] = self.data
165 mapper.connect('tap', '/v1.0/tap/create',
166 controller=TapController, action='create_tap',
167 conditions=dict(method=['POST']))
169 mapper.connect('tap', '/v1.0/tap/delete',
170 controller=TapController, action='delete_tap',
171 conditions=dict(method=['POST']))