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