1#! /usr/bin/env python
2
3from __future__ import print_function
4
5import getopt
6import os
7import re
8import six
9from six.moves import socketserver
10import subprocess
11import sys
12import tempfile
13import threading
14
15from ryu.ofproto import ofproto_parser
16from ryu.ofproto import ofproto_v1_0
17from ryu.ofproto import ofproto_v1_0_parser
18from ryu.ofproto import ofproto_v1_5
19from ryu.ofproto import ofproto_v1_5_parser
20from ryu.ofproto import ofproto_protocol
21
22if six.PY3:
23    TimeoutExpired = subprocess.TimeoutExpired
24else:
25    # As python2 doesn't have timeout for subprocess.call,
26    # this script may hang.
27    TimeoutExpired = None
28
29STD_MATCH = [
30    'in_port=43981',
31    'dl_vlan=999',
32    'dl_dst=aa:bb:cc:99:88:77',
33    'dl_type=0x0800',  # ETH_TYPE_IP
34    'nw_dst=192.168.2.1',
35    'tun_src=192.168.2.3',
36    'tun_dst=192.168.2.4',
37    'tun_id=50000']
38
39MESSAGES = [
40    {'name': 'action_learn',
41     'versions': [4],
42     'cmd': 'add-flow',
43     'args': ['table=2',
44              'importance=39032'] + STD_MATCH + [
45                  'actions=strip_vlan,mod_nw_dst:192.168.2.9,' +
46                  'learn(table=99,priority=1,hard_timeout=300,' +
47                  'OXM_OF_VLAN_VID[0..11],' +
48                  'OXM_OF_ETH_DST[]=OXM_OF_ETH_SRC[],' +
49                  'load:0->OXM_OF_VLAN_VID[],' +
50                  'load:OXM_OF_TUNNEL_ID[]->OXM_OF_TUNNEL_ID[],' +
51                  'output:OXM_OF_IN_PORT[]),goto_table:100']},
52    {'name': 'match_conj',
53     'versions': [4],
54     'cmd': 'mod-flows',
55     'args': ['table=3',
56              'cookie=0x123456789abcdef0/0xffffffffffffffff',
57              'dl_vlan=1234',
58              'conj_id=0xabcdef',
59              'actions=strip_vlan,goto_table:100']},
60    {'name': 'match_pkt_mark',
61     'versions': [4],
62     'cmd': 'mod-flows',
63     'args': ['table=3',
64              'cookie=0x123456789abcdef0/0xffffffffffffffff',
65              'dl_vlan=1234',
66              'pkt_mark=54321',
67              'actions=strip_vlan,goto_table:100']},
68    {'name': 'match_pkt_mark_masked',
69     'versions': [4],
70     'cmd': 'mod-flows',
71     'args': ['table=3',
72              'cookie=0x123456789abcdef0/0xffffffffffffffff',
73              'dl_vlan=1234',
74              'pkt_mark=0xd431/0xffff',
75              'actions=strip_vlan,goto_table:100']},
76    {'name': 'action_conjunction',
77     'versions': [4],
78     'cmd': 'mod-flows',
79     'args': (['table=2',
80               'cookie=0x123456789abcdef0/0xffffffffffffffff'] +
81              STD_MATCH +
82              ['actions=conjunction(0xabcdef,1/2)'])},
83    {'name': 'match_load_nx_register',
84     'versions': [4],
85     'cmd': 'mod-flows',
86     'args': ['table=3',
87              'cookie=0x123456789abcdef0/0xffffffffffffffff',
88              'reg0=0x1234',
89              'reg5=0xabcd/0xffff',
90              'actions=load:0xdeadbee->NXM_NX_REG0[4..31]']},
91    {'name': 'match_move_nx_register',
92     'versions': [4],
93     'cmd': 'mod-flows',
94     'args': ['table=3',
95              'cookie=0x123456789abcdef0/0xffffffffffffffff',
96              'reg0=0x1234',
97              'reg5=0xabcd/0xffff',
98              'actions=move:NXM_NX_REG0[10..15]->NXM_NX_REG1[0..5]']},
99    {'name': 'action_resubmit',
100     'versions': [4],
101     'cmd': 'add-flow',
102     'args': (['table=3',
103               'importance=39032'] +
104              STD_MATCH +
105              ['actions=resubmit(1234,99)'])},
106    {'name': 'action_ct',
107     'versions': [4],
108     'cmd': 'add-flow',
109     'args': (['table=3,',
110               'importance=39032'] +
111              ['dl_type=0x0800,ct_state=-trk'] +
112              ['actions=ct(table=4,zone=NXM_NX_REG0[4..31])'])},
113    {'name': 'action_ct_exec',
114     'versions': [4],
115     'cmd': 'add-flow',
116     'args': (['table=3,',
117               'importance=39032'] +
118              ['dl_type=0x0800,ct_state=+trk+est'] +
119              ['actions=ct(commit,exec(set_field:0x654321->ct_mark))'])},
120    {'name': 'action_ct_nat',
121     'versions': [4],
122     'cmd': 'add-flow',
123     'args': (['table=3,',
124               'importance=39032'] +
125              ['dl_type=0x0800'] +
126              ['actions=ct(commit,nat(src=10.1.12.0-10.1.13.255:1-1023)'])},
127    {'name': 'action_ct_nat_v6',
128     'versions': [4],
129     'cmd': 'add-flow',
130     'args': (['table=3,',
131               'importance=39032'] +
132              ['dl_type=0x86dd'] +
133              ['actions=ct(commit,nat(dst=2001:1::1-2001:1::ffff)'])},
134    {'name': 'action_ct_clear',
135     'versions': [4],
136     'cmd': 'add-flow',
137     'args': (['table=3,',
138               'importance=39032'] +
139              ['dl_type=0x0800,ct_state=+trk'] +
140              ['actions=ct_clear'])},
141    {'name': 'action_note',
142     'versions': [4],
143     'cmd': 'add-flow',
144     'args': (['priority=100'] +
145              ['actions=note:04.05.06.07.00.00'])},
146    {'name': 'action_controller',
147     'versions': [4],
148     'cmd': 'add-flow',
149     'args': (['priority=100'] +
150              ['actions=controller(reason=packet_out,max_len=1024,id=1)'])},
151    {'name': 'action_fintimeout',
152     'versions': [4],
153     'cmd': 'add-flow',
154     'args': (['priority=100,tcp'] +
155              ['actions=fin_timeout(idle_timeout=30,hard_timeout=60)'])},
156    {'name': 'action_dec_nw_ttl',
157     'versions': [1],
158     'cmd': 'add-flow',
159     'args': (['priority=100,mpls'] +
160              ['actions=dec_ttl'])},
161    {'name': 'action_push_mpls',
162     'versions': [1],
163     'cmd': 'add-flow',
164     'args': (['priority=100,ip'] +
165              ['actions=push_mpls:0x8847'])},
166    {'name': 'action_pop_mpls',
167     'versions': [1],
168     'cmd': 'add-flow',
169     'args': (['priority=100,mpls'] +
170              ['actions=pop_mpls:0x0800'])},
171    {'name': 'action_set_mpls_ttl',
172     'versions': [1],
173     'cmd': 'add-flow',
174     'args': (['priority=100,mpls'] +
175              ['actions=set_mpls_ttl(127)'])},
176    {'name': 'action_dec_mpls_ttl',
177     'versions': [1],
178     'cmd': 'add-flow',
179     'args': (['priority=100,mpls'] +
180              ['actions=dec_mpls_ttl'])},
181    {'name': 'action_set_mpls_label',
182     'versions': [1],
183     'cmd': 'add-flow',
184     'args': (['priority=100,mpls'] +
185              ['actions=set_mpls_label(10)'])},
186    {'name': 'action_set_mpls_tc',
187     'versions': [1],
188     'cmd': 'add-flow',
189     'args': (['priority=100,mpls'] +
190              ['actions=set_mpls_tc(10)'])},
191    {'name': 'action_dec_ttl_cnt_ids',
192     'versions': [4],
193     'cmd': 'add-flow',
194     'args': (['priority=100,tcp'] +
195              ['actions=dec_ttl(1,2,3,4,5)'])},
196    {'name': 'action_stack_push',
197     'versions': [4],
198     'cmd': 'add-flow',
199     'args': (['priority=100'] +
200              ['actions=push:NXM_NX_REG2[1..5]'])},
201    {'name': 'action_stack_pop',
202     'versions': [4],
203     'cmd': 'add-flow',
204     'args': (['priority=100'] +
205              ['actions=pop:NXM_NX_REG2[1..5]'])},
206    {'name': 'action_sample',
207     'versions': [4],
208     'cmd': 'add-flow',
209     'args': (['priority=100'] +
210              ['actions=sample(probability=3,collector_set_id=1,' +
211               'obs_domain_id=2,obs_point_id=3)'])},
212    {'name': 'action_sample2',
213     'versions': [4],
214     'cmd': 'add-flow',
215     'args': (['priority=100'] +
216              ['actions=sample(probability=3,collector_set_id=1,' +
217               'obs_domain_id=2,obs_point_id=3,sampling_port=8080)'])},
218    {'name': 'action_controller2',
219     'versions': [4],
220     'cmd': 'add-flow',
221     'args': (['priority=100'] +
222              ['actions=controller(reason=packet_out,max_len=1024,' +
223               'id=10,userdata=01.02.03.04.05,pause)'])},
224    {'name': 'action_output_trunc',
225     'versions': [4],
226     'cmd': 'add-flow',
227     'args': (['priority=100'] +
228              ['actions=output(port=8080,max_len=1024)'])},
229
230    {'name': 'bundle-add',
231     'versions': [4],
232     'bundled': True,
233     'cmd': 'add-flow',
234     'args': ['table=33',
235              'dl_vlan=1234',
236              'actions=strip_vlan,goto_table:100']},
237
238
239    # ToDo: The following actions are not eligible
240    # {'name': 'action_regload2'},
241    # {'name': 'action_outputreg2'},
242]
243
244buf = []
245
246
247class MyHandler(socketserver.BaseRequestHandler):
248    verbose = False
249
250    @staticmethod
251    def _add_msg_to_buf(data, msg_len):
252        # HACK: Clear xid into zero
253        buf.append(data[:4] + b'\x00\x00\x00\x00' + data[8:msg_len])
254
255    def handle(self):
256        desc = ofproto_protocol.ProtocolDesc()
257        residue = b''
258        while True:
259            if residue:
260                data = residue
261                residue = b''
262            else:
263                data = self.request.recv(1024)
264                if data == b'':
265                    break
266            if self.verbose:
267                print(data)
268            h = ofproto_parser.header(data)
269            if self.verbose:
270                print(h)
271            version, msg_type, msg_len, xid = h
272            residue = data[msg_len:]
273            desc.set_version(version=version)
274            if msg_type == desc.ofproto.OFPT_HELLO:
275                hello = desc.ofproto_parser.OFPHello(desc)
276                hello.serialize()
277                self.request.send(hello.buf)
278            elif msg_type == desc.ofproto.OFPT_FLOW_MOD:
279                self._add_msg_to_buf(data, msg_len)
280            elif version == 4 and msg_type == desc.ofproto.OFPT_EXPERIMENTER:
281                # This is for OF13 Ext-230 bundle
282                # TODO: support bundle for OF>1.3
283                exp = desc.ofproto_parser.OFPExperimenter.parser(
284                    object(), version, msg_type, msg_len, xid, data)
285                self._add_msg_to_buf(data, msg_len)
286                if isinstance(exp, desc.ofproto_parser.ONFBundleCtrlMsg):
287                    ctrlrep = desc.ofproto_parser.ONFBundleCtrlMsg(
288                        desc, exp.bundle_id, exp.type + 1, 0, [])
289                    ctrlrep.xid = xid
290                    ctrlrep.serialize()
291                    self.request.send(ctrlrep.buf)
292            elif msg_type == desc.ofproto.OFPT_BARRIER_REQUEST:
293                brep = desc.ofproto_parser.OFPBarrierReply(desc)
294                brep.xid = xid
295                brep.serialize()
296                self.request.send(brep.buf)
297
298
299class MyVerboseHandler(MyHandler):
300    verbose = True
301
302
303if __name__ == '__main__':
304    optlist, args = getopt.getopt(sys.argv[1:], 'dvo:')
305    debug = False
306    ofctl_cmd = '/usr/bin/ovs-ofctl'
307    verbose = False
308    for o, a in optlist:
309        if o == '-d':
310            debug = True
311        elif o == '-v':
312            verbose = True
313        elif o == '-o':
314            ofctl_cmd = a
315
316    if not os.access(ofctl_cmd, os.X_OK):
317        raise Exception("%s is not executable" % ofctl_cmd)
318    ovs_version = subprocess.Popen([ofctl_cmd, '--version'],
319                                   stdout=subprocess.PIPE)
320    has_names = False
321    try:
322        ver_tuple = re.search(r'\s(\d+)\.(\d+)(\.\d*|\s*$)',
323                              ovs_version.stdout.readline().decode()).groups()
324        if int(ver_tuple[0]) > 2 or \
325           int(ver_tuple[0]) == 2 and int(ver_tuple[1]) >= 8:
326            has_names = True
327    except AttributeError:
328        pass
329
330    outpath = '../packet_data'
331    socketdir = tempfile.mkdtemp()
332    socketname = os.path.join(socketdir, 'ovs')
333    server = socketserver.UnixStreamServer(socketname,
334                                           MyVerboseHandler if verbose else
335                                           MyHandler)
336    if debug or verbose:
337        print("Serving at %s" % socketname)
338
339    for msg in MESSAGES:
340        bundled = msg.get('bundled', False)
341        for v in msg['versions']:
342            cmdargs = [ofctl_cmd, '-O', 'OpenFlow%2d' % (v + 9)]
343            if verbose:
344                cmdargs.append('-v')
345            if has_names:
346                cmdargs.append('--no-names')
347            if bundled:
348                cmdargs.append('--bundle')
349            cmdargs.append(msg['cmd'])
350            cmdargs.append('unix:%s' % socketname)
351            cmdargs.append('\n'.join(msg['args']))
352            if verbose:
353                print("Running cmd: " + ' '.join(cmdargs) + "\n")
354            t = threading.Thread(target=subprocess.call, args=[cmdargs],
355                                 kwargs={'timeout': 5})
356            t.start()
357            server.handle_request()
358            if debug:
359                for buf1 in buf:
360                    print(buf1)
361                buf = []
362            else:
363                for i, buf1 in enumerate(buf):
364                    suffix = ('-%d' % (i + 1)) if i else ''
365                    outf = os.path.join(
366                        outpath, "of%d" % (v + 9),
367                        "ovs-ofctl-of%d-%s%s.packet" % (
368                            v + 9, msg['name'], suffix))
369                    print("Writing %s..." % outf)
370                    with open(outf, 'wb') as f:
371                        f.write(buf1)
372                buf = []
373            try:
374                t.join()
375            except TimeoutExpired as e:
376                print(e)
377
378    if debug:
379        while True:
380            server.handle_request()
381            print(buf.pop())
382
383    os.unlink(socketname)
384    os.rmdir(socketdir)
385