]>
Commit | Line | Data |
---|---|---|
25f16c87 AC |
1 | #!/usr/bin/env python3 |
2 | # SPDX-License-Identifier: GPL-2.0 | |
3 | ||
4 | # Controls the openvswitch module. Part of the kselftest suite, but | |
5 | # can be used for some diagnostic purpose as well. | |
6 | ||
7 | import argparse | |
8 | import errno | |
e52b07aa AC |
9 | import ipaddress |
10 | import logging | |
9feac87b | 11 | import multiprocessing |
918423fd | 12 | import re |
9feac87b | 13 | import struct |
25f16c87 | 14 | import sys |
e52b07aa | 15 | import time |
918423fd AC |
16 | import types |
17 | import uuid | |
25f16c87 AC |
18 | |
19 | try: | |
20 | from pyroute2 import NDB | |
21 | ||
e52b07aa | 22 | from pyroute2.netlink import NLA_F_NESTED |
25f16c87 | 23 | from pyroute2.netlink import NLM_F_ACK |
e52b07aa | 24 | from pyroute2.netlink import NLM_F_DUMP |
25f16c87 AC |
25 | from pyroute2.netlink import NLM_F_REQUEST |
26 | from pyroute2.netlink import genlmsg | |
27 | from pyroute2.netlink import nla | |
e52b07aa | 28 | from pyroute2.netlink import nlmsg_atoms |
25f16c87 AC |
29 | from pyroute2.netlink.exceptions import NetlinkError |
30 | from pyroute2.netlink.generic import GenericNetlinkSocket | |
92e37f20 AC |
31 | import pyroute2 |
32 | ||
25f16c87 | 33 | except ModuleNotFoundError: |
92e37f20 | 34 | print("Need to install the python pyroute2 package >= 0.6.") |
25f16c87 AC |
35 | sys.exit(0) |
36 | ||
37 | ||
38 | OVS_DATAPATH_FAMILY = "ovs_datapath" | |
39 | OVS_VPORT_FAMILY = "ovs_vport" | |
40 | OVS_FLOW_FAMILY = "ovs_flow" | |
41 | OVS_PACKET_FAMILY = "ovs_packet" | |
42 | OVS_METER_FAMILY = "ovs_meter" | |
43 | OVS_CT_LIMIT_FAMILY = "ovs_ct_limit" | |
44 | ||
45 | OVS_DATAPATH_VERSION = 2 | |
46 | OVS_DP_CMD_NEW = 1 | |
47 | OVS_DP_CMD_DEL = 2 | |
48 | OVS_DP_CMD_GET = 3 | |
49 | OVS_DP_CMD_SET = 4 | |
50 | ||
51 | OVS_VPORT_CMD_NEW = 1 | |
52 | OVS_VPORT_CMD_DEL = 2 | |
53 | OVS_VPORT_CMD_GET = 3 | |
54 | OVS_VPORT_CMD_SET = 4 | |
55 | ||
e52b07aa AC |
56 | OVS_FLOW_CMD_NEW = 1 |
57 | OVS_FLOW_CMD_DEL = 2 | |
58 | OVS_FLOW_CMD_GET = 3 | |
59 | OVS_FLOW_CMD_SET = 4 | |
60 | ||
61 | ||
62 | def macstr(mac): | |
63 | outstr = ":".join(["%02X" % i for i in mac]) | |
64 | return outstr | |
65 | ||
66 | ||
2893ba9c AC |
67 | def strcspn(str1, str2): |
68 | tot = 0 | |
69 | for char in str1: | |
70 | if str2.find(char) != -1: | |
71 | return tot | |
72 | tot += 1 | |
73 | return tot | |
74 | ||
75 | ||
918423fd AC |
76 | def strspn(str1, str2): |
77 | tot = 0 | |
78 | for char in str1: | |
79 | if str2.find(char) == -1: | |
80 | return tot | |
81 | tot += 1 | |
82 | return tot | |
83 | ||
84 | ||
85 | def intparse(statestr, defmask="0xffffffff"): | |
86 | totalparse = strspn(statestr, "0123456789abcdefABCDEFx/") | |
87 | # scan until "/" | |
88 | count = strspn(statestr, "x0123456789abcdefABCDEF") | |
89 | ||
90 | firstnum = statestr[:count] | |
91 | if firstnum[-1] == "/": | |
92 | firstnum = firstnum[:-1] | |
93 | k = int(firstnum, 0) | |
94 | ||
95 | m = None | |
96 | if defmask is not None: | |
97 | secondnum = defmask | |
98 | if statestr[count] == "/": | |
99 | secondnum = statestr[count + 1 :] # this is wrong... | |
100 | m = int(secondnum, 0) | |
101 | ||
102 | return statestr[totalparse + 1 :], k, m | |
103 | ||
104 | ||
105 | def parse_flags(flag_str, flag_vals): | |
106 | bitResult = 0 | |
107 | maskResult = 0 | |
108 | ||
109 | if len(flag_str) == 0: | |
110 | return flag_str, bitResult, maskResult | |
111 | ||
112 | if flag_str[0].isdigit(): | |
113 | idx = 0 | |
114 | while flag_str[idx].isdigit() or flag_str[idx] == "x": | |
115 | idx += 1 | |
116 | digits = flag_str[:idx] | |
117 | flag_str = flag_str[idx:] | |
118 | ||
119 | bitResult = int(digits, 0) | |
120 | maskResult = int(digits, 0) | |
121 | ||
122 | while len(flag_str) > 0 and (flag_str[0] == "+" or flag_str[0] == "-"): | |
123 | if flag_str[0] == "+": | |
124 | setFlag = True | |
125 | elif flag_str[0] == "-": | |
126 | setFlag = False | |
127 | ||
128 | flag_str = flag_str[1:] | |
129 | ||
130 | flag_len = 0 | |
131 | while ( | |
132 | flag_str[flag_len] != "+" | |
133 | and flag_str[flag_len] != "-" | |
134 | and flag_str[flag_len] != "," | |
135 | and flag_str[flag_len] != ")" | |
136 | ): | |
137 | flag_len += 1 | |
138 | ||
139 | flag = flag_str[0:flag_len] | |
140 | ||
141 | if flag in flag_vals: | |
142 | if maskResult & flag_vals[flag]: | |
143 | raise KeyError( | |
144 | "Flag %s set once, cannot be set in multiples" % flag | |
145 | ) | |
146 | ||
147 | if setFlag: | |
148 | bitResult |= flag_vals[flag] | |
149 | ||
150 | maskResult |= flag_vals[flag] | |
151 | else: | |
152 | raise KeyError("Missing flag value: %s" % flag) | |
153 | ||
154 | flag_str = flag_str[flag_len:] | |
155 | ||
156 | return flag_str, bitResult, maskResult | |
157 | ||
158 | ||
159 | def parse_ct_state(statestr): | |
160 | ct_flags = { | |
161 | "new": 1 << 0, | |
162 | "est": 1 << 1, | |
163 | "rel": 1 << 2, | |
164 | "rpl": 1 << 3, | |
165 | "inv": 1 << 4, | |
166 | "trk": 1 << 5, | |
167 | "snat": 1 << 6, | |
168 | "dnat": 1 << 7, | |
169 | } | |
170 | ||
171 | return parse_flags(statestr, ct_flags) | |
172 | ||
173 | ||
9f1179fb AM |
174 | def convert_mac(data): |
175 | def to_bytes(mac): | |
176 | mac_split = mac.split(":") | |
177 | ret = bytearray([int(i, 16) for i in mac_split]) | |
178 | return bytes(ret) | |
e52b07aa | 179 | |
9f1179fb | 180 | mac_str, _, mask_str = data.partition('/') |
e52b07aa | 181 | |
9f1179fb AM |
182 | if not mac_str: |
183 | mac_str = mask_str = "00:00:00:00:00:00" | |
184 | elif not mask_str: | |
185 | mask_str = "FF:FF:FF:FF:FF:FF" | |
e52b07aa | 186 | |
9f1179fb | 187 | return to_bytes(mac_str), to_bytes(mask_str) |
e52b07aa | 188 | |
9f1179fb AM |
189 | def convert_ipv4(data): |
190 | ip, _, mask = data.partition('/') | |
191 | ||
192 | if not ip: | |
193 | ip = mask = 0 | |
194 | elif not mask: | |
195 | mask = 0xFFFFFFFF | |
196 | elif mask.isdigit(): | |
197 | mask = (0xFFFFFFFF << (32 - int(mask))) & 0xFFFFFFFF | |
198 | ||
199 | return int(ipaddress.IPv4Address(ip)), int(ipaddress.IPv4Address(mask)) | |
200 | ||
201 | def convert_int(size): | |
202 | def convert_int_sized(data): | |
203 | value, _, mask = data.partition('/') | |
204 | ||
205 | if not value: | |
206 | return 0, 0 | |
207 | elif not mask: | |
208 | return int(value, 0), pow(2, size) - 1 | |
209 | else: | |
210 | return int(value, 0), int(mask, 0) | |
211 | ||
212 | return convert_int_sized | |
25f16c87 | 213 | |
918423fd AC |
214 | def parse_starts_block(block_str, scanstr, returnskipped, scanregex=False): |
215 | if scanregex: | |
216 | m = re.search(scanstr, block_str) | |
217 | if m is None: | |
218 | if returnskipped: | |
219 | return block_str | |
220 | return False | |
221 | if returnskipped: | |
222 | block_str = block_str[len(m.group(0)) :] | |
223 | return block_str | |
224 | return True | |
225 | ||
226 | if block_str.startswith(scanstr): | |
227 | if returnskipped: | |
228 | block_str = block_str[len(scanstr) :] | |
229 | else: | |
230 | return True | |
231 | ||
232 | if returnskipped: | |
233 | return block_str | |
234 | ||
235 | return False | |
236 | ||
237 | ||
238 | def parse_extract_field( | |
239 | block_str, fieldstr, scanfmt, convert, masked=False, defval=None | |
240 | ): | |
241 | if fieldstr and not block_str.startswith(fieldstr): | |
242 | return block_str, defval | |
243 | ||
244 | if fieldstr: | |
245 | str_skiplen = len(fieldstr) | |
246 | str_skipped = block_str[str_skiplen:] | |
247 | if str_skiplen == 0: | |
248 | return str_skipped, defval | |
249 | else: | |
250 | str_skiplen = 0 | |
251 | str_skipped = block_str | |
252 | ||
253 | m = re.search(scanfmt, str_skipped) | |
254 | if m is None: | |
255 | raise ValueError("Bad fmt string") | |
256 | ||
257 | data = m.group(0) | |
258 | if convert: | |
259 | data = convert(m.group(0)) | |
260 | ||
261 | str_skipped = str_skipped[len(m.group(0)) :] | |
262 | if masked: | |
263 | if str_skipped[0] == "/": | |
264 | raise ValueError("Masking support TBD...") | |
265 | ||
266 | str_skipped = str_skipped[strspn(str_skipped, ", ") :] | |
267 | return str_skipped, data | |
268 | ||
269 | ||
25f16c87 AC |
270 | class ovs_dp_msg(genlmsg): |
271 | # include the OVS version | |
272 | # We need a custom header rather than just being able to rely on | |
273 | # genlmsg because fields ends up not expressing everything correctly | |
274 | # if we use the canonical example of setting fields = (('customfield',),) | |
275 | fields = genlmsg.fields + (("dpifindex", "I"),) | |
276 | ||
277 | ||
e52b07aa AC |
278 | class ovsactions(nla): |
279 | nla_flags = NLA_F_NESTED | |
280 | ||
281 | nla_map = ( | |
282 | ("OVS_ACTION_ATTR_UNSPEC", "none"), | |
283 | ("OVS_ACTION_ATTR_OUTPUT", "uint32"), | |
284 | ("OVS_ACTION_ATTR_USERSPACE", "userspace"), | |
285 | ("OVS_ACTION_ATTR_SET", "none"), | |
286 | ("OVS_ACTION_ATTR_PUSH_VLAN", "none"), | |
287 | ("OVS_ACTION_ATTR_POP_VLAN", "flag"), | |
288 | ("OVS_ACTION_ATTR_SAMPLE", "none"), | |
289 | ("OVS_ACTION_ATTR_RECIRC", "uint32"), | |
290 | ("OVS_ACTION_ATTR_HASH", "none"), | |
291 | ("OVS_ACTION_ATTR_PUSH_MPLS", "none"), | |
292 | ("OVS_ACTION_ATTR_POP_MPLS", "flag"), | |
293 | ("OVS_ACTION_ATTR_SET_MASKED", "none"), | |
294 | ("OVS_ACTION_ATTR_CT", "ctact"), | |
295 | ("OVS_ACTION_ATTR_TRUNC", "uint32"), | |
296 | ("OVS_ACTION_ATTR_PUSH_ETH", "none"), | |
297 | ("OVS_ACTION_ATTR_POP_ETH", "flag"), | |
298 | ("OVS_ACTION_ATTR_CT_CLEAR", "flag"), | |
299 | ("OVS_ACTION_ATTR_PUSH_NSH", "none"), | |
300 | ("OVS_ACTION_ATTR_POP_NSH", "flag"), | |
301 | ("OVS_ACTION_ATTR_METER", "none"), | |
302 | ("OVS_ACTION_ATTR_CLONE", "none"), | |
303 | ("OVS_ACTION_ATTR_CHECK_PKT_LEN", "none"), | |
304 | ("OVS_ACTION_ATTR_ADD_MPLS", "none"), | |
305 | ("OVS_ACTION_ATTR_DEC_TTL", "none"), | |
e7bc7db9 | 306 | ("OVS_ACTION_ATTR_DROP", "uint32"), |
e52b07aa AC |
307 | ) |
308 | ||
309 | class ctact(nla): | |
310 | nla_flags = NLA_F_NESTED | |
311 | ||
312 | nla_map = ( | |
313 | ("OVS_CT_ATTR_NONE", "none"), | |
314 | ("OVS_CT_ATTR_COMMIT", "flag"), | |
315 | ("OVS_CT_ATTR_ZONE", "uint16"), | |
316 | ("OVS_CT_ATTR_MARK", "none"), | |
317 | ("OVS_CT_ATTR_LABELS", "none"), | |
318 | ("OVS_CT_ATTR_HELPER", "asciiz"), | |
319 | ("OVS_CT_ATTR_NAT", "natattr"), | |
320 | ("OVS_CT_ATTR_FORCE_COMMIT", "flag"), | |
321 | ("OVS_CT_ATTR_EVENTMASK", "uint32"), | |
322 | ("OVS_CT_ATTR_TIMEOUT", "asciiz"), | |
323 | ) | |
324 | ||
325 | class natattr(nla): | |
326 | nla_flags = NLA_F_NESTED | |
327 | ||
328 | nla_map = ( | |
329 | ("OVS_NAT_ATTR_NONE", "none"), | |
330 | ("OVS_NAT_ATTR_SRC", "flag"), | |
331 | ("OVS_NAT_ATTR_DST", "flag"), | |
332 | ("OVS_NAT_ATTR_IP_MIN", "ipaddr"), | |
333 | ("OVS_NAT_ATTR_IP_MAX", "ipaddr"), | |
334 | ("OVS_NAT_ATTR_PROTO_MIN", "uint16"), | |
335 | ("OVS_NAT_ATTR_PROTO_MAX", "uint16"), | |
336 | ("OVS_NAT_ATTR_PERSISTENT", "flag"), | |
337 | ("OVS_NAT_ATTR_PROTO_HASH", "flag"), | |
338 | ("OVS_NAT_ATTR_PROTO_RANDOM", "flag"), | |
339 | ) | |
340 | ||
341 | def dpstr(self, more=False): | |
342 | print_str = "nat(" | |
343 | ||
344 | if self.get_attr("OVS_NAT_ATTR_SRC"): | |
345 | print_str += "src" | |
346 | elif self.get_attr("OVS_NAT_ATTR_DST"): | |
347 | print_str += "dst" | |
348 | else: | |
349 | print_str += "XXX-unknown-nat" | |
350 | ||
351 | if self.get_attr("OVS_NAT_ATTR_IP_MIN") or self.get_attr( | |
352 | "OVS_NAT_ATTR_IP_MAX" | |
353 | ): | |
354 | if self.get_attr("OVS_NAT_ATTR_IP_MIN"): | |
355 | print_str += "=%s," % str( | |
356 | self.get_attr("OVS_NAT_ATTR_IP_MIN") | |
357 | ) | |
358 | ||
359 | if self.get_attr("OVS_NAT_ATTR_IP_MAX"): | |
360 | print_str += "-%s," % str( | |
361 | self.get_attr("OVS_NAT_ATTR_IP_MAX") | |
362 | ) | |
363 | else: | |
364 | print_str += "," | |
365 | ||
366 | if self.get_attr("OVS_NAT_ATTR_PROTO_MIN"): | |
367 | print_str += "proto_min=%d," % self.get_attr( | |
368 | "OVS_NAT_ATTR_PROTO_MIN" | |
369 | ) | |
370 | ||
371 | if self.get_attr("OVS_NAT_ATTR_PROTO_MAX"): | |
372 | print_str += "proto_max=%d," % self.get_attr( | |
373 | "OVS_NAT_ATTR_PROTO_MAX" | |
374 | ) | |
375 | ||
376 | if self.get_attr("OVS_NAT_ATTR_PERSISTENT"): | |
377 | print_str += "persistent," | |
378 | if self.get_attr("OVS_NAT_ATTR_HASH"): | |
379 | print_str += "hash," | |
380 | if self.get_attr("OVS_NAT_ATTR_RANDOM"): | |
381 | print_str += "random" | |
382 | print_str += ")" | |
383 | return print_str | |
384 | ||
385 | def dpstr(self, more=False): | |
386 | print_str = "ct(" | |
387 | ||
388 | if self.get_attr("OVS_CT_ATTR_COMMIT") is not None: | |
389 | print_str += "commit," | |
390 | if self.get_attr("OVS_CT_ATTR_ZONE") is not None: | |
391 | print_str += "zone=%d," % self.get_attr("OVS_CT_ATTR_ZONE") | |
392 | if self.get_attr("OVS_CT_ATTR_HELPER") is not None: | |
393 | print_str += "helper=%s," % self.get_attr("OVS_CT_ATTR_HELPER") | |
394 | if self.get_attr("OVS_CT_ATTR_NAT") is not None: | |
395 | print_str += self.get_attr("OVS_CT_ATTR_NAT").dpstr(more) | |
396 | print_str += "," | |
397 | if self.get_attr("OVS_CT_ATTR_FORCE_COMMIT") is not None: | |
398 | print_str += "force," | |
399 | if self.get_attr("OVS_CT_ATTR_EVENTMASK") is not None: | |
400 | print_str += "emask=0x%X," % self.get_attr( | |
401 | "OVS_CT_ATTR_EVENTMASK" | |
402 | ) | |
403 | if self.get_attr("OVS_CT_ATTR_TIMEOUT") is not None: | |
404 | print_str += "timeout=%s" % self.get_attr( | |
405 | "OVS_CT_ATTR_TIMEOUT" | |
406 | ) | |
407 | print_str += ")" | |
408 | return print_str | |
409 | ||
410 | class userspace(nla): | |
411 | nla_flags = NLA_F_NESTED | |
412 | ||
413 | nla_map = ( | |
414 | ("OVS_USERSPACE_ATTR_UNUSED", "none"), | |
415 | ("OVS_USERSPACE_ATTR_PID", "uint32"), | |
416 | ("OVS_USERSPACE_ATTR_USERDATA", "array(uint8)"), | |
417 | ("OVS_USERSPACE_ATTR_EGRESS_TUN_PORT", "uint32"), | |
418 | ) | |
419 | ||
420 | def dpstr(self, more=False): | |
421 | print_str = "userspace(" | |
422 | if self.get_attr("OVS_USERSPACE_ATTR_PID") is not None: | |
423 | print_str += "pid=%d," % self.get_attr( | |
424 | "OVS_USERSPACE_ATTR_PID" | |
425 | ) | |
426 | if self.get_attr("OVS_USERSPACE_ATTR_USERDATA") is not None: | |
427 | print_str += "userdata=" | |
428 | for f in self.get_attr("OVS_USERSPACE_ATTR_USERDATA"): | |
429 | print_str += "%x." % f | |
430 | if self.get_attr("OVS_USERSPACE_ATTR_TUN_PORT") is not None: | |
431 | print_str += "egress_tun_port=%d" % self.get_attr( | |
432 | "OVS_USERSPACE_ATTR_TUN_PORT" | |
433 | ) | |
434 | print_str += ")" | |
435 | return print_str | |
436 | ||
437 | def dpstr(self, more=False): | |
438 | print_str = "" | |
439 | ||
440 | for field in self.nla_map: | |
441 | if field[1] == "none" or self.get_attr(field[0]) is None: | |
442 | continue | |
443 | if print_str != "": | |
444 | print_str += "," | |
445 | ||
446 | if field[1] == "uint32": | |
447 | if field[0] == "OVS_ACTION_ATTR_OUTPUT": | |
448 | print_str += "%d" % int(self.get_attr(field[0])) | |
449 | elif field[0] == "OVS_ACTION_ATTR_RECIRC": | |
450 | print_str += "recirc(0x%x)" % int(self.get_attr(field[0])) | |
451 | elif field[0] == "OVS_ACTION_ATTR_TRUNC": | |
452 | print_str += "trunc(%d)" % int(self.get_attr(field[0])) | |
e7bc7db9 EG |
453 | elif field[0] == "OVS_ACTION_ATTR_DROP": |
454 | print_str += "drop(%d)" % int(self.get_attr(field[0])) | |
e52b07aa AC |
455 | elif field[1] == "flag": |
456 | if field[0] == "OVS_ACTION_ATTR_CT_CLEAR": | |
457 | print_str += "ct_clear" | |
458 | elif field[0] == "OVS_ACTION_ATTR_POP_VLAN": | |
459 | print_str += "pop_vlan" | |
460 | elif field[0] == "OVS_ACTION_ATTR_POP_ETH": | |
461 | print_str += "pop_eth" | |
462 | elif field[0] == "OVS_ACTION_ATTR_POP_NSH": | |
463 | print_str += "pop_nsh" | |
464 | elif field[0] == "OVS_ACTION_ATTR_POP_MPLS": | |
465 | print_str += "pop_mpls" | |
466 | else: | |
467 | datum = self.get_attr(field[0]) | |
468 | print_str += datum.dpstr(more) | |
469 | ||
470 | return print_str | |
471 | ||
918423fd AC |
472 | def parse(self, actstr): |
473 | while len(actstr) != 0: | |
474 | parsed = False | |
475 | if actstr.startswith("drop"): | |
e7bc7db9 EG |
476 | # If no reason is provided, the implicit drop is used (i.e no |
477 | # action). If some reason is given, an explicit action is used. | |
478 | actstr, reason = parse_extract_field( | |
479 | actstr, | |
480 | "drop(", | |
481 | "([0-9]+)", | |
482 | lambda x: int(x, 0), | |
483 | False, | |
484 | None, | |
485 | ) | |
486 | if reason is not None: | |
487 | self["attrs"].append(["OVS_ACTION_ATTR_DROP", reason]) | |
488 | parsed = True | |
489 | else: | |
490 | return | |
918423fd AC |
491 | |
492 | elif parse_starts_block(actstr, "^(\d+)", False, True): | |
493 | actstr, output = parse_extract_field( | |
494 | actstr, None, "(\d+)", lambda x: int(x), False, "0" | |
495 | ) | |
496 | self["attrs"].append(["OVS_ACTION_ATTR_OUTPUT", output]) | |
497 | parsed = True | |
498 | elif parse_starts_block(actstr, "recirc(", False): | |
499 | actstr, recircid = parse_extract_field( | |
500 | actstr, | |
501 | "recirc(", | |
502 | "([0-9a-fA-Fx]+)", | |
503 | lambda x: int(x, 0), | |
504 | False, | |
505 | 0, | |
506 | ) | |
507 | self["attrs"].append(["OVS_ACTION_ATTR_RECIRC", recircid]) | |
508 | parsed = True | |
509 | ||
510 | parse_flat_map = ( | |
511 | ("ct_clear", "OVS_ACTION_ATTR_CT_CLEAR"), | |
512 | ("pop_vlan", "OVS_ACTION_ATTR_POP_VLAN"), | |
513 | ("pop_eth", "OVS_ACTION_ATTR_POP_ETH"), | |
514 | ("pop_nsh", "OVS_ACTION_ATTR_POP_NSH"), | |
515 | ) | |
516 | ||
517 | for flat_act in parse_flat_map: | |
518 | if parse_starts_block(actstr, flat_act[0], False): | |
519 | actstr += len(flat_act[0]) | |
520 | self["attrs"].append([flat_act[1]]) | |
521 | actstr = actstr[strspn(actstr, ", ") :] | |
522 | parsed = True | |
523 | ||
2893ba9c AC |
524 | if parse_starts_block(actstr, "ct(", False): |
525 | actstr = actstr[len("ct(") :] | |
526 | ctact = ovsactions.ctact() | |
527 | ||
528 | for scan in ( | |
529 | ("commit", "OVS_CT_ATTR_COMMIT", None), | |
530 | ("force_commit", "OVS_CT_ATTR_FORCE_COMMIT", None), | |
531 | ("zone", "OVS_CT_ATTR_ZONE", int), | |
532 | ("mark", "OVS_CT_ATTR_MARK", int), | |
533 | ("helper", "OVS_CT_ATTR_HELPER", lambda x, y: str(x)), | |
534 | ("timeout", "OVS_CT_ATTR_TIMEOUT", lambda x, y: str(x)), | |
535 | ): | |
536 | if actstr.startswith(scan[0]): | |
537 | actstr = actstr[len(scan[0]) :] | |
538 | if scan[2] is not None: | |
539 | if actstr[0] != "=": | |
540 | raise ValueError("Invalid ct attr") | |
541 | actstr = actstr[1:] | |
542 | pos = strcspn(actstr, ",)") | |
543 | datum = scan[2](actstr[:pos], 0) | |
544 | ctact["attrs"].append([scan[1], datum]) | |
545 | actstr = actstr[pos:] | |
546 | else: | |
547 | ctact["attrs"].append([scan[1], None]) | |
548 | actstr = actstr[strspn(actstr, ", ") :] | |
60f10077 AC |
549 | # it seems strange to put this here, but nat() is a complex |
550 | # sub-action and this lets it sit anywhere in the ct() action | |
551 | if actstr.startswith("nat"): | |
552 | actstr = actstr[3:] | |
553 | natact = ovsactions.ctact.natattr() | |
554 | ||
555 | if actstr.startswith("("): | |
556 | t = None | |
557 | actstr = actstr[1:] | |
558 | if actstr.startswith("src"): | |
559 | t = "OVS_NAT_ATTR_SRC" | |
560 | actstr = actstr[3:] | |
561 | elif actstr.startswith("dst"): | |
562 | t = "OVS_NAT_ATTR_DST" | |
563 | actstr = actstr[3:] | |
564 | ||
565 | actstr, ip_block_min = parse_extract_field( | |
566 | actstr, "=", "([0-9a-fA-F\.]+)", str, False | |
567 | ) | |
568 | actstr, ip_block_max = parse_extract_field( | |
569 | actstr, "-", "([0-9a-fA-F\.]+)", str, False | |
570 | ) | |
571 | ||
572 | actstr, proto_min = parse_extract_field( | |
573 | actstr, ":", "(\d+)", int, False | |
574 | ) | |
575 | actstr, proto_max = parse_extract_field( | |
576 | actstr, "-", "(\d+)", int, False | |
577 | ) | |
578 | ||
579 | if t is not None: | |
580 | natact["attrs"].append([t, None]) | |
581 | ||
582 | if ip_block_min is not None: | |
583 | natact["attrs"].append( | |
584 | ["OVS_NAT_ATTR_IP_MIN", ip_block_min] | |
585 | ) | |
586 | if ip_block_max is not None: | |
587 | natact["attrs"].append( | |
588 | ["OVS_NAT_ATTR_IP_MAX", ip_block_max] | |
589 | ) | |
590 | if proto_min is not None: | |
591 | natact["attrs"].append( | |
592 | ["OVS_NAT_ATTR_PROTO_MIN", proto_min] | |
593 | ) | |
594 | if proto_max is not None: | |
595 | natact["attrs"].append( | |
596 | ["OVS_NAT_ATTR_PROTO_MAX", proto_max] | |
597 | ) | |
598 | ||
599 | for natscan in ( | |
600 | ("persistent", "OVS_NAT_ATTR_PERSISTENT"), | |
601 | ("hash", "OVS_NAT_ATTR_PROTO_HASH"), | |
602 | ("random", "OVS_NAT_ATTR_PROTO_RANDOM"), | |
603 | ): | |
604 | if actstr.startswith(natscan[0]): | |
605 | actstr = actstr[len(natscan[0]) :] | |
606 | natact["attrs"].append([natscan[1], None]) | |
607 | actstr = actstr[strspn(actstr, ", ") :] | |
608 | ||
609 | ctact["attrs"].append(["OVS_CT_ATTR_NAT", natact]) | |
610 | actstr = actstr[strspn(actstr, ",) ") :] | |
2893ba9c AC |
611 | |
612 | self["attrs"].append(["OVS_ACTION_ATTR_CT", ctact]) | |
613 | parsed = True | |
614 | ||
615 | actstr = actstr[strspn(actstr, "), ") :] | |
918423fd AC |
616 | if not parsed: |
617 | raise ValueError("Action str: '%s' not supported" % actstr) | |
618 | ||
e52b07aa AC |
619 | |
620 | class ovskey(nla): | |
621 | nla_flags = NLA_F_NESTED | |
622 | nla_map = ( | |
623 | ("OVS_KEY_ATTR_UNSPEC", "none"), | |
624 | ("OVS_KEY_ATTR_ENCAP", "none"), | |
625 | ("OVS_KEY_ATTR_PRIORITY", "uint32"), | |
626 | ("OVS_KEY_ATTR_IN_PORT", "uint32"), | |
627 | ("OVS_KEY_ATTR_ETHERNET", "ethaddr"), | |
628 | ("OVS_KEY_ATTR_VLAN", "uint16"), | |
629 | ("OVS_KEY_ATTR_ETHERTYPE", "be16"), | |
630 | ("OVS_KEY_ATTR_IPV4", "ovs_key_ipv4"), | |
631 | ("OVS_KEY_ATTR_IPV6", "ovs_key_ipv6"), | |
632 | ("OVS_KEY_ATTR_TCP", "ovs_key_tcp"), | |
633 | ("OVS_KEY_ATTR_UDP", "ovs_key_udp"), | |
634 | ("OVS_KEY_ATTR_ICMP", "ovs_key_icmp"), | |
635 | ("OVS_KEY_ATTR_ICMPV6", "ovs_key_icmpv6"), | |
636 | ("OVS_KEY_ATTR_ARP", "ovs_key_arp"), | |
637 | ("OVS_KEY_ATTR_ND", "ovs_key_nd"), | |
638 | ("OVS_KEY_ATTR_SKB_MARK", "uint32"), | |
639 | ("OVS_KEY_ATTR_TUNNEL", "none"), | |
640 | ("OVS_KEY_ATTR_SCTP", "ovs_key_sctp"), | |
641 | ("OVS_KEY_ATTR_TCP_FLAGS", "be16"), | |
642 | ("OVS_KEY_ATTR_DP_HASH", "uint32"), | |
643 | ("OVS_KEY_ATTR_RECIRC_ID", "uint32"), | |
644 | ("OVS_KEY_ATTR_MPLS", "array(ovs_key_mpls)"), | |
645 | ("OVS_KEY_ATTR_CT_STATE", "uint32"), | |
646 | ("OVS_KEY_ATTR_CT_ZONE", "uint16"), | |
647 | ("OVS_KEY_ATTR_CT_MARK", "uint32"), | |
648 | ("OVS_KEY_ATTR_CT_LABELS", "none"), | |
649 | ("OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4", "ovs_key_ct_tuple_ipv4"), | |
650 | ("OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6", "ovs_key_ct_tuple_ipv6"), | |
651 | ("OVS_KEY_ATTR_NSH", "none"), | |
652 | ("OVS_KEY_ATTR_PACKET_TYPE", "none"), | |
653 | ("OVS_KEY_ATTR_ND_EXTENSIONS", "none"), | |
654 | ("OVS_KEY_ATTR_TUNNEL_INFO", "none"), | |
655 | ("OVS_KEY_ATTR_IPV6_EXTENSIONS", "none"), | |
656 | ) | |
657 | ||
658 | class ovs_key_proto(nla): | |
659 | fields = ( | |
660 | ("src", "!H"), | |
661 | ("dst", "!H"), | |
662 | ) | |
663 | ||
664 | fields_map = ( | |
9f1179fb AM |
665 | ("src", "src", "%d", lambda x: int(x) if x else 0, |
666 | convert_int(16)), | |
667 | ("dst", "dst", "%d", lambda x: int(x) if x else 0, | |
668 | convert_int(16)), | |
e52b07aa AC |
669 | ) |
670 | ||
671 | def __init__( | |
672 | self, | |
673 | protostr, | |
674 | data=None, | |
675 | offset=None, | |
676 | parent=None, | |
677 | length=None, | |
678 | init=None, | |
679 | ): | |
680 | self.proto_str = protostr | |
681 | nla.__init__( | |
682 | self, | |
683 | data=data, | |
684 | offset=offset, | |
685 | parent=parent, | |
686 | length=length, | |
687 | init=init, | |
688 | ) | |
689 | ||
918423fd AC |
690 | def parse(self, flowstr, typeInst): |
691 | if not flowstr.startswith(self.proto_str): | |
692 | return None, None | |
693 | ||
694 | k = typeInst() | |
695 | m = typeInst() | |
696 | ||
697 | flowstr = flowstr[len(self.proto_str) :] | |
698 | if flowstr.startswith("("): | |
699 | flowstr = flowstr[1:] | |
700 | ||
701 | keybits = b"" | |
702 | maskbits = b"" | |
703 | for f in self.fields_map: | |
704 | if flowstr.startswith(f[1]): | |
705 | # the following assumes that the field looks | |
706 | # something like 'field.' where '.' is a | |
707 | # character that we don't exactly care about. | |
708 | flowstr = flowstr[len(f[1]) + 1 :] | |
709 | splitchar = 0 | |
710 | for c in flowstr: | |
711 | if c == "," or c == ")": | |
712 | break | |
713 | splitchar += 1 | |
714 | data = flowstr[:splitchar] | |
715 | flowstr = flowstr[splitchar:] | |
716 | else: | |
9f1179fb | 717 | data = "" |
918423fd AC |
718 | |
719 | if len(f) > 4: | |
9f1179fb | 720 | k[f[0]], m[f[0]] = f[4](data) |
918423fd | 721 | else: |
9f1179fb AM |
722 | k[f[0]] = f[3](data) |
723 | m[f[0]] = f[3](data) | |
918423fd AC |
724 | |
725 | flowstr = flowstr[strspn(flowstr, ", ") :] | |
726 | if len(flowstr) == 0: | |
727 | return flowstr, k, m | |
728 | ||
729 | flowstr = flowstr[strspn(flowstr, "), ") :] | |
730 | ||
731 | return flowstr, k, m | |
732 | ||
e52b07aa AC |
733 | def dpstr(self, masked=None, more=False): |
734 | outstr = self.proto_str + "(" | |
735 | first = False | |
736 | for f in self.fields_map: | |
737 | if first: | |
738 | outstr += "," | |
739 | if masked is None: | |
740 | outstr += "%s=" % f[0] | |
741 | if isinstance(f[2], str): | |
742 | outstr += f[2] % self[f[1]] | |
743 | else: | |
744 | outstr += f[2](self[f[1]]) | |
745 | first = True | |
746 | elif more or f[3](masked[f[1]]) != 0: | |
747 | outstr += "%s=" % f[0] | |
748 | if isinstance(f[2], str): | |
749 | outstr += f[2] % self[f[1]] | |
750 | else: | |
751 | outstr += f[2](self[f[1]]) | |
752 | outstr += "/" | |
753 | if isinstance(f[2], str): | |
754 | outstr += f[2] % masked[f[1]] | |
755 | else: | |
756 | outstr += f[2](masked[f[1]]) | |
757 | first = True | |
758 | outstr += ")" | |
759 | return outstr | |
760 | ||
761 | class ethaddr(ovs_key_proto): | |
762 | fields = ( | |
763 | ("src", "!6s"), | |
764 | ("dst", "!6s"), | |
765 | ) | |
766 | ||
767 | fields_map = ( | |
768 | ( | |
769 | "src", | |
770 | "src", | |
771 | macstr, | |
772 | lambda x: int.from_bytes(x, "big"), | |
773 | convert_mac, | |
774 | ), | |
775 | ( | |
776 | "dst", | |
777 | "dst", | |
778 | macstr, | |
779 | lambda x: int.from_bytes(x, "big"), | |
780 | convert_mac, | |
781 | ), | |
782 | ) | |
783 | ||
784 | def __init__( | |
785 | self, | |
786 | data=None, | |
787 | offset=None, | |
788 | parent=None, | |
789 | length=None, | |
790 | init=None, | |
791 | ): | |
792 | ovskey.ovs_key_proto.__init__( | |
793 | self, | |
794 | "eth", | |
795 | data=data, | |
796 | offset=offset, | |
797 | parent=parent, | |
798 | length=length, | |
799 | init=init, | |
800 | ) | |
801 | ||
802 | class ovs_key_ipv4(ovs_key_proto): | |
803 | fields = ( | |
804 | ("src", "!I"), | |
805 | ("dst", "!I"), | |
806 | ("proto", "B"), | |
807 | ("tos", "B"), | |
808 | ("ttl", "B"), | |
809 | ("frag", "B"), | |
810 | ) | |
811 | ||
812 | fields_map = ( | |
813 | ( | |
814 | "src", | |
815 | "src", | |
816 | lambda x: str(ipaddress.IPv4Address(x)), | |
817 | int, | |
818 | convert_ipv4, | |
819 | ), | |
820 | ( | |
821 | "dst", | |
822 | "dst", | |
823 | lambda x: str(ipaddress.IPv4Address(x)), | |
824 | int, | |
825 | convert_ipv4, | |
826 | ), | |
9f1179fb AM |
827 | ("proto", "proto", "%d", lambda x: int(x) if x else 0, |
828 | convert_int(8)), | |
829 | ("tos", "tos", "%d", lambda x: int(x) if x else 0, | |
830 | convert_int(8)), | |
831 | ("ttl", "ttl", "%d", lambda x: int(x) if x else 0, | |
832 | convert_int(8)), | |
833 | ("frag", "frag", "%d", lambda x: int(x) if x else 0, | |
834 | convert_int(8)), | |
e52b07aa AC |
835 | ) |
836 | ||
837 | def __init__( | |
838 | self, | |
839 | data=None, | |
840 | offset=None, | |
841 | parent=None, | |
842 | length=None, | |
843 | init=None, | |
844 | ): | |
845 | ovskey.ovs_key_proto.__init__( | |
846 | self, | |
847 | "ipv4", | |
848 | data=data, | |
849 | offset=offset, | |
850 | parent=parent, | |
851 | length=length, | |
852 | init=init, | |
853 | ) | |
854 | ||
855 | class ovs_key_ipv6(ovs_key_proto): | |
856 | fields = ( | |
857 | ("src", "!16s"), | |
858 | ("dst", "!16s"), | |
859 | ("label", "!I"), | |
860 | ("proto", "B"), | |
861 | ("tclass", "B"), | |
862 | ("hlimit", "B"), | |
863 | ("frag", "B"), | |
864 | ) | |
865 | ||
866 | fields_map = ( | |
867 | ( | |
868 | "src", | |
869 | "src", | |
870 | lambda x: str(ipaddress.IPv6Address(x)), | |
871 | lambda x: int.from_bytes(x, "big"), | |
872 | lambda x: ipaddress.IPv6Address(x), | |
873 | ), | |
874 | ( | |
875 | "dst", | |
876 | "dst", | |
877 | lambda x: str(ipaddress.IPv6Address(x)), | |
878 | lambda x: int.from_bytes(x, "big"), | |
879 | lambda x: ipaddress.IPv6Address(x), | |
880 | ), | |
881 | ("label", "label", "%d", int), | |
882 | ("proto", "proto", "%d", int), | |
883 | ("tclass", "tclass", "%d", int), | |
884 | ("hlimit", "hlimit", "%d", int), | |
885 | ("frag", "frag", "%d", int), | |
886 | ) | |
887 | ||
888 | def __init__( | |
889 | self, | |
890 | data=None, | |
891 | offset=None, | |
892 | parent=None, | |
893 | length=None, | |
894 | init=None, | |
895 | ): | |
896 | ovskey.ovs_key_proto.__init__( | |
897 | self, | |
898 | "ipv6", | |
899 | data=data, | |
900 | offset=offset, | |
901 | parent=parent, | |
902 | length=length, | |
903 | init=init, | |
904 | ) | |
905 | ||
906 | class ovs_key_tcp(ovs_key_proto): | |
907 | def __init__( | |
908 | self, | |
909 | data=None, | |
910 | offset=None, | |
911 | parent=None, | |
912 | length=None, | |
913 | init=None, | |
914 | ): | |
915 | ovskey.ovs_key_proto.__init__( | |
916 | self, | |
917 | "tcp", | |
918 | data=data, | |
919 | offset=offset, | |
920 | parent=parent, | |
921 | length=length, | |
922 | init=init, | |
923 | ) | |
924 | ||
925 | class ovs_key_udp(ovs_key_proto): | |
926 | def __init__( | |
927 | self, | |
928 | data=None, | |
929 | offset=None, | |
930 | parent=None, | |
931 | length=None, | |
932 | init=None, | |
933 | ): | |
934 | ovskey.ovs_key_proto.__init__( | |
935 | self, | |
936 | "udp", | |
937 | data=data, | |
938 | offset=offset, | |
939 | parent=parent, | |
940 | length=length, | |
941 | init=init, | |
942 | ) | |
943 | ||
944 | class ovs_key_sctp(ovs_key_proto): | |
945 | def __init__( | |
946 | self, | |
947 | data=None, | |
948 | offset=None, | |
949 | parent=None, | |
950 | length=None, | |
951 | init=None, | |
952 | ): | |
953 | ovskey.ovs_key_proto.__init__( | |
954 | self, | |
955 | "sctp", | |
956 | data=data, | |
957 | offset=offset, | |
958 | parent=parent, | |
959 | length=length, | |
960 | init=init, | |
961 | ) | |
962 | ||
963 | class ovs_key_icmp(ovs_key_proto): | |
964 | fields = ( | |
965 | ("type", "B"), | |
966 | ("code", "B"), | |
967 | ) | |
968 | ||
969 | fields_map = ( | |
9f1179fb AM |
970 | ("type", "type", "%d", lambda x: int(x) if x else 0), |
971 | ("code", "code", "%d", lambda x: int(x) if x else 0), | |
e52b07aa AC |
972 | ) |
973 | ||
974 | def __init__( | |
975 | self, | |
976 | data=None, | |
977 | offset=None, | |
978 | parent=None, | |
979 | length=None, | |
980 | init=None, | |
981 | ): | |
982 | ovskey.ovs_key_proto.__init__( | |
983 | self, | |
984 | "icmp", | |
985 | data=data, | |
986 | offset=offset, | |
987 | parent=parent, | |
988 | length=length, | |
989 | init=init, | |
990 | ) | |
991 | ||
992 | class ovs_key_icmpv6(ovs_key_icmp): | |
993 | def __init__( | |
994 | self, | |
995 | data=None, | |
996 | offset=None, | |
997 | parent=None, | |
998 | length=None, | |
999 | init=None, | |
1000 | ): | |
1001 | ovskey.ovs_key_proto.__init__( | |
1002 | self, | |
1003 | "icmpv6", | |
1004 | data=data, | |
1005 | offset=offset, | |
1006 | parent=parent, | |
1007 | length=length, | |
1008 | init=init, | |
1009 | ) | |
1010 | ||
1011 | class ovs_key_arp(ovs_key_proto): | |
1012 | fields = ( | |
1013 | ("sip", "!I"), | |
1014 | ("tip", "!I"), | |
1015 | ("op", "!H"), | |
1016 | ("sha", "!6s"), | |
1017 | ("tha", "!6s"), | |
1018 | ("pad", "xx"), | |
1019 | ) | |
1020 | ||
1021 | fields_map = ( | |
1022 | ( | |
1023 | "sip", | |
1024 | "sip", | |
1025 | lambda x: str(ipaddress.IPv4Address(x)), | |
1026 | int, | |
1027 | convert_ipv4, | |
1028 | ), | |
1029 | ( | |
1030 | "tip", | |
1031 | "tip", | |
1032 | lambda x: str(ipaddress.IPv4Address(x)), | |
1033 | int, | |
1034 | convert_ipv4, | |
1035 | ), | |
9f1179fb | 1036 | ("op", "op", "%d", lambda x: int(x) if x else 0), |
e52b07aa AC |
1037 | ( |
1038 | "sha", | |
1039 | "sha", | |
1040 | macstr, | |
1041 | lambda x: int.from_bytes(x, "big"), | |
1042 | convert_mac, | |
1043 | ), | |
1044 | ( | |
1045 | "tha", | |
1046 | "tha", | |
1047 | macstr, | |
1048 | lambda x: int.from_bytes(x, "big"), | |
1049 | convert_mac, | |
1050 | ), | |
1051 | ) | |
1052 | ||
1053 | def __init__( | |
1054 | self, | |
1055 | data=None, | |
1056 | offset=None, | |
1057 | parent=None, | |
1058 | length=None, | |
1059 | init=None, | |
1060 | ): | |
1061 | ovskey.ovs_key_proto.__init__( | |
1062 | self, | |
1063 | "arp", | |
1064 | data=data, | |
1065 | offset=offset, | |
1066 | parent=parent, | |
1067 | length=length, | |
1068 | init=init, | |
1069 | ) | |
1070 | ||
1071 | class ovs_key_nd(ovs_key_proto): | |
1072 | fields = ( | |
1073 | ("target", "!16s"), | |
1074 | ("sll", "!6s"), | |
1075 | ("tll", "!6s"), | |
1076 | ) | |
1077 | ||
1078 | fields_map = ( | |
1079 | ( | |
1080 | "target", | |
1081 | "target", | |
1082 | lambda x: str(ipaddress.IPv6Address(x)), | |
1083 | lambda x: int.from_bytes(x, "big"), | |
1084 | ), | |
1085 | ("sll", "sll", macstr, lambda x: int.from_bytes(x, "big")), | |
1086 | ("tll", "tll", macstr, lambda x: int.from_bytes(x, "big")), | |
1087 | ) | |
1088 | ||
1089 | def __init__( | |
1090 | self, | |
1091 | data=None, | |
1092 | offset=None, | |
1093 | parent=None, | |
1094 | length=None, | |
1095 | init=None, | |
1096 | ): | |
1097 | ovskey.ovs_key_proto.__init__( | |
1098 | self, | |
1099 | "nd", | |
1100 | data=data, | |
1101 | offset=offset, | |
1102 | parent=parent, | |
1103 | length=length, | |
1104 | init=init, | |
1105 | ) | |
1106 | ||
1107 | class ovs_key_ct_tuple_ipv4(ovs_key_proto): | |
1108 | fields = ( | |
1109 | ("src", "!I"), | |
1110 | ("dst", "!I"), | |
1111 | ("tp_src", "!H"), | |
1112 | ("tp_dst", "!H"), | |
1113 | ("proto", "B"), | |
1114 | ) | |
1115 | ||
1116 | fields_map = ( | |
1117 | ( | |
1118 | "src", | |
1119 | "src", | |
1120 | lambda x: str(ipaddress.IPv4Address(x)), | |
1121 | int, | |
8eff0e06 | 1122 | convert_ipv4, |
e52b07aa AC |
1123 | ), |
1124 | ( | |
1125 | "dst", | |
1126 | "dst", | |
8eff0e06 | 1127 | lambda x: str(ipaddress.IPv4Address(x)), |
e52b07aa | 1128 | int, |
8eff0e06 | 1129 | convert_ipv4, |
e52b07aa AC |
1130 | ), |
1131 | ("tp_src", "tp_src", "%d", int), | |
1132 | ("tp_dst", "tp_dst", "%d", int), | |
1133 | ("proto", "proto", "%d", int), | |
1134 | ) | |
1135 | ||
1136 | def __init__( | |
1137 | self, | |
1138 | data=None, | |
1139 | offset=None, | |
1140 | parent=None, | |
1141 | length=None, | |
1142 | init=None, | |
1143 | ): | |
1144 | ovskey.ovs_key_proto.__init__( | |
1145 | self, | |
1146 | "ct_tuple4", | |
1147 | data=data, | |
1148 | offset=offset, | |
1149 | parent=parent, | |
1150 | length=length, | |
1151 | init=init, | |
1152 | ) | |
1153 | ||
1154 | class ovs_key_ct_tuple_ipv6(nla): | |
1155 | fields = ( | |
1156 | ("src", "!16s"), | |
1157 | ("dst", "!16s"), | |
1158 | ("tp_src", "!H"), | |
1159 | ("tp_dst", "!H"), | |
1160 | ("proto", "B"), | |
1161 | ) | |
1162 | ||
1163 | fields_map = ( | |
1164 | ( | |
1165 | "src", | |
1166 | "src", | |
1167 | lambda x: str(ipaddress.IPv6Address(x)), | |
1168 | lambda x: int.from_bytes(x, "big", convertmac), | |
1169 | ), | |
1170 | ( | |
1171 | "dst", | |
1172 | "dst", | |
1173 | lambda x: str(ipaddress.IPv6Address(x)), | |
1174 | lambda x: int.from_bytes(x, "big"), | |
1175 | ), | |
1176 | ("tp_src", "tp_src", "%d", int), | |
1177 | ("tp_dst", "tp_dst", "%d", int), | |
1178 | ("proto", "proto", "%d", int), | |
1179 | ) | |
1180 | ||
1181 | def __init__( | |
1182 | self, | |
1183 | data=None, | |
1184 | offset=None, | |
1185 | parent=None, | |
1186 | length=None, | |
1187 | init=None, | |
1188 | ): | |
1189 | ovskey.ovs_key_proto.__init__( | |
1190 | self, | |
1191 | "ct_tuple6", | |
1192 | data=data, | |
1193 | offset=offset, | |
1194 | parent=parent, | |
1195 | length=length, | |
1196 | init=init, | |
1197 | ) | |
1198 | ||
1199 | class ovs_key_mpls(nla): | |
1200 | fields = (("lse", ">I"),) | |
1201 | ||
918423fd AC |
1202 | def parse(self, flowstr, mask=None): |
1203 | for field in ( | |
1204 | ("OVS_KEY_ATTR_PRIORITY", "skb_priority", intparse), | |
1205 | ("OVS_KEY_ATTR_SKB_MARK", "skb_mark", intparse), | |
1206 | ("OVS_KEY_ATTR_RECIRC_ID", "recirc_id", intparse), | |
1207 | ("OVS_KEY_ATTR_DP_HASH", "dp_hash", intparse), | |
1208 | ("OVS_KEY_ATTR_CT_STATE", "ct_state", parse_ct_state), | |
1209 | ("OVS_KEY_ATTR_CT_ZONE", "ct_zone", intparse), | |
1210 | ("OVS_KEY_ATTR_CT_MARK", "ct_mark", intparse), | |
1211 | ("OVS_KEY_ATTR_IN_PORT", "in_port", intparse), | |
1212 | ( | |
1213 | "OVS_KEY_ATTR_ETHERNET", | |
1214 | "eth", | |
1215 | ovskey.ethaddr, | |
1216 | ), | |
1217 | ( | |
1218 | "OVS_KEY_ATTR_ETHERTYPE", | |
1219 | "eth_type", | |
1220 | lambda x: intparse(x, "0xffff"), | |
1221 | ), | |
1222 | ( | |
1223 | "OVS_KEY_ATTR_IPV4", | |
1224 | "ipv4", | |
1225 | ovskey.ovs_key_ipv4, | |
1226 | ), | |
1227 | ( | |
1228 | "OVS_KEY_ATTR_IPV6", | |
1229 | "ipv6", | |
1230 | ovskey.ovs_key_ipv6, | |
1231 | ), | |
1232 | ( | |
1233 | "OVS_KEY_ATTR_ARP", | |
1234 | "arp", | |
1235 | ovskey.ovs_key_arp, | |
1236 | ), | |
1237 | ( | |
1238 | "OVS_KEY_ATTR_TCP", | |
1239 | "tcp", | |
1240 | ovskey.ovs_key_tcp, | |
1241 | ), | |
9f1179fb AM |
1242 | ( |
1243 | "OVS_KEY_ATTR_UDP", | |
1244 | "udp", | |
1245 | ovskey.ovs_key_udp, | |
1246 | ), | |
1247 | ( | |
1248 | "OVS_KEY_ATTR_ICMP", | |
1249 | "icmp", | |
1250 | ovskey.ovs_key_icmp, | |
1251 | ), | |
918423fd AC |
1252 | ( |
1253 | "OVS_KEY_ATTR_TCP_FLAGS", | |
1254 | "tcp_flags", | |
1255 | lambda x: parse_flags(x, None), | |
1256 | ), | |
1257 | ): | |
1258 | fld = field[1] + "(" | |
1259 | if not flowstr.startswith(fld): | |
1260 | continue | |
1261 | ||
1262 | if not isinstance(field[2], types.FunctionType): | |
1263 | nk = field[2]() | |
1264 | flowstr, k, m = nk.parse(flowstr, field[2]) | |
1265 | else: | |
1266 | flowstr = flowstr[len(fld) :] | |
1267 | flowstr, k, m = field[2](flowstr) | |
1268 | ||
1269 | if m and mask is not None: | |
1270 | mask["attrs"].append([field[0], m]) | |
1271 | self["attrs"].append([field[0], k]) | |
1272 | ||
1273 | flowstr = flowstr[strspn(flowstr, "),") :] | |
1274 | ||
1275 | return flowstr | |
1276 | ||
e52b07aa AC |
1277 | def dpstr(self, mask=None, more=False): |
1278 | print_str = "" | |
1279 | ||
1280 | for field in ( | |
1281 | ( | |
1282 | "OVS_KEY_ATTR_PRIORITY", | |
1283 | "skb_priority", | |
1284 | "%d", | |
1285 | lambda x: False, | |
1286 | True, | |
1287 | ), | |
1288 | ( | |
1289 | "OVS_KEY_ATTR_SKB_MARK", | |
1290 | "skb_mark", | |
1291 | "%d", | |
1292 | lambda x: False, | |
1293 | True, | |
1294 | ), | |
1295 | ( | |
1296 | "OVS_KEY_ATTR_RECIRC_ID", | |
1297 | "recirc_id", | |
1298 | "0x%08X", | |
1299 | lambda x: False, | |
1300 | True, | |
1301 | ), | |
1302 | ( | |
1303 | "OVS_KEY_ATTR_DP_HASH", | |
1304 | "dp_hash", | |
1305 | "0x%08X", | |
1306 | lambda x: False, | |
1307 | True, | |
1308 | ), | |
1309 | ( | |
1310 | "OVS_KEY_ATTR_CT_STATE", | |
1311 | "ct_state", | |
1312 | "0x%04x", | |
1313 | lambda x: False, | |
1314 | True, | |
1315 | ), | |
1316 | ( | |
1317 | "OVS_KEY_ATTR_CT_ZONE", | |
1318 | "ct_zone", | |
1319 | "0x%04x", | |
1320 | lambda x: False, | |
1321 | True, | |
1322 | ), | |
1323 | ( | |
1324 | "OVS_KEY_ATTR_CT_MARK", | |
1325 | "ct_mark", | |
1326 | "0x%08x", | |
1327 | lambda x: False, | |
1328 | True, | |
1329 | ), | |
1330 | ( | |
1331 | "OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4", | |
1332 | None, | |
1333 | None, | |
1334 | False, | |
1335 | False, | |
1336 | ), | |
1337 | ( | |
1338 | "OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6", | |
1339 | None, | |
1340 | None, | |
1341 | False, | |
1342 | False, | |
1343 | ), | |
1344 | ( | |
1345 | "OVS_KEY_ATTR_IN_PORT", | |
1346 | "in_port", | |
1347 | "%d", | |
1348 | lambda x: True, | |
1349 | True, | |
1350 | ), | |
1351 | ("OVS_KEY_ATTR_ETHERNET", None, None, False, False), | |
1352 | ( | |
1353 | "OVS_KEY_ATTR_ETHERTYPE", | |
1354 | "eth_type", | |
1355 | "0x%04x", | |
1356 | lambda x: int(x) == 0xFFFF, | |
1357 | True, | |
1358 | ), | |
1359 | ("OVS_KEY_ATTR_IPV4", None, None, False, False), | |
1360 | ("OVS_KEY_ATTR_IPV6", None, None, False, False), | |
1361 | ("OVS_KEY_ATTR_ARP", None, None, False, False), | |
1362 | ("OVS_KEY_ATTR_TCP", None, None, False, False), | |
1363 | ( | |
1364 | "OVS_KEY_ATTR_TCP_FLAGS", | |
1365 | "tcp_flags", | |
1366 | "0x%04x", | |
1367 | lambda x: False, | |
1368 | True, | |
1369 | ), | |
1370 | ("OVS_KEY_ATTR_UDP", None, None, False, False), | |
1371 | ("OVS_KEY_ATTR_SCTP", None, None, False, False), | |
1372 | ("OVS_KEY_ATTR_ICMP", None, None, False, False), | |
1373 | ("OVS_KEY_ATTR_ICMPV6", None, None, False, False), | |
1374 | ("OVS_KEY_ATTR_ND", None, None, False, False), | |
1375 | ): | |
1376 | v = self.get_attr(field[0]) | |
1377 | if v is not None: | |
1378 | m = None if mask is None else mask.get_attr(field[0]) | |
1379 | if field[4] is False: | |
1380 | print_str += v.dpstr(m, more) | |
1381 | print_str += "," | |
1382 | else: | |
1383 | if m is None or field[3](m): | |
1384 | print_str += field[1] + "(" | |
1385 | print_str += field[2] % v | |
1386 | print_str += ")," | |
1387 | elif more or m != 0: | |
1388 | print_str += field[1] + "(" | |
1389 | print_str += (field[2] % v) + "/" + (field[2] % m) | |
1390 | print_str += ")," | |
1391 | ||
1392 | return print_str | |
1393 | ||
1394 | ||
9feac87b AC |
1395 | class OvsPacket(GenericNetlinkSocket): |
1396 | OVS_PACKET_CMD_MISS = 1 # Flow table miss | |
1397 | OVS_PACKET_CMD_ACTION = 2 # USERSPACE action | |
1398 | OVS_PACKET_CMD_EXECUTE = 3 # Apply actions to packet | |
1399 | ||
1400 | class ovs_packet_msg(ovs_dp_msg): | |
1401 | nla_map = ( | |
1402 | ("OVS_PACKET_ATTR_UNSPEC", "none"), | |
1403 | ("OVS_PACKET_ATTR_PACKET", "array(uint8)"), | |
1404 | ("OVS_PACKET_ATTR_KEY", "ovskey"), | |
1405 | ("OVS_PACKET_ATTR_ACTIONS", "ovsactions"), | |
1406 | ("OVS_PACKET_ATTR_USERDATA", "none"), | |
1407 | ("OVS_PACKET_ATTR_EGRESS_TUN_KEY", "none"), | |
1408 | ("OVS_PACKET_ATTR_UNUSED1", "none"), | |
1409 | ("OVS_PACKET_ATTR_UNUSED2", "none"), | |
1410 | ("OVS_PACKET_ATTR_PROBE", "none"), | |
1411 | ("OVS_PACKET_ATTR_MRU", "uint16"), | |
1412 | ("OVS_PACKET_ATTR_LEN", "uint32"), | |
1413 | ("OVS_PACKET_ATTR_HASH", "uint64"), | |
1414 | ) | |
1415 | ||
1416 | def __init__(self): | |
1417 | GenericNetlinkSocket.__init__(self) | |
1418 | self.bind(OVS_PACKET_FAMILY, OvsPacket.ovs_packet_msg) | |
1419 | ||
1420 | def upcall_handler(self, up=None): | |
1421 | print("listening on upcall packet handler:", self.epid) | |
1422 | while True: | |
1423 | try: | |
1424 | msgs = self.get() | |
1425 | for msg in msgs: | |
1426 | if not up: | |
1427 | continue | |
1428 | if msg["cmd"] == OvsPacket.OVS_PACKET_CMD_MISS: | |
1429 | up.miss(msg) | |
1430 | elif msg["cmd"] == OvsPacket.OVS_PACKET_CMD_ACTION: | |
1431 | up.action(msg) | |
1432 | elif msg["cmd"] == OvsPacket.OVS_PACKET_CMD_EXECUTE: | |
1433 | up.execute(msg) | |
1434 | else: | |
1435 | print("Unkonwn cmd: %d" % msg["cmd"]) | |
1436 | except NetlinkError as ne: | |
1437 | raise ne | |
1438 | ||
1439 | ||
25f16c87 | 1440 | class OvsDatapath(GenericNetlinkSocket): |
25f16c87 AC |
1441 | OVS_DP_F_VPORT_PIDS = 1 << 1 |
1442 | OVS_DP_F_DISPATCH_UPCALL_PER_CPU = 1 << 3 | |
1443 | ||
1444 | class dp_cmd_msg(ovs_dp_msg): | |
1445 | """ | |
1446 | Message class that will be used to communicate with the kernel module. | |
1447 | """ | |
1448 | ||
1449 | nla_map = ( | |
1450 | ("OVS_DP_ATTR_UNSPEC", "none"), | |
1451 | ("OVS_DP_ATTR_NAME", "asciiz"), | |
306dc213 | 1452 | ("OVS_DP_ATTR_UPCALL_PID", "array(uint32)"), |
25f16c87 AC |
1453 | ("OVS_DP_ATTR_STATS", "dpstats"), |
1454 | ("OVS_DP_ATTR_MEGAFLOW_STATS", "megaflowstats"), | |
1455 | ("OVS_DP_ATTR_USER_FEATURES", "uint32"), | |
1456 | ("OVS_DP_ATTR_PAD", "none"), | |
1457 | ("OVS_DP_ATTR_MASKS_CACHE_SIZE", "uint32"), | |
1458 | ("OVS_DP_ATTR_PER_CPU_PIDS", "array(uint32)"), | |
1459 | ) | |
1460 | ||
1461 | class dpstats(nla): | |
1462 | fields = ( | |
1463 | ("hit", "=Q"), | |
1464 | ("missed", "=Q"), | |
1465 | ("lost", "=Q"), | |
1466 | ("flows", "=Q"), | |
1467 | ) | |
1468 | ||
1469 | class megaflowstats(nla): | |
1470 | fields = ( | |
1471 | ("mask_hit", "=Q"), | |
1472 | ("masks", "=I"), | |
1473 | ("padding", "=I"), | |
1474 | ("cache_hits", "=Q"), | |
1475 | ("pad1", "=Q"), | |
1476 | ) | |
1477 | ||
1478 | def __init__(self): | |
1479 | GenericNetlinkSocket.__init__(self) | |
1480 | self.bind(OVS_DATAPATH_FAMILY, OvsDatapath.dp_cmd_msg) | |
1481 | ||
1482 | def info(self, dpname, ifindex=0): | |
1483 | msg = OvsDatapath.dp_cmd_msg() | |
1484 | msg["cmd"] = OVS_DP_CMD_GET | |
1485 | msg["version"] = OVS_DATAPATH_VERSION | |
1486 | msg["reserved"] = 0 | |
1487 | msg["dpifindex"] = ifindex | |
1488 | msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname]) | |
1489 | ||
1490 | try: | |
1491 | reply = self.nlm_request( | |
1492 | msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | |
1493 | ) | |
1494 | reply = reply[0] | |
1495 | except NetlinkError as ne: | |
1496 | if ne.code == errno.ENODEV: | |
1497 | reply = None | |
1498 | else: | |
1499 | raise ne | |
1500 | ||
1501 | return reply | |
1502 | ||
9feac87b AC |
1503 | def create( |
1504 | self, dpname, shouldUpcall=False, versionStr=None, p=OvsPacket() | |
1505 | ): | |
25f16c87 AC |
1506 | msg = OvsDatapath.dp_cmd_msg() |
1507 | msg["cmd"] = OVS_DP_CMD_NEW | |
1508 | if versionStr is None: | |
1509 | msg["version"] = OVS_DATAPATH_VERSION | |
1510 | else: | |
1511 | msg["version"] = int(versionStr.split(":")[0], 0) | |
1512 | msg["reserved"] = 0 | |
1513 | msg["dpifindex"] = 0 | |
1514 | msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname]) | |
1515 | ||
1516 | dpfeatures = 0 | |
1517 | if versionStr is not None and versionStr.find(":") != -1: | |
1518 | dpfeatures = int(versionStr.split(":")[1], 0) | |
1519 | else: | |
9feac87b AC |
1520 | if versionStr is None or versionStr.find(":") == -1: |
1521 | dpfeatures |= OvsDatapath.OVS_DP_F_DISPATCH_UPCALL_PER_CPU | |
1522 | dpfeatures &= ~OvsDatapath.OVS_DP_F_VPORT_PIDS | |
1523 | ||
1524 | nproc = multiprocessing.cpu_count() | |
1525 | procarray = [] | |
1526 | for i in range(1, nproc): | |
1527 | procarray += [int(p.epid)] | |
1528 | msg["attrs"].append(["OVS_DP_ATTR_UPCALL_PID", procarray]) | |
25f16c87 AC |
1529 | msg["attrs"].append(["OVS_DP_ATTR_USER_FEATURES", dpfeatures]) |
1530 | if not shouldUpcall: | |
9feac87b | 1531 | msg["attrs"].append(["OVS_DP_ATTR_UPCALL_PID", [0]]) |
25f16c87 AC |
1532 | |
1533 | try: | |
1534 | reply = self.nlm_request( | |
1535 | msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK | |
1536 | ) | |
1537 | reply = reply[0] | |
1538 | except NetlinkError as ne: | |
1539 | if ne.code == errno.EEXIST: | |
1540 | reply = None | |
1541 | else: | |
1542 | raise ne | |
1543 | ||
1544 | return reply | |
1545 | ||
1546 | def destroy(self, dpname): | |
1547 | msg = OvsDatapath.dp_cmd_msg() | |
1548 | msg["cmd"] = OVS_DP_CMD_DEL | |
1549 | msg["version"] = OVS_DATAPATH_VERSION | |
1550 | msg["reserved"] = 0 | |
1551 | msg["dpifindex"] = 0 | |
1552 | msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname]) | |
1553 | ||
1554 | try: | |
1555 | reply = self.nlm_request( | |
1556 | msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK | |
1557 | ) | |
1558 | reply = reply[0] | |
1559 | except NetlinkError as ne: | |
1560 | if ne.code == errno.ENODEV: | |
1561 | reply = None | |
1562 | else: | |
1563 | raise ne | |
1564 | ||
1565 | return reply | |
1566 | ||
1567 | ||
1568 | class OvsVport(GenericNetlinkSocket): | |
74cc26f4 AC |
1569 | OVS_VPORT_TYPE_NETDEV = 1 |
1570 | OVS_VPORT_TYPE_INTERNAL = 2 | |
1571 | OVS_VPORT_TYPE_GRE = 3 | |
1572 | OVS_VPORT_TYPE_VXLAN = 4 | |
1573 | OVS_VPORT_TYPE_GENEVE = 5 | |
1574 | ||
25f16c87 AC |
1575 | class ovs_vport_msg(ovs_dp_msg): |
1576 | nla_map = ( | |
1577 | ("OVS_VPORT_ATTR_UNSPEC", "none"), | |
1578 | ("OVS_VPORT_ATTR_PORT_NO", "uint32"), | |
1579 | ("OVS_VPORT_ATTR_TYPE", "uint32"), | |
1580 | ("OVS_VPORT_ATTR_NAME", "asciiz"), | |
1581 | ("OVS_VPORT_ATTR_OPTIONS", "none"), | |
1582 | ("OVS_VPORT_ATTR_UPCALL_PID", "array(uint32)"), | |
1583 | ("OVS_VPORT_ATTR_STATS", "vportstats"), | |
1584 | ("OVS_VPORT_ATTR_PAD", "none"), | |
1585 | ("OVS_VPORT_ATTR_IFINDEX", "uint32"), | |
1586 | ("OVS_VPORT_ATTR_NETNSID", "uint32"), | |
1587 | ) | |
1588 | ||
1589 | class vportstats(nla): | |
1590 | fields = ( | |
1591 | ("rx_packets", "=Q"), | |
1592 | ("tx_packets", "=Q"), | |
1593 | ("rx_bytes", "=Q"), | |
1594 | ("tx_bytes", "=Q"), | |
1595 | ("rx_errors", "=Q"), | |
1596 | ("tx_errors", "=Q"), | |
1597 | ("rx_dropped", "=Q"), | |
1598 | ("tx_dropped", "=Q"), | |
1599 | ) | |
1600 | ||
1601 | def type_to_str(vport_type): | |
74cc26f4 | 1602 | if vport_type == OvsVport.OVS_VPORT_TYPE_NETDEV: |
25f16c87 | 1603 | return "netdev" |
74cc26f4 | 1604 | elif vport_type == OvsVport.OVS_VPORT_TYPE_INTERNAL: |
25f16c87 | 1605 | return "internal" |
74cc26f4 | 1606 | elif vport_type == OvsVport.OVS_VPORT_TYPE_GRE: |
25f16c87 | 1607 | return "gre" |
74cc26f4 | 1608 | elif vport_type == OvsVport.OVS_VPORT_TYPE_VXLAN: |
25f16c87 | 1609 | return "vxlan" |
74cc26f4 | 1610 | elif vport_type == OvsVport.OVS_VPORT_TYPE_GENEVE: |
25f16c87 | 1611 | return "geneve" |
74cc26f4 AC |
1612 | raise ValueError("Unknown vport type:%d" % vport_type) |
1613 | ||
1614 | def str_to_type(vport_type): | |
1615 | if vport_type == "netdev": | |
1616 | return OvsVport.OVS_VPORT_TYPE_NETDEV | |
1617 | elif vport_type == "internal": | |
1618 | return OvsVport.OVS_VPORT_TYPE_INTERNAL | |
1619 | elif vport_type == "gre": | |
1620 | return OvsVport.OVS_VPORT_TYPE_INTERNAL | |
1621 | elif vport_type == "vxlan": | |
1622 | return OvsVport.OVS_VPORT_TYPE_VXLAN | |
1623 | elif vport_type == "geneve": | |
1624 | return OvsVport.OVS_VPORT_TYPE_GENEVE | |
1625 | raise ValueError("Unknown vport type: '%s'" % vport_type) | |
25f16c87 | 1626 | |
9feac87b | 1627 | def __init__(self, packet=OvsPacket()): |
25f16c87 AC |
1628 | GenericNetlinkSocket.__init__(self) |
1629 | self.bind(OVS_VPORT_FAMILY, OvsVport.ovs_vport_msg) | |
9feac87b | 1630 | self.upcall_packet = packet |
25f16c87 AC |
1631 | |
1632 | def info(self, vport_name, dpifindex=0, portno=None): | |
1633 | msg = OvsVport.ovs_vport_msg() | |
1634 | ||
1635 | msg["cmd"] = OVS_VPORT_CMD_GET | |
1636 | msg["version"] = OVS_DATAPATH_VERSION | |
1637 | msg["reserved"] = 0 | |
1638 | msg["dpifindex"] = dpifindex | |
1639 | ||
1640 | if portno is None: | |
1641 | msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_name]) | |
1642 | else: | |
1643 | msg["attrs"].append(["OVS_VPORT_ATTR_PORT_NO", portno]) | |
1644 | ||
1645 | try: | |
1646 | reply = self.nlm_request( | |
1647 | msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | |
1648 | ) | |
1649 | reply = reply[0] | |
1650 | except NetlinkError as ne: | |
1651 | if ne.code == errno.ENODEV: | |
1652 | reply = None | |
1653 | else: | |
1654 | raise ne | |
1655 | return reply | |
1656 | ||
74cc26f4 AC |
1657 | def attach(self, dpindex, vport_ifname, ptype): |
1658 | msg = OvsVport.ovs_vport_msg() | |
1659 | ||
1660 | msg["cmd"] = OVS_VPORT_CMD_NEW | |
1661 | msg["version"] = OVS_DATAPATH_VERSION | |
1662 | msg["reserved"] = 0 | |
1663 | msg["dpifindex"] = dpindex | |
1664 | port_type = OvsVport.str_to_type(ptype) | |
1665 | ||
1666 | msg["attrs"].append(["OVS_VPORT_ATTR_TYPE", port_type]) | |
1667 | msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) | |
9feac87b AC |
1668 | msg["attrs"].append( |
1669 | ["OVS_VPORT_ATTR_UPCALL_PID", [self.upcall_packet.epid]] | |
1670 | ) | |
1671 | ||
1672 | try: | |
1673 | reply = self.nlm_request( | |
1674 | msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK | |
1675 | ) | |
1676 | reply = reply[0] | |
1677 | except NetlinkError as ne: | |
1678 | if ne.code == errno.EEXIST: | |
1679 | reply = None | |
1680 | else: | |
1681 | raise ne | |
1682 | return reply | |
1683 | ||
1684 | def reset_upcall(self, dpindex, vport_ifname, p=None): | |
1685 | msg = OvsVport.ovs_vport_msg() | |
1686 | ||
1687 | msg["cmd"] = OVS_VPORT_CMD_SET | |
1688 | msg["version"] = OVS_DATAPATH_VERSION | |
1689 | msg["reserved"] = 0 | |
1690 | msg["dpifindex"] = dpindex | |
1691 | msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) | |
1692 | ||
1693 | if p == None: | |
1694 | p = self.upcall_packet | |
1695 | else: | |
1696 | self.upcall_packet = p | |
1697 | ||
1698 | msg["attrs"].append(["OVS_VPORT_ATTR_UPCALL_PID", [p.epid]]) | |
74cc26f4 AC |
1699 | |
1700 | try: | |
1701 | reply = self.nlm_request( | |
1702 | msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK | |
1703 | ) | |
1704 | reply = reply[0] | |
1705 | except NetlinkError as ne: | |
1706 | raise ne | |
1707 | return reply | |
1708 | ||
1709 | def detach(self, dpindex, vport_ifname): | |
1710 | msg = OvsVport.ovs_vport_msg() | |
1711 | ||
1712 | msg["cmd"] = OVS_VPORT_CMD_DEL | |
1713 | msg["version"] = OVS_DATAPATH_VERSION | |
1714 | msg["reserved"] = 0 | |
1715 | msg["dpifindex"] = dpindex | |
1716 | msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) | |
25f16c87 | 1717 | |
74cc26f4 AC |
1718 | try: |
1719 | reply = self.nlm_request( | |
1720 | msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK | |
1721 | ) | |
1722 | reply = reply[0] | |
1723 | except NetlinkError as ne: | |
1724 | if ne.code == errno.ENODEV: | |
1725 | reply = None | |
1726 | else: | |
1727 | raise ne | |
1728 | return reply | |
1729 | ||
9feac87b AC |
1730 | def upcall_handler(self, handler=None): |
1731 | self.upcall_packet.upcall_handler(handler) | |
1732 | ||
74cc26f4 | 1733 | |
e52b07aa AC |
1734 | class OvsFlow(GenericNetlinkSocket): |
1735 | class ovs_flow_msg(ovs_dp_msg): | |
1736 | nla_map = ( | |
1737 | ("OVS_FLOW_ATTR_UNSPEC", "none"), | |
1738 | ("OVS_FLOW_ATTR_KEY", "ovskey"), | |
1739 | ("OVS_FLOW_ATTR_ACTIONS", "ovsactions"), | |
1740 | ("OVS_FLOW_ATTR_STATS", "flowstats"), | |
1741 | ("OVS_FLOW_ATTR_TCP_FLAGS", "uint8"), | |
1742 | ("OVS_FLOW_ATTR_USED", "uint64"), | |
1743 | ("OVS_FLOW_ATTR_CLEAR", "none"), | |
1744 | ("OVS_FLOW_ATTR_MASK", "ovskey"), | |
1745 | ("OVS_FLOW_ATTR_PROBE", "none"), | |
1746 | ("OVS_FLOW_ATTR_UFID", "array(uint32)"), | |
1747 | ("OVS_FLOW_ATTR_UFID_FLAGS", "uint32"), | |
1748 | ) | |
1749 | ||
1750 | class flowstats(nla): | |
1751 | fields = ( | |
1752 | ("packets", "=Q"), | |
1753 | ("bytes", "=Q"), | |
1754 | ) | |
1755 | ||
1756 | def dpstr(self, more=False): | |
1757 | ufid = self.get_attr("OVS_FLOW_ATTR_UFID") | |
1758 | ufid_str = "" | |
1759 | if ufid is not None: | |
1760 | ufid_str = ( | |
1761 | "ufid:{:08x}-{:04x}-{:04x}-{:04x}-{:04x}{:08x}".format( | |
1762 | ufid[0], | |
1763 | ufid[1] >> 16, | |
1764 | ufid[1] & 0xFFFF, | |
1765 | ufid[2] >> 16, | |
1766 | ufid[2] & 0, | |
1767 | ufid[3], | |
1768 | ) | |
1769 | ) | |
1770 | ||
1771 | key_field = self.get_attr("OVS_FLOW_ATTR_KEY") | |
1772 | keymsg = None | |
1773 | if key_field is not None: | |
1774 | keymsg = key_field | |
1775 | ||
1776 | mask_field = self.get_attr("OVS_FLOW_ATTR_MASK") | |
1777 | maskmsg = None | |
1778 | if mask_field is not None: | |
1779 | maskmsg = mask_field | |
1780 | ||
1781 | acts_field = self.get_attr("OVS_FLOW_ATTR_ACTIONS") | |
1782 | actsmsg = None | |
1783 | if acts_field is not None: | |
1784 | actsmsg = acts_field | |
1785 | ||
1786 | print_str = "" | |
1787 | ||
1788 | if more: | |
1789 | print_str += ufid_str + "," | |
1790 | ||
1791 | if keymsg is not None: | |
1792 | print_str += keymsg.dpstr(maskmsg, more) | |
1793 | ||
1794 | stats = self.get_attr("OVS_FLOW_ATTR_STATS") | |
1795 | if stats is None: | |
1796 | print_str += " packets:0, bytes:0," | |
1797 | else: | |
1798 | print_str += " packets:%d, bytes:%d," % ( | |
1799 | stats["packets"], | |
1800 | stats["bytes"], | |
1801 | ) | |
1802 | ||
1803 | used = self.get_attr("OVS_FLOW_ATTR_USED") | |
1804 | print_str += " used:" | |
1805 | if used is None: | |
1806 | print_str += "never," | |
1807 | else: | |
1808 | used_time = int(used) | |
1809 | cur_time_sec = time.clock_gettime(time.CLOCK_MONOTONIC) | |
1810 | used_time = (cur_time_sec * 1000) - used_time | |
1811 | print_str += "{}s,".format(used_time / 1000) | |
1812 | ||
1813 | print_str += " actions:" | |
1814 | if ( | |
1815 | actsmsg is None | |
1816 | or "attrs" not in actsmsg | |
1817 | or len(actsmsg["attrs"]) == 0 | |
1818 | ): | |
1819 | print_str += "drop" | |
1820 | else: | |
1821 | print_str += actsmsg.dpstr(more) | |
1822 | ||
1823 | return print_str | |
1824 | ||
918423fd AC |
1825 | def parse(self, flowstr, actstr, dpidx=0): |
1826 | OVS_UFID_F_OMIT_KEY = 1 << 0 | |
1827 | OVS_UFID_F_OMIT_MASK = 1 << 1 | |
1828 | OVS_UFID_F_OMIT_ACTIONS = 1 << 2 | |
1829 | ||
1830 | self["cmd"] = 0 | |
1831 | self["version"] = 0 | |
1832 | self["reserved"] = 0 | |
1833 | self["dpifindex"] = 0 | |
1834 | ||
1835 | if flowstr.startswith("ufid:"): | |
1836 | count = 5 | |
1837 | while flowstr[count] != ",": | |
1838 | count += 1 | |
1839 | ufidstr = flowstr[5:count] | |
1840 | flowstr = flowstr[count + 1 :] | |
1841 | else: | |
1842 | ufidstr = str(uuid.uuid4()) | |
1843 | uuidRawObj = uuid.UUID(ufidstr).fields | |
1844 | ||
1845 | self["attrs"].append( | |
1846 | [ | |
1847 | "OVS_FLOW_ATTR_UFID", | |
1848 | [ | |
1849 | uuidRawObj[0], | |
1850 | uuidRawObj[1] << 16 | uuidRawObj[2], | |
1851 | uuidRawObj[3] << 24 | |
1852 | | uuidRawObj[4] << 16 | |
1853 | | uuidRawObj[5] & (0xFF << 32) >> 32, | |
1854 | uuidRawObj[5] & (0xFFFFFFFF), | |
1855 | ], | |
1856 | ] | |
1857 | ) | |
1858 | self["attrs"].append( | |
1859 | [ | |
1860 | "OVS_FLOW_ATTR_UFID_FLAGS", | |
1861 | int( | |
1862 | OVS_UFID_F_OMIT_KEY | |
1863 | | OVS_UFID_F_OMIT_MASK | |
1864 | | OVS_UFID_F_OMIT_ACTIONS | |
1865 | ), | |
1866 | ] | |
1867 | ) | |
1868 | ||
1869 | k = ovskey() | |
1870 | m = ovskey() | |
1871 | k.parse(flowstr, m) | |
1872 | self["attrs"].append(["OVS_FLOW_ATTR_KEY", k]) | |
1873 | self["attrs"].append(["OVS_FLOW_ATTR_MASK", m]) | |
1874 | ||
1875 | a = ovsactions() | |
1876 | a.parse(actstr) | |
1877 | self["attrs"].append(["OVS_FLOW_ATTR_ACTIONS", a]) | |
1878 | ||
e52b07aa AC |
1879 | def __init__(self): |
1880 | GenericNetlinkSocket.__init__(self) | |
1881 | ||
1882 | self.bind(OVS_FLOW_FAMILY, OvsFlow.ovs_flow_msg) | |
1883 | ||
918423fd AC |
1884 | def add_flow(self, dpifindex, flowmsg): |
1885 | """ | |
1886 | Send a new flow message to the kernel. | |
1887 | ||
1888 | dpifindex should be a valid datapath obtained by calling | |
1889 | into the OvsDatapath lookup | |
1890 | ||
1891 | flowmsg is a flow object obtained by calling a dpparse | |
1892 | """ | |
1893 | ||
1894 | flowmsg["cmd"] = OVS_FLOW_CMD_NEW | |
1895 | flowmsg["version"] = OVS_DATAPATH_VERSION | |
1896 | flowmsg["reserved"] = 0 | |
1897 | flowmsg["dpifindex"] = dpifindex | |
1898 | ||
1899 | try: | |
1900 | reply = self.nlm_request( | |
1901 | flowmsg, | |
1902 | msg_type=self.prid, | |
1903 | msg_flags=NLM_F_REQUEST | NLM_F_ACK, | |
1904 | ) | |
1905 | reply = reply[0] | |
1906 | except NetlinkError as ne: | |
1907 | print(flowmsg) | |
1908 | raise ne | |
1909 | return reply | |
1910 | ||
76035fd1 AC |
1911 | def del_flows(self, dpifindex): |
1912 | """ | |
1913 | Send a del message to the kernel that will drop all flows. | |
1914 | ||
1915 | dpifindex should be a valid datapath obtained by calling | |
1916 | into the OvsDatapath lookup | |
1917 | """ | |
1918 | ||
1919 | flowmsg = OvsFlow.ovs_flow_msg() | |
1920 | flowmsg["cmd"] = OVS_FLOW_CMD_DEL | |
1921 | flowmsg["version"] = OVS_DATAPATH_VERSION | |
1922 | flowmsg["reserved"] = 0 | |
1923 | flowmsg["dpifindex"] = dpifindex | |
1924 | ||
1925 | try: | |
1926 | reply = self.nlm_request( | |
1927 | flowmsg, | |
1928 | msg_type=self.prid, | |
1929 | msg_flags=NLM_F_REQUEST | NLM_F_ACK, | |
1930 | ) | |
1931 | reply = reply[0] | |
1932 | except NetlinkError as ne: | |
1933 | print(flowmsg) | |
1934 | raise ne | |
1935 | return reply | |
1936 | ||
e52b07aa AC |
1937 | def dump(self, dpifindex, flowspec=None): |
1938 | """ | |
1939 | Returns a list of messages containing flows. | |
1940 | ||
1941 | dpifindex should be a valid datapath obtained by calling | |
1942 | into the OvsDatapath lookup | |
1943 | ||
1944 | flowpsec is a string which represents a flow in the dpctl | |
1945 | format. | |
1946 | """ | |
1947 | msg = OvsFlow.ovs_flow_msg() | |
1948 | ||
1949 | msg["cmd"] = OVS_FLOW_CMD_GET | |
1950 | msg["version"] = OVS_DATAPATH_VERSION | |
1951 | msg["reserved"] = 0 | |
1952 | msg["dpifindex"] = dpifindex | |
1953 | ||
1954 | msg_flags = NLM_F_REQUEST | NLM_F_ACK | |
1955 | if flowspec is None: | |
1956 | msg_flags |= NLM_F_DUMP | |
1957 | rep = None | |
1958 | ||
1959 | try: | |
1960 | rep = self.nlm_request( | |
1961 | msg, | |
1962 | msg_type=self.prid, | |
1963 | msg_flags=msg_flags, | |
1964 | ) | |
1965 | except NetlinkError as ne: | |
1966 | raise ne | |
1967 | return rep | |
1968 | ||
9feac87b AC |
1969 | def miss(self, packetmsg): |
1970 | seq = packetmsg["header"]["sequence_number"] | |
1971 | keystr = "(none)" | |
1972 | key_field = packetmsg.get_attr("OVS_PACKET_ATTR_KEY") | |
1973 | if key_field is not None: | |
1974 | keystr = key_field.dpstr(None, True) | |
1975 | ||
1976 | pktdata = packetmsg.get_attr("OVS_PACKET_ATTR_PACKET") | |
1977 | pktpres = "yes" if pktdata is not None else "no" | |
1978 | ||
1979 | print("MISS upcall[%d/%s]: %s" % (seq, pktpres, keystr), flush=True) | |
1980 | ||
1981 | def execute(self, packetmsg): | |
1982 | print("userspace execute command") | |
1983 | ||
1984 | def action(self, packetmsg): | |
1985 | print("userspace action command") | |
1986 | ||
e52b07aa | 1987 | |
74cc26f4 | 1988 | def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()): |
25f16c87 AC |
1989 | dp_name = dp_lookup_rep.get_attr("OVS_DP_ATTR_NAME") |
1990 | base_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_STATS") | |
1991 | megaflow_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_MEGAFLOW_STATS") | |
1992 | user_features = dp_lookup_rep.get_attr("OVS_DP_ATTR_USER_FEATURES") | |
1993 | masks_cache_size = dp_lookup_rep.get_attr("OVS_DP_ATTR_MASKS_CACHE_SIZE") | |
1994 | ||
1995 | print("%s:" % dp_name) | |
1996 | print( | |
1997 | " lookups: hit:%d missed:%d lost:%d" | |
1998 | % (base_stats["hit"], base_stats["missed"], base_stats["lost"]) | |
1999 | ) | |
2000 | print(" flows:%d" % base_stats["flows"]) | |
2001 | pkts = base_stats["hit"] + base_stats["missed"] | |
2002 | avg = (megaflow_stats["mask_hit"] / pkts) if pkts != 0 else 0.0 | |
2003 | print( | |
2004 | " masks: hit:%d total:%d hit/pkt:%f" | |
2005 | % (megaflow_stats["mask_hit"], megaflow_stats["masks"], avg) | |
2006 | ) | |
2007 | print(" caches:") | |
2008 | print(" masks-cache: size:%d" % masks_cache_size) | |
2009 | ||
2010 | if user_features is not None: | |
2011 | print(" features: 0x%X" % user_features) | |
2012 | ||
2013 | # port print out | |
25f16c87 AC |
2014 | for iface in ndb.interfaces: |
2015 | rep = vpl.info(iface.ifname, ifindex) | |
2016 | if rep is not None: | |
2017 | print( | |
2018 | " port %d: %s (%s)" | |
2019 | % ( | |
2020 | rep.get_attr("OVS_VPORT_ATTR_PORT_NO"), | |
2021 | rep.get_attr("OVS_VPORT_ATTR_NAME"), | |
2022 | OvsVport.type_to_str(rep.get_attr("OVS_VPORT_ATTR_TYPE")), | |
2023 | ) | |
2024 | ) | |
2025 | ||
2026 | ||
2027 | def main(argv): | |
e52b07aa AC |
2028 | nlmsg_atoms.ovskey = ovskey |
2029 | nlmsg_atoms.ovsactions = ovsactions | |
2030 | ||
92e37f20 AC |
2031 | # version check for pyroute2 |
2032 | prverscheck = pyroute2.__version__.split(".") | |
2033 | if int(prverscheck[0]) == 0 and int(prverscheck[1]) < 6: | |
2034 | print("Need to upgrade the python pyroute2 package to >= 0.6.") | |
2035 | sys.exit(0) | |
2036 | ||
25f16c87 AC |
2037 | parser = argparse.ArgumentParser() |
2038 | parser.add_argument( | |
2039 | "-v", | |
2040 | "--verbose", | |
2041 | action="count", | |
2042 | help="Increment 'verbose' output counter.", | |
e52b07aa | 2043 | default=0, |
25f16c87 AC |
2044 | ) |
2045 | subparsers = parser.add_subparsers() | |
2046 | ||
2047 | showdpcmd = subparsers.add_parser("show") | |
2048 | showdpcmd.add_argument( | |
2049 | "showdp", metavar="N", type=str, nargs="?", help="Datapath Name" | |
2050 | ) | |
2051 | ||
2052 | adddpcmd = subparsers.add_parser("add-dp") | |
2053 | adddpcmd.add_argument("adddp", help="Datapath Name") | |
2054 | adddpcmd.add_argument( | |
2055 | "-u", | |
2056 | "--upcall", | |
2057 | action="store_true", | |
2058 | help="Leave open a reader for upcalls", | |
2059 | ) | |
2060 | adddpcmd.add_argument( | |
2061 | "-V", | |
2062 | "--versioning", | |
2063 | required=False, | |
2064 | help="Specify a custom version / feature string", | |
2065 | ) | |
2066 | ||
2067 | deldpcmd = subparsers.add_parser("del-dp") | |
2068 | deldpcmd.add_argument("deldp", help="Datapath Name") | |
2069 | ||
74cc26f4 AC |
2070 | addifcmd = subparsers.add_parser("add-if") |
2071 | addifcmd.add_argument("dpname", help="Datapath Name") | |
2072 | addifcmd.add_argument("addif", help="Interface name for adding") | |
9feac87b AC |
2073 | addifcmd.add_argument( |
2074 | "-u", | |
2075 | "--upcall", | |
2076 | action="store_true", | |
2077 | help="Leave open a reader for upcalls", | |
2078 | ) | |
74cc26f4 AC |
2079 | addifcmd.add_argument( |
2080 | "-t", | |
2081 | "--ptype", | |
2082 | type=str, | |
2083 | default="netdev", | |
2084 | choices=["netdev", "internal"], | |
2085 | help="Interface type (default netdev)", | |
2086 | ) | |
2087 | delifcmd = subparsers.add_parser("del-if") | |
2088 | delifcmd.add_argument("dpname", help="Datapath Name") | |
2089 | delifcmd.add_argument("delif", help="Interface name for adding") | |
2090 | ||
e52b07aa AC |
2091 | dumpflcmd = subparsers.add_parser("dump-flows") |
2092 | dumpflcmd.add_argument("dumpdp", help="Datapath Name") | |
2093 | ||
918423fd AC |
2094 | addflcmd = subparsers.add_parser("add-flow") |
2095 | addflcmd.add_argument("flbr", help="Datapath name") | |
2096 | addflcmd.add_argument("flow", help="Flow specification") | |
2097 | addflcmd.add_argument("acts", help="Flow actions") | |
2098 | ||
76035fd1 AC |
2099 | delfscmd = subparsers.add_parser("del-flows") |
2100 | delfscmd.add_argument("flsbr", help="Datapath name") | |
2101 | ||
25f16c87 AC |
2102 | args = parser.parse_args() |
2103 | ||
e52b07aa AC |
2104 | if args.verbose > 0: |
2105 | if args.verbose > 1: | |
2106 | logging.basicConfig(level=logging.DEBUG) | |
2107 | ||
9feac87b | 2108 | ovspk = OvsPacket() |
25f16c87 | 2109 | ovsdp = OvsDatapath() |
9feac87b | 2110 | ovsvp = OvsVport(ovspk) |
e52b07aa | 2111 | ovsflow = OvsFlow() |
25f16c87 AC |
2112 | ndb = NDB() |
2113 | ||
2114 | if hasattr(args, "showdp"): | |
2115 | found = False | |
2116 | for iface in ndb.interfaces: | |
2117 | rep = None | |
2118 | if args.showdp is None: | |
2119 | rep = ovsdp.info(iface.ifname, 0) | |
2120 | elif args.showdp == iface.ifname: | |
2121 | rep = ovsdp.info(iface.ifname, 0) | |
2122 | ||
2123 | if rep is not None: | |
2124 | found = True | |
74cc26f4 | 2125 | print_ovsdp_full(rep, iface.index, ndb, ovsvp) |
25f16c87 AC |
2126 | |
2127 | if not found: | |
2128 | msg = "No DP found" | |
2129 | if args.showdp is not None: | |
2130 | msg += ":'%s'" % args.showdp | |
2131 | print(msg) | |
2132 | elif hasattr(args, "adddp"): | |
9feac87b | 2133 | rep = ovsdp.create(args.adddp, args.upcall, args.versioning, ovspk) |
25f16c87 AC |
2134 | if rep is None: |
2135 | print("DP '%s' already exists" % args.adddp) | |
2136 | else: | |
2137 | print("DP '%s' added" % args.adddp) | |
9feac87b AC |
2138 | if args.upcall: |
2139 | ovspk.upcall_handler(ovsflow) | |
25f16c87 AC |
2140 | elif hasattr(args, "deldp"): |
2141 | ovsdp.destroy(args.deldp) | |
74cc26f4 AC |
2142 | elif hasattr(args, "addif"): |
2143 | rep = ovsdp.info(args.dpname, 0) | |
2144 | if rep is None: | |
2145 | print("DP '%s' not found." % args.dpname) | |
2146 | return 1 | |
9feac87b | 2147 | dpindex = rep["dpifindex"] |
74cc26f4 AC |
2148 | rep = ovsvp.attach(rep["dpifindex"], args.addif, args.ptype) |
2149 | msg = "vport '%s'" % args.addif | |
2150 | if rep and rep["header"]["error"] is None: | |
2151 | msg += " added." | |
2152 | else: | |
2153 | msg += " failed to add." | |
9feac87b AC |
2154 | if args.upcall: |
2155 | if rep is None: | |
2156 | rep = ovsvp.reset_upcall(dpindex, args.addif, ovspk) | |
2157 | ovsvp.upcall_handler(ovsflow) | |
74cc26f4 AC |
2158 | elif hasattr(args, "delif"): |
2159 | rep = ovsdp.info(args.dpname, 0) | |
2160 | if rep is None: | |
2161 | print("DP '%s' not found." % args.dpname) | |
2162 | return 1 | |
2163 | rep = ovsvp.detach(rep["dpifindex"], args.delif) | |
2164 | msg = "vport '%s'" % args.delif | |
2165 | if rep and rep["header"]["error"] is None: | |
2166 | msg += " removed." | |
2167 | else: | |
2168 | msg += " failed to remove." | |
e52b07aa AC |
2169 | elif hasattr(args, "dumpdp"): |
2170 | rep = ovsdp.info(args.dumpdp, 0) | |
2171 | if rep is None: | |
2172 | print("DP '%s' not found." % args.dumpdp) | |
2173 | return 1 | |
2174 | rep = ovsflow.dump(rep["dpifindex"]) | |
2175 | for flow in rep: | |
2176 | print(flow.dpstr(True if args.verbose > 0 else False)) | |
918423fd AC |
2177 | elif hasattr(args, "flbr"): |
2178 | rep = ovsdp.info(args.flbr, 0) | |
2179 | if rep is None: | |
2180 | print("DP '%s' not found." % args.flbr) | |
2181 | return 1 | |
2182 | flow = OvsFlow.ovs_flow_msg() | |
2183 | flow.parse(args.flow, args.acts, rep["dpifindex"]) | |
2184 | ovsflow.add_flow(rep["dpifindex"], flow) | |
76035fd1 AC |
2185 | elif hasattr(args, "flsbr"): |
2186 | rep = ovsdp.info(args.flsbr, 0) | |
2187 | if rep is None: | |
2188 | print("DP '%s' not found." % args.flsbr) | |
2189 | ovsflow.del_flows(rep["dpifindex"]) | |
25f16c87 AC |
2190 | |
2191 | return 0 | |
2192 | ||
2193 | ||
2194 | if __name__ == "__main__": | |
2195 | sys.exit(main(sys.argv)) |