]>
Commit | Line | Data |
---|---|---|
4acd1e87 BP |
1 | /* |
2 | * Copyright (c) 2016 Nicira, Inc. | |
3 | * | |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at: | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | ||
17 | #include <config.h> | |
18 | ||
19 | #include <getopt.h> | |
20 | ||
21 | #include "command-line.h" | |
22 | #include "compiler.h" | |
23 | #include "daemon.h" | |
24 | #include "dirs.h" | |
25 | #include "fatal-signal.h" | |
26 | #include "flow.h" | |
27 | #include "nx-match.h" | |
28 | #include "openvswitch/dynamic-string.h" | |
29 | #include "openvswitch/ofp-actions.h" | |
d444a914 BP |
30 | #include "openvswitch/ofp-print.h" |
31 | #include "openvswitch/vconn.h" | |
4acd1e87 BP |
32 | #include "openvswitch/vlog.h" |
33 | #include "ovn/actions.h" | |
34 | #include "ovn/expr.h" | |
35 | #include "ovn/lex.h" | |
36 | #include "ovn/lib/logical-fields.h" | |
37 | #include "ovn/lib/ovn-sb-idl.h" | |
1585ff63 | 38 | #include "ovn/lib/ovn-dhcp.h" |
4acd1e87 BP |
39 | #include "ovn/lib/ovn-util.h" |
40 | #include "ovsdb-idl.h" | |
41 | #include "poll-loop.h" | |
42 | #include "stream-ssl.h" | |
43 | #include "stream.h" | |
44 | #include "unixctl.h" | |
45 | #include "util.h" | |
46 | ||
47 | VLOG_DEFINE_THIS_MODULE(ovntrace); | |
48 | ||
49 | /* --db: The database server to contact. */ | |
50 | static const char *db; | |
51 | ||
52 | /* --unixctl-path: Path to use for unixctl server, for "monitor" and "snoop" | |
53 | commands. */ | |
54 | static char *unixctl_path; | |
55 | ||
56 | /* The southbound database. */ | |
57 | static struct ovsdb_idl *ovnsb_idl; | |
58 | ||
59 | /* --detailed: Show a detailed, table-by-table trace. */ | |
60 | static bool detailed; | |
61 | ||
62 | /* --summary: Show a trace that omits table information. */ | |
63 | static bool summary; | |
64 | ||
65 | /* --minimal: Show a trace with only minimal information. */ | |
66 | static bool minimal; | |
67 | ||
d444a914 BP |
68 | /* --ovs: OVS instance to contact to get OpenFlow flows. */ |
69 | static const char *ovs; | |
70 | static struct vconn *vconn; | |
71 | ||
4acd1e87 BP |
72 | OVS_NO_RETURN static void usage(void); |
73 | static void parse_options(int argc, char *argv[]); | |
74 | static char *trace(const char *datapath, const char *flow); | |
75 | static void read_db(void); | |
76 | static unixctl_cb_func ovntrace_exit; | |
77 | static unixctl_cb_func ovntrace_trace; | |
78 | ||
79 | int | |
80 | main(int argc, char *argv[]) | |
81 | { | |
82 | set_program_name(argv[0]); | |
fe559381 | 83 | service_start(&argc, &argv); |
4acd1e87 BP |
84 | fatal_ignore_sigpipe(); |
85 | vlog_set_levels_from_string_assert("reconnect:warn"); | |
4acd1e87 BP |
86 | |
87 | /* Parse command line. */ | |
88 | parse_options(argc, argv); | |
89 | argc -= optind; | |
90 | argv += optind; | |
91 | ||
92 | if (get_detach()) { | |
93 | if (argc != 0) { | |
94 | ovs_fatal(0, "non-option arguments not supported with --detach " | |
f850c889 | 95 | "(use --help for help)"); |
4acd1e87 BP |
96 | } |
97 | } else { | |
98 | if (argc != 2) { | |
99 | ovs_fatal(0, "exactly two non-option arguments are required " | |
f850c889 | 100 | "(use --help for help)"); |
4acd1e87 BP |
101 | } |
102 | } | |
103 | ||
104 | struct unixctl_server *server = NULL; | |
105 | bool exiting = false; | |
106 | if (get_detach()) { | |
107 | daemonize_start(false); | |
108 | int error = unixctl_server_create(unixctl_path, &server); | |
109 | if (error) { | |
110 | ovs_fatal(error, "failed to create unixctl server"); | |
111 | } | |
112 | unixctl_command_register("exit", "", 0, 0, ovntrace_exit, &exiting); | |
113 | unixctl_command_register("trace", "[OPTIONS] DATAPATH MICROFLOW", | |
114 | 2, INT_MAX, ovntrace_trace, NULL); | |
115 | } | |
116 | ovnsb_idl = ovsdb_idl_create(db, &sbrec_idl_class, true, false); | |
117 | ||
118 | bool already_read = false; | |
119 | for (;;) { | |
120 | ovsdb_idl_run(ovnsb_idl); | |
121 | unixctl_server_run(server); | |
122 | if (!ovsdb_idl_is_alive(ovnsb_idl)) { | |
123 | int retval = ovsdb_idl_get_last_error(ovnsb_idl); | |
124 | ovs_fatal(0, "%s: database connection failed (%s)", | |
125 | db, ovs_retval_to_string(retval)); | |
126 | } | |
127 | ||
128 | if (ovsdb_idl_has_ever_connected(ovnsb_idl)) { | |
129 | if (!already_read) { | |
130 | already_read = true; | |
131 | read_db(); | |
132 | } | |
133 | ||
134 | daemonize_complete(); | |
135 | if (!get_detach()) { | |
136 | char *output = trace(argv[0], argv[1]); | |
137 | fputs(output, stdout); | |
138 | free(output); | |
139 | return 0; | |
140 | } | |
141 | } | |
142 | ||
143 | if (exiting) { | |
144 | break; | |
145 | } | |
146 | ovsdb_idl_wait(ovnsb_idl); | |
147 | unixctl_server_wait(server); | |
148 | poll_block(); | |
149 | } | |
150 | } | |
151 | ||
d444a914 BP |
152 | static char * |
153 | default_ovs(void) | |
154 | { | |
155 | return xasprintf("unix:%s/br-int.mgmt", ovs_rundir()); | |
156 | } | |
157 | ||
4acd1e87 BP |
158 | static void |
159 | parse_options(int argc, char *argv[]) | |
160 | { | |
161 | enum { | |
162 | OPT_DB = UCHAR_MAX + 1, | |
163 | OPT_UNIXCTL, | |
164 | OPT_DETAILED, | |
165 | OPT_SUMMARY, | |
166 | OPT_MINIMAL, | |
167 | OPT_ALL, | |
d444a914 | 168 | OPT_OVS, |
4acd1e87 | 169 | DAEMON_OPTION_ENUMS, |
e18a1d08 | 170 | SSL_OPTION_ENUMS, |
4acd1e87 BP |
171 | VLOG_OPTION_ENUMS |
172 | }; | |
173 | static const struct option long_options[] = { | |
174 | {"db", required_argument, NULL, OPT_DB}, | |
175 | {"unixctl", required_argument, NULL, OPT_UNIXCTL}, | |
176 | {"detailed", no_argument, NULL, OPT_DETAILED}, | |
177 | {"summary", no_argument, NULL, OPT_SUMMARY}, | |
178 | {"minimal", no_argument, NULL, OPT_MINIMAL}, | |
179 | {"all", no_argument, NULL, OPT_ALL}, | |
d444a914 | 180 | {"ovs", optional_argument, NULL, OPT_OVS}, |
4acd1e87 BP |
181 | {"help", no_argument, NULL, 'h'}, |
182 | {"version", no_argument, NULL, 'V'}, | |
183 | DAEMON_LONG_OPTIONS, | |
184 | VLOG_LONG_OPTIONS, | |
185 | STREAM_SSL_LONG_OPTIONS, | |
186 | {NULL, 0, NULL, 0}, | |
187 | }; | |
188 | char *short_options = ovs_cmdl_long_options_to_short_options(long_options); | |
189 | ||
190 | for (;;) { | |
191 | int idx; | |
192 | int c; | |
193 | ||
194 | c = getopt_long(argc, argv, short_options, long_options, &idx); | |
195 | if (c == -1) { | |
196 | break; | |
197 | } | |
198 | ||
199 | switch (c) { | |
200 | case OPT_DB: | |
201 | db = optarg; | |
202 | break; | |
203 | ||
204 | case OPT_UNIXCTL: | |
205 | unixctl_path = optarg; | |
206 | break; | |
207 | ||
208 | case OPT_DETAILED: | |
209 | detailed = true; | |
210 | break; | |
211 | ||
212 | case OPT_SUMMARY: | |
213 | summary = true; | |
214 | break; | |
215 | ||
216 | case OPT_MINIMAL: | |
217 | minimal = true; | |
218 | break; | |
219 | ||
220 | case OPT_ALL: | |
221 | detailed = summary = minimal = true; | |
222 | break; | |
223 | ||
d444a914 BP |
224 | case OPT_OVS: |
225 | ovs = optarg ? optarg : default_ovs(); | |
226 | break; | |
227 | ||
4acd1e87 BP |
228 | case 'h': |
229 | usage(); | |
230 | ||
231 | case 'V': | |
232 | ovs_print_version(0, 0); | |
233 | printf("DB Schema %s\n", sbrec_get_db_version()); | |
234 | exit(EXIT_SUCCESS); | |
235 | ||
236 | DAEMON_OPTION_HANDLERS | |
237 | VLOG_OPTION_HANDLERS | |
238 | STREAM_SSL_OPTION_HANDLERS | |
239 | ||
240 | case '?': | |
241 | exit(EXIT_FAILURE); | |
242 | ||
243 | default: | |
244 | abort(); | |
245 | } | |
246 | } | |
247 | free(short_options); | |
248 | ||
249 | if (!db) { | |
250 | db = default_sb_db(); | |
251 | } | |
252 | ||
253 | if (!detailed && !summary && !minimal) { | |
254 | detailed = true; | |
255 | } | |
256 | } | |
257 | ||
258 | static void | |
259 | usage(void) | |
260 | { | |
261 | printf("\ | |
262 | %s: OVN trace utility\n\ | |
263 | usage: %s [OPTIONS] DATAPATH MICROFLOW\n\ | |
264 | %s [OPTIONS] --detach\n\ | |
265 | \n\ | |
d444a914 | 266 | Output format options:\n\ |
2e6d1f4a BP |
267 | --detailed table-by-table \"backtrace\" (default)\n\ |
268 | --summary less detailed, more parseable\n\ | |
269 | --minimal minimum to explain externally visible behavior\n\ | |
270 | --all provide all forms of output\n", | |
271 | program_name, program_name, program_name); | |
4acd1e87 BP |
272 | daemon_usage(); |
273 | vlog_usage(); | |
274 | printf("\n\ | |
275 | Other options:\n\ | |
2e6d1f4a BP |
276 | --db=DATABASE connect to DATABASE\n\ |
277 | (default: %s)\n\ | |
d444a914 BP |
278 | --ovs[=REMOTE] obtain corresponding OpenFlow flows from REMOTE\n\ |
279 | (default: %s)\n\ | |
2e6d1f4a BP |
280 | --unixctl=SOCKET set control socket name\n\ |
281 | -h, --help display this help message\n\ | |
282 | -V, --version display version information\n", | |
d444a914 | 283 | default_sb_db(), default_ovs()); |
4acd1e87 | 284 | stream_usage("database", true, true, false); |
d444a914 | 285 | vconn_usage(true, false, false); |
4acd1e87 BP |
286 | exit(EXIT_SUCCESS); |
287 | } | |
288 | \f | |
289 | struct ovntrace_datapath { | |
290 | struct hmap_node sb_uuid_node; | |
291 | struct uuid sb_uuid; | |
292 | struct uuid nb_uuid; | |
293 | char *name; | |
294 | uint32_t tunnel_key; | |
295 | ||
296 | struct ovs_list mcgroups; /* Contains "struct ovntrace_mcgroup"s. */ | |
297 | ||
298 | struct ovntrace_flow **flows; | |
299 | size_t n_flows, allocated_flows; | |
300 | ||
301 | struct hmap mac_bindings; /* Contains "struct ovntrace_mac_binding"s. */ | |
302 | }; | |
303 | ||
304 | struct ovntrace_port { | |
305 | struct ovntrace_datapath *dp; | |
306 | char *name; | |
307 | char *type; | |
308 | uint16_t tunnel_key; | |
309 | struct ovntrace_port *peer; /* Patch ports only. */ | |
310 | }; | |
311 | ||
312 | struct ovntrace_mcgroup { | |
313 | struct ovs_list list_node; /* In struct ovntrace_datapath's 'mcgroups'. */ | |
314 | ||
315 | struct ovntrace_datapath *dp; | |
316 | char *name; | |
317 | ||
318 | uint16_t tunnel_key; | |
319 | ||
320 | struct ovntrace_port **ports; | |
321 | size_t n_ports; | |
322 | }; | |
323 | ||
324 | enum ovntrace_pipeline { P_INGRESS, P_EGRESS }; | |
325 | ||
326 | struct ovntrace_flow { | |
d444a914 | 327 | struct uuid uuid; |
4acd1e87 BP |
328 | enum ovntrace_pipeline pipeline; |
329 | int table_id; | |
330 | char *stage_name; | |
d8026bbf | 331 | char *source; |
4acd1e87 BP |
332 | int priority; |
333 | char *match_s; | |
334 | struct expr *match; | |
335 | struct ovnact *ovnacts; | |
336 | size_t ovnacts_len; | |
337 | }; | |
338 | ||
339 | struct ovntrace_mac_binding { | |
340 | struct hmap_node node; | |
341 | uint16_t port_key; | |
342 | struct in6_addr ip; | |
343 | struct eth_addr mac; | |
344 | }; | |
345 | ||
346 | static inline uint32_t | |
347 | hash_mac_binding(uint16_t port_key, const struct in6_addr *ip) | |
348 | { | |
349 | return hash_bytes(ip, sizeof *ip, port_key); | |
350 | } | |
351 | ||
352 | /* Every ovntrace_datapath, by southbound Datapath_Binding record UUID. */ | |
353 | static struct hmap datapaths; | |
354 | ||
355 | /* Every ovntrace_port, by name. */ | |
356 | static struct shash ports; | |
357 | ||
358 | /* Symbol table for expressions and actions. */ | |
359 | static struct shash symtab; | |
360 | ||
361 | /* Address sets. */ | |
362 | static struct shash address_sets; | |
363 | ||
1585ff63 BP |
364 | /* DHCP options. */ |
365 | static struct hmap dhcp_opts; /* Contains "struct dhcp_opts_map"s. */ | |
366 | static struct hmap dhcpv6_opts; /* Contains "struct dhcp_opts_map"s. */ | |
367 | ||
4acd1e87 BP |
368 | static struct ovntrace_datapath * |
369 | ovntrace_datapath_find_by_sb_uuid(const struct uuid *sb_uuid) | |
370 | { | |
371 | struct ovntrace_datapath *dp; | |
372 | HMAP_FOR_EACH_WITH_HASH (dp, sb_uuid_node, uuid_hash(sb_uuid), | |
373 | &datapaths) { | |
374 | if (uuid_equals(&dp->sb_uuid, sb_uuid)) { | |
375 | return dp; | |
376 | } | |
377 | } | |
378 | return NULL; | |
379 | } | |
380 | ||
381 | static const struct ovntrace_datapath * | |
382 | ovntrace_datapath_find_by_name(const char *name) | |
383 | { | |
384 | struct uuid uuid; | |
385 | bool is_uuid = uuid_from_string(&uuid, name); | |
386 | ||
387 | struct ovntrace_datapath *dp; | |
388 | HMAP_FOR_EACH (dp, sb_uuid_node, &datapaths) { | |
389 | if (!strcmp(name, dp->name) | |
390 | || (is_uuid | |
391 | && (uuid_equals(&uuid, &dp->sb_uuid) || | |
392 | uuid_equals(&uuid, &dp->nb_uuid)))) { | |
393 | return dp; | |
394 | } | |
395 | } | |
396 | return NULL; | |
397 | } | |
398 | ||
399 | static const struct ovntrace_port * | |
400 | ovntrace_port_find_by_key(const struct ovntrace_datapath *dp, | |
401 | uint16_t tunnel_key) | |
402 | { | |
403 | const struct shash_node *node; | |
404 | SHASH_FOR_EACH (node, &ports) { | |
405 | const struct ovntrace_port *port = node->data; | |
406 | if (port->dp == dp && port->tunnel_key == tunnel_key) { | |
407 | return port; | |
408 | } | |
409 | } | |
410 | return NULL; | |
411 | } | |
412 | ||
413 | static const struct ovntrace_mcgroup * | |
414 | ovntrace_mcgroup_find_by_key(const struct ovntrace_datapath *dp, | |
415 | uint16_t tunnel_key) | |
416 | { | |
417 | const struct ovntrace_mcgroup *mcgroup; | |
418 | LIST_FOR_EACH (mcgroup, list_node, &dp->mcgroups) { | |
419 | if (mcgroup->tunnel_key == tunnel_key) { | |
420 | return mcgroup; | |
421 | } | |
422 | } | |
423 | return NULL; | |
424 | } | |
425 | ||
426 | static const struct ovntrace_mcgroup * | |
427 | ovntrace_mcgroup_find_by_name(const struct ovntrace_datapath *dp, | |
428 | const char *name) | |
429 | { | |
430 | const struct ovntrace_mcgroup *mcgroup; | |
431 | LIST_FOR_EACH (mcgroup, list_node, &dp->mcgroups) { | |
432 | if (!strcmp(mcgroup->name, name)) { | |
433 | return mcgroup; | |
434 | } | |
435 | } | |
436 | return NULL; | |
437 | } | |
438 | ||
439 | static const struct ovntrace_mac_binding * | |
440 | ovntrace_mac_binding_find(const struct ovntrace_datapath *dp, | |
441 | uint16_t port_key, const struct in6_addr *ip) | |
442 | { | |
443 | const struct ovntrace_mac_binding *bind; | |
444 | HMAP_FOR_EACH_WITH_HASH (bind, node, hash_mac_binding(port_key, ip), | |
445 | &dp->mac_bindings) { | |
446 | if (bind->port_key == port_key && ipv6_addr_equals(ip, &bind->ip)) { | |
447 | return bind; | |
448 | } | |
449 | } | |
450 | return NULL; | |
451 | } | |
452 | ||
453 | static void | |
454 | read_datapaths(void) | |
455 | { | |
456 | hmap_init(&datapaths); | |
457 | const struct sbrec_datapath_binding *sbdb; | |
458 | SBREC_DATAPATH_BINDING_FOR_EACH (sbdb, ovnsb_idl) { | |
459 | struct ovntrace_datapath *dp = xzalloc(sizeof *dp); | |
460 | const struct smap *ids = &sbdb->external_ids; | |
461 | ||
462 | dp->sb_uuid = sbdb->header_.uuid; | |
463 | if (!smap_get_uuid(ids, "logical-switch", &dp->nb_uuid) && | |
464 | !smap_get_uuid(ids, "logical-router", &dp->nb_uuid)) { | |
465 | dp->nb_uuid = dp->sb_uuid; | |
466 | } | |
467 | ||
468 | const char *name = smap_get(ids, "name"); | |
469 | dp->name = (name | |
470 | ? xstrdup(name) | |
471 | : xasprintf(UUID_FMT, UUID_ARGS(&dp->nb_uuid))); | |
472 | ||
473 | dp->tunnel_key = sbdb->tunnel_key; | |
474 | ||
475 | ovs_list_init(&dp->mcgroups); | |
476 | hmap_init(&dp->mac_bindings); | |
477 | ||
478 | hmap_insert(&datapaths, &dp->sb_uuid_node, uuid_hash(&dp->sb_uuid)); | |
479 | } | |
480 | } | |
481 | ||
482 | static void | |
483 | read_ports(void) | |
484 | { | |
485 | shash_init(&ports); | |
486 | const struct sbrec_port_binding *sbpb; | |
487 | SBREC_PORT_BINDING_FOR_EACH (sbpb, ovnsb_idl) { | |
488 | const char *port_name = sbpb->logical_port; | |
489 | struct ovntrace_datapath *dp | |
490 | = ovntrace_datapath_find_by_sb_uuid(&sbpb->datapath->header_.uuid); | |
491 | if (!dp) { | |
492 | VLOG_WARN("logical port %s missing datapath", port_name); | |
493 | continue; | |
494 | } | |
495 | ||
496 | struct ovntrace_port *port = xzalloc(sizeof *port); | |
497 | if (!shash_add_once(&ports, port_name, port)) { | |
498 | VLOG_WARN("duplicate logical port name %s", port_name); | |
499 | free(port); | |
500 | continue; | |
501 | } | |
502 | port->dp = dp; | |
503 | port->name = xstrdup(port_name); | |
504 | port->type = xstrdup(sbpb->type); | |
505 | port->tunnel_key = sbpb->tunnel_key; | |
506 | ||
507 | if (!strcmp(sbpb->type, "patch")) { | |
508 | const char *peer_name = smap_get(&sbpb->options, "peer"); | |
509 | if (peer_name) { | |
510 | struct ovntrace_port *peer | |
511 | = shash_find_data(&ports, peer_name); | |
512 | if (peer) { | |
513 | port->peer = peer; | |
514 | port->peer->peer = port; | |
515 | } | |
516 | } | |
517 | } | |
518 | } | |
519 | } | |
520 | ||
521 | static int | |
522 | compare_port(const void *a_, const void *b_) | |
523 | { | |
524 | struct ovntrace_port *const *ap = a_; | |
525 | struct ovntrace_port *const *bp = b_; | |
526 | const struct ovntrace_port *a = *ap; | |
527 | const struct ovntrace_port *b = *bp; | |
528 | ||
529 | return strcmp(a->name, b->name); | |
530 | } | |
531 | ||
532 | static void | |
533 | read_mcgroups(void) | |
534 | { | |
535 | const struct sbrec_multicast_group *sbmg; | |
536 | SBREC_MULTICAST_GROUP_FOR_EACH (sbmg, ovnsb_idl) { | |
537 | struct ovntrace_datapath *dp | |
538 | = ovntrace_datapath_find_by_sb_uuid(&sbmg->datapath->header_.uuid); | |
539 | if (!dp) { | |
540 | VLOG_WARN("logical multicast group %s missing datapath", | |
541 | sbmg->name); | |
542 | continue; | |
543 | } | |
544 | ||
545 | struct ovntrace_mcgroup *mcgroup = xzalloc(sizeof *mcgroup); | |
546 | ovs_list_push_back(&dp->mcgroups, &mcgroup->list_node); | |
547 | mcgroup->dp = dp; | |
548 | mcgroup->tunnel_key = sbmg->tunnel_key; | |
549 | mcgroup->name = xstrdup(sbmg->name); | |
550 | mcgroup->ports = xmalloc(sbmg->n_ports * sizeof *mcgroup->ports); | |
551 | for (size_t i = 0; i < sbmg->n_ports; i++) { | |
552 | const char *port_name = sbmg->ports[i]->logical_port; | |
553 | struct ovntrace_port *p = shash_find_data(&ports, port_name); | |
554 | if (!p) { | |
555 | VLOG_WARN("missing port %s", port_name); | |
556 | continue; | |
557 | } | |
558 | if (!uuid_equals(&sbmg->ports[i]->datapath->header_.uuid, | |
559 | &p->dp->sb_uuid)) { | |
560 | VLOG_WARN("multicast group %s in datapath %s contains " | |
561 | "port %s outside that datapath", | |
562 | mcgroup->name, mcgroup->dp->name, port_name); | |
563 | continue; | |
564 | } | |
565 | mcgroup->ports[mcgroup->n_ports++] = p; | |
566 | } | |
567 | ||
568 | /* Sort the ports in alphabetical order to make output more | |
569 | * predictable. */ | |
570 | qsort(mcgroup->ports, mcgroup->n_ports, sizeof *mcgroup->ports, | |
571 | compare_port); | |
572 | } | |
573 | } | |
574 | ||
575 | static void | |
576 | read_address_sets(void) | |
577 | { | |
578 | shash_init(&address_sets); | |
579 | ||
580 | const struct sbrec_address_set *sbas; | |
581 | SBREC_ADDRESS_SET_FOR_EACH (sbas, ovnsb_idl) { | |
582 | expr_macros_add(&address_sets, sbas->name, | |
583 | (const char *const *) sbas->addresses, | |
584 | sbas->n_addresses); | |
585 | } | |
586 | } | |
587 | ||
588 | static int | |
589 | compare_flow(const void *a_, const void *b_) | |
590 | { | |
591 | struct ovntrace_flow *const *ap = a_; | |
592 | struct ovntrace_flow *const *bp = b_; | |
593 | const struct ovntrace_flow *a = *ap; | |
594 | const struct ovntrace_flow *b = *bp; | |
595 | ||
596 | if (a->pipeline != b->pipeline) { | |
597 | /* Sort P_INGRESS before P_EGRESS. */ | |
598 | return a->pipeline == P_EGRESS ? 1 : -1; | |
599 | } else if (a->table_id != b->table_id) { | |
600 | /* Sort in increasing order of table_id. */ | |
601 | return a->table_id > b->table_id ? 1 : -1; | |
602 | } else if (a->priority != b->priority) { | |
603 | /* Sort in decreasing order of priority. */ | |
604 | return a->priority > b->priority ? -1 : 1; | |
605 | } else { | |
606 | /* Otherwise who cares. */ | |
607 | return 0; | |
608 | } | |
609 | } | |
610 | ||
611 | static void | |
612 | read_flows(void) | |
613 | { | |
614 | ovn_init_symtab(&symtab); | |
615 | ||
616 | const struct sbrec_logical_flow *sblf; | |
617 | SBREC_LOGICAL_FLOW_FOR_EACH (sblf, ovnsb_idl) { | |
618 | const struct sbrec_datapath_binding *sbdb = sblf->logical_datapath; | |
619 | struct ovntrace_datapath *dp | |
620 | = ovntrace_datapath_find_by_sb_uuid(&sbdb->header_.uuid); | |
621 | if (!dp) { | |
622 | VLOG_WARN("logical flow missing datapath"); | |
623 | continue; | |
624 | } | |
625 | ||
626 | char *error; | |
627 | struct expr *match; | |
628 | match = expr_parse_string(sblf->match, &symtab, &address_sets, &error); | |
629 | if (error) { | |
630 | VLOG_WARN("%s: parsing expression failed (%s)", | |
631 | sblf->match, error); | |
632 | free(error); | |
633 | continue; | |
634 | } | |
635 | ||
636 | struct ovnact_parse_params pp = { | |
637 | .symtab = &symtab, | |
1585ff63 BP |
638 | .dhcp_opts = &dhcp_opts, |
639 | .dhcpv6_opts = &dhcpv6_opts, | |
4acd1e87 BP |
640 | .n_tables = 16, |
641 | .cur_ltable = sblf->table_id, | |
642 | }; | |
643 | uint64_t stub[1024 / 8]; | |
644 | struct ofpbuf ovnacts = OFPBUF_STUB_INITIALIZER(stub); | |
645 | struct expr *prereqs; | |
646 | error = ovnacts_parse_string(sblf->actions, &pp, &ovnacts, &prereqs); | |
647 | if (error) { | |
648 | VLOG_WARN("%s: parsing actions failed (%s)", sblf->actions, error); | |
649 | free(error); | |
650 | expr_destroy(match); | |
651 | continue; | |
652 | } | |
653 | ||
654 | match = expr_combine(EXPR_T_AND, match, prereqs); | |
655 | match = expr_annotate(match, &symtab, &error); | |
656 | if (error) { | |
657 | VLOG_WARN("match annotation failed (%s)", error); | |
658 | free(error); | |
659 | expr_destroy(match); | |
660 | ovnacts_free(ovnacts.data, ovnacts.size); | |
661 | ofpbuf_uninit(&ovnacts); | |
662 | continue; | |
663 | } | |
664 | if (match) { | |
665 | match = expr_simplify(match); | |
666 | } | |
667 | ||
668 | struct ovntrace_flow *flow = xzalloc(sizeof *flow); | |
d444a914 | 669 | flow->uuid = sblf->header_.uuid; |
4acd1e87 BP |
670 | flow->pipeline = (!strcmp(sblf->pipeline, "ingress") |
671 | ? P_INGRESS | |
672 | : P_EGRESS); | |
673 | flow->table_id = sblf->table_id; | |
674 | flow->stage_name = nullable_xstrdup(smap_get(&sblf->external_ids, | |
675 | "stage-name")); | |
d8026bbf BP |
676 | flow->source = nullable_xstrdup(smap_get(&sblf->external_ids, |
677 | "source")); | |
4acd1e87 BP |
678 | flow->priority = sblf->priority; |
679 | flow->match_s = xstrdup(sblf->match); | |
680 | flow->match = match; | |
681 | flow->ovnacts_len = ovnacts.size; | |
682 | flow->ovnacts = ofpbuf_steal_data(&ovnacts); | |
683 | ||
684 | if (dp->n_flows >= dp->allocated_flows) { | |
685 | dp->flows = x2nrealloc(dp->flows, &dp->allocated_flows, | |
686 | sizeof *dp->flows); | |
687 | } | |
688 | dp->flows[dp->n_flows++] = flow; | |
689 | } | |
690 | ||
691 | const struct ovntrace_datapath *dp; | |
692 | HMAP_FOR_EACH (dp, sb_uuid_node, &datapaths) { | |
693 | qsort(dp->flows, dp->n_flows, sizeof *dp->flows, compare_flow); | |
694 | } | |
695 | } | |
696 | ||
1585ff63 BP |
697 | static void |
698 | read_dhcp_opts(void) | |
699 | { | |
700 | hmap_init(&dhcp_opts); | |
701 | const struct sbrec_dhcp_options *sdo; | |
702 | SBREC_DHCP_OPTIONS_FOR_EACH (sdo, ovnsb_idl) { | |
703 | dhcp_opt_add(&dhcp_opts, sdo->name, sdo->code, sdo->type); | |
704 | } | |
705 | ||
706 | ||
707 | hmap_init(&dhcpv6_opts); | |
708 | const struct sbrec_dhcpv6_options *sdo6; | |
709 | SBREC_DHCPV6_OPTIONS_FOR_EACH(sdo6, ovnsb_idl) { | |
710 | dhcp_opt_add(&dhcpv6_opts, sdo6->name, sdo6->code, sdo6->type); | |
711 | } | |
712 | } | |
713 | ||
4acd1e87 BP |
714 | static void |
715 | read_mac_bindings(void) | |
716 | { | |
717 | const struct sbrec_mac_binding *sbmb; | |
718 | SBREC_MAC_BINDING_FOR_EACH (sbmb, ovnsb_idl) { | |
719 | const struct ovntrace_port *port = shash_find_data( | |
720 | &ports, sbmb->logical_port); | |
721 | if (!port) { | |
722 | VLOG_WARN("missing port %s", sbmb->logical_port); | |
723 | continue; | |
724 | } | |
725 | ||
726 | if (!uuid_equals(&port->dp->sb_uuid, &sbmb->datapath->header_.uuid)) { | |
727 | VLOG_WARN("port %s is in wrong datapath", sbmb->logical_port); | |
728 | continue; | |
729 | } | |
730 | ||
731 | struct in6_addr ip6; | |
732 | ovs_be32 ip4; | |
733 | if (ip_parse(sbmb->ip, &ip4)) { | |
734 | ip6 = in6_addr_mapped_ipv4(ip4); | |
735 | } else if (!ipv6_parse(sbmb->ip, &ip6)) { | |
736 | VLOG_WARN("%s: bad IP address", sbmb->ip); | |
737 | continue; | |
738 | } | |
739 | ||
740 | struct eth_addr mac; | |
741 | if (!eth_addr_from_string(sbmb->mac, &mac)) { | |
742 | VLOG_WARN("%s: bad Ethernet address", sbmb->mac); | |
743 | continue; | |
744 | } | |
745 | ||
746 | struct ovntrace_mac_binding *binding = xmalloc(sizeof *binding); | |
747 | binding->port_key = port->tunnel_key; | |
748 | binding->ip = ip6; | |
749 | binding->mac = mac; | |
750 | hmap_insert(&port->dp->mac_bindings, &binding->node, | |
751 | hash_mac_binding(binding->port_key, &ip6)); | |
752 | } | |
753 | } | |
754 | ||
755 | static void | |
756 | read_db(void) | |
757 | { | |
758 | read_datapaths(); | |
759 | read_ports(); | |
760 | read_mcgroups(); | |
761 | read_address_sets(); | |
1585ff63 | 762 | read_dhcp_opts(); |
4acd1e87 BP |
763 | read_flows(); |
764 | read_mac_bindings(); | |
765 | } | |
766 | ||
767 | static bool | |
768 | ovntrace_lookup_port(const void *dp_, const char *port_name, | |
769 | unsigned int *portp) | |
770 | { | |
771 | const struct ovntrace_datapath *dp = dp_; | |
772 | ||
773 | if (port_name[0] == '\0') { | |
774 | *portp = 0; | |
775 | return true; | |
776 | } | |
777 | ||
778 | const struct ovntrace_port *port = shash_find_data(&ports, port_name); | |
779 | if (port) { | |
780 | if (port->dp == dp) { | |
781 | *portp = port->tunnel_key; | |
782 | return true; | |
783 | } | |
784 | VLOG_WARN("%s: not in datapath %s", port_name, dp->name); | |
785 | } | |
786 | ||
787 | const struct ovntrace_mcgroup *mcgroup = ovntrace_mcgroup_find_by_name(dp, port_name); | |
788 | if (mcgroup) { | |
789 | *portp = mcgroup->tunnel_key; | |
790 | return true; | |
791 | } | |
792 | ||
793 | VLOG_WARN("%s: unknown logical port\n", port_name); | |
794 | return false; | |
795 | } | |
796 | ||
797 | static const struct ovntrace_flow * | |
798 | ovntrace_flow_lookup(const struct ovntrace_datapath *dp, | |
799 | const struct flow *uflow, | |
800 | uint8_t table_id, enum ovntrace_pipeline pipeline) | |
801 | { | |
802 | for (size_t i = 0; i < dp->n_flows; i++) { | |
803 | const struct ovntrace_flow *flow = dp->flows[i]; | |
804 | if (flow->pipeline == pipeline && | |
805 | flow->table_id == table_id && | |
806 | expr_evaluate(flow->match, uflow, ovntrace_lookup_port, dp)) { | |
807 | return flow; | |
808 | } | |
809 | } | |
810 | return NULL; | |
811 | } | |
812 | ||
4c62f3b0 RB |
813 | static char * |
814 | ovntrace_stage_name(const struct ovntrace_datapath *dp, | |
815 | uint8_t table_id, enum ovntrace_pipeline pipeline) | |
816 | { | |
817 | for (size_t i = 0; i < dp->n_flows; i++) { | |
818 | const struct ovntrace_flow *flow = dp->flows[i]; | |
819 | if (flow->pipeline == pipeline && flow->table_id == table_id) { | |
820 | return xstrdup(flow->stage_name);; | |
821 | } | |
822 | } | |
823 | return NULL; | |
824 | } | |
4acd1e87 BP |
825 | |
826 | enum ovntrace_node_type { | |
827 | OVNTRACE_NODE_OUTPUT, | |
828 | OVNTRACE_NODE_MODIFY, | |
829 | OVNTRACE_NODE_PIPELINE, | |
830 | OVNTRACE_NODE_TABLE, | |
831 | OVNTRACE_NODE_ACTION, | |
832 | OVNTRACE_NODE_ERROR, | |
833 | OVNTRACE_NODE_TRANSFORMATION | |
834 | }; | |
835 | ||
836 | static bool | |
837 | ovntrace_node_type_is_terminal(enum ovntrace_node_type type) | |
838 | { | |
839 | switch (type) { | |
840 | case OVNTRACE_NODE_OUTPUT: | |
841 | case OVNTRACE_NODE_MODIFY: | |
842 | case OVNTRACE_NODE_ACTION: | |
843 | case OVNTRACE_NODE_ERROR: | |
844 | return true; | |
845 | ||
846 | case OVNTRACE_NODE_PIPELINE: | |
847 | case OVNTRACE_NODE_TABLE: | |
848 | case OVNTRACE_NODE_TRANSFORMATION: | |
849 | return false; | |
850 | } | |
851 | ||
852 | OVS_NOT_REACHED(); | |
853 | } | |
854 | ||
855 | struct ovntrace_node { | |
856 | struct ovs_list node; /* In parent. */ | |
857 | ||
858 | enum ovntrace_node_type type; | |
859 | const char *name; | |
860 | bool always_indent; | |
861 | struct ovs_list subs; /* List of children. */ | |
862 | }; | |
863 | ||
864 | static struct ovntrace_node * OVS_PRINTF_FORMAT(3, 4) | |
865 | ovntrace_node_append(struct ovs_list *super, enum ovntrace_node_type type, | |
866 | const char *format, ...) | |
867 | { | |
868 | va_list args; | |
869 | va_start(args, format); | |
870 | char *s = xvasprintf(format, args); | |
871 | va_end(args); | |
872 | ||
873 | struct ovntrace_node *node = xmalloc(sizeof *node); | |
874 | ovs_list_push_back(super, &node->node); | |
875 | node->type = type; | |
876 | node->name = s; | |
877 | node->always_indent = false; | |
878 | ovs_list_init(&node->subs); | |
879 | ||
880 | return node; | |
881 | } | |
882 | ||
883 | static void | |
884 | ovntrace_node_clone(const struct ovs_list *old, struct ovs_list *new) | |
885 | { | |
886 | const struct ovntrace_node *osub; | |
887 | LIST_FOR_EACH (osub, node, old) { | |
888 | struct ovntrace_node *nsub = ovntrace_node_append(new, osub->type, | |
889 | "%s", osub->name); | |
890 | nsub->always_indent = osub->always_indent; | |
891 | ovntrace_node_clone(&osub->subs, &nsub->subs); | |
892 | } | |
893 | } | |
894 | ||
895 | static void | |
896 | ovntrace_node_print_details(struct ds *output, | |
897 | const struct ovs_list *nodes, int level) | |
898 | { | |
899 | const struct ovntrace_node *sub; | |
900 | LIST_FOR_EACH (sub, node, nodes) { | |
901 | if (sub->type == OVNTRACE_NODE_MODIFY) { | |
902 | continue; | |
903 | } | |
904 | ||
905 | bool more = sub->node.next != nodes || sub->always_indent || ovntrace_node_type_is_terminal(sub->type); | |
906 | bool title = (sub->type == OVNTRACE_NODE_PIPELINE || | |
907 | sub->type == OVNTRACE_NODE_TRANSFORMATION); | |
908 | if (title) { | |
909 | ds_put_char(output, '\n'); | |
910 | } | |
911 | ds_put_char_multiple(output, ' ', (level + more) * 4); | |
912 | ds_put_format(output, "%s\n", sub->name); | |
913 | if (title) { | |
914 | ds_put_char_multiple(output, ' ', (level + more) * 4); | |
915 | ds_put_char_multiple(output, '-', strlen(sub->name)); | |
916 | ds_put_char(output, '\n'); | |
917 | } | |
918 | ||
919 | ovntrace_node_print_details(output, &sub->subs, level + more + more); | |
920 | } | |
921 | } | |
922 | ||
923 | static void | |
924 | ovntrace_node_prune_summary(struct ovs_list *nodes) | |
925 | { | |
926 | struct ovntrace_node *sub, *next; | |
927 | LIST_FOR_EACH_SAFE (sub, next, node, nodes) { | |
928 | ovntrace_node_prune_summary(&sub->subs); | |
929 | if (sub->type == OVNTRACE_NODE_MODIFY || | |
930 | sub->type == OVNTRACE_NODE_TABLE) { | |
931 | ovs_list_remove(&sub->node); | |
932 | ovs_list_splice(&next->node, sub->subs.next, &sub->subs); | |
933 | } | |
934 | } | |
935 | } | |
936 | ||
937 | static void | |
938 | ovntrace_node_print_summary(struct ds *output, const struct ovs_list *nodes, | |
939 | int level) | |
940 | { | |
941 | const struct ovntrace_node *sub; | |
942 | LIST_FOR_EACH (sub, node, nodes) { | |
943 | if (sub->type == OVNTRACE_NODE_ACTION | |
944 | && !strncmp(sub->name, "next(", 5)) { | |
945 | continue; | |
946 | } | |
947 | ||
948 | ds_put_char_multiple(output, ' ', level * 4); | |
949 | ds_put_cstr(output, sub->name); | |
950 | if (!ovs_list_is_empty(&sub->subs)) { | |
951 | ds_put_cstr(output, " {\n"); | |
952 | ovntrace_node_print_summary(output, &sub->subs, level + 1); | |
953 | ds_put_char_multiple(output, ' ', level * 4); | |
954 | ds_put_char(output, '}'); | |
955 | } | |
956 | if (sub->type != OVNTRACE_NODE_ACTION) { | |
957 | ds_put_char(output, ';'); | |
958 | } | |
959 | ds_put_char(output, '\n'); | |
960 | } | |
961 | } | |
962 | ||
963 | static void | |
964 | ovntrace_node_prune_hard(struct ovs_list *nodes) | |
965 | { | |
966 | struct ovntrace_node *sub, *next; | |
967 | LIST_FOR_EACH_SAFE (sub, next, node, nodes) { | |
968 | ovntrace_node_prune_hard(&sub->subs); | |
969 | if (sub->type == OVNTRACE_NODE_ACTION || | |
970 | sub->type == OVNTRACE_NODE_PIPELINE || | |
971 | sub->type == OVNTRACE_NODE_TABLE || | |
972 | sub->type == OVNTRACE_NODE_OUTPUT) { | |
973 | ovs_list_remove(&sub->node); | |
974 | ovs_list_splice(&next->node, sub->subs.next, &sub->subs); | |
975 | } | |
976 | } | |
977 | } | |
978 | ||
979 | static void | |
980 | execute_load(const struct ovnact_load *load, | |
981 | const struct ovntrace_datapath *dp, struct flow *uflow, | |
982 | struct ovs_list *super OVS_UNUSED) | |
983 | { | |
128684a6 JR |
984 | const struct ovnact_encode_params ep = { |
985 | .lookup_port = ovntrace_lookup_port, | |
986 | .aux = dp, | |
987 | }; | |
988 | uint64_t stub[512 / 8]; | |
989 | struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub); | |
4acd1e87 | 990 | |
128684a6 | 991 | ovnacts_encode(&load->ovnact, sizeof *load, &ep, &ofpacts); |
4acd1e87 | 992 | |
128684a6 JR |
993 | struct ofpact *a; |
994 | OFPACT_FOR_EACH (a, ofpacts.data, ofpacts.size) { | |
995 | struct ofpact_set_field *sf = ofpact_get_SET_FIELD(a); | |
4acd1e87 | 996 | |
128684a6 JR |
997 | if (!mf_is_register(sf->field->id)) { |
998 | struct ds s = DS_EMPTY_INITIALIZER; | |
999 | ovnacts_format(&load->ovnact, OVNACT_LOAD_SIZE, &s); | |
1000 | ds_chomp(&s, ';'); | |
1001 | ||
1002 | ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s", | |
1003 | ds_cstr(&s)); | |
4acd1e87 | 1004 | |
128684a6 JR |
1005 | ds_destroy(&s); |
1006 | } | |
1007 | ||
1008 | if (mf_are_prereqs_ok(sf->field, uflow, NULL)) { | |
1009 | mf_set_flow_value_masked(sf->field, sf->value, | |
1010 | ofpact_set_field_mask(sf), uflow); | |
1011 | } | |
4acd1e87 | 1012 | } |
128684a6 | 1013 | ofpbuf_uninit(&ofpacts); |
4acd1e87 BP |
1014 | } |
1015 | ||
1016 | static void | |
1017 | summarize_move(const struct mf_subfield *rsrc, | |
1018 | const struct expr_field *dst, const struct mf_subfield *rdst, | |
1019 | const struct flow *uflow, struct ovs_list *super OVS_UNUSED) | |
1020 | { | |
1021 | if (!mf_is_register(rdst->field->id)) { | |
1022 | struct ds s = DS_EMPTY_INITIALIZER; | |
1023 | expr_field_format(dst, &s); | |
1024 | ds_put_cstr(&s, " = "); | |
1025 | ||
1026 | if (rsrc->ofs == 0 && rsrc->n_bits >= rsrc->field->n_bits) { | |
1027 | union mf_value value; | |
1028 | mf_get_value(rsrc->field, uflow, &value); | |
1029 | mf_format(rsrc->field, &value, NULL, &s); | |
1030 | } else { | |
1031 | union mf_subvalue cst; | |
1032 | mf_read_subfield(rsrc, uflow, &cst); | |
1033 | ds_put_hex(&s, &cst, sizeof cst); | |
1034 | } | |
1035 | ||
1036 | ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s", ds_cstr(&s)); | |
1037 | ||
1038 | ds_destroy(&s); | |
1039 | } | |
1040 | } | |
1041 | ||
1042 | static void | |
1043 | execute_move(const struct ovnact_move *move, struct flow *uflow, | |
1044 | struct ovs_list *super) | |
1045 | { | |
1046 | struct mf_subfield dst = expr_resolve_field(&move->lhs); | |
1047 | struct mf_subfield src = expr_resolve_field(&move->rhs); | |
1048 | summarize_move(&src, &move->lhs, &dst, uflow, super); | |
1049 | mf_subfield_copy(&src, &dst, uflow, NULL); | |
1050 | } | |
1051 | ||
1052 | static void | |
1053 | execute_exchange(const struct ovnact_move *move, struct flow *uflow, | |
1054 | struct ovs_list *super) | |
1055 | { | |
1056 | struct mf_subfield a = expr_resolve_field(&move->lhs); | |
1057 | struct mf_subfield b = expr_resolve_field(&move->rhs); | |
1058 | summarize_move(&b, &move->lhs, &a, uflow, super); | |
1059 | summarize_move(&a, &move->rhs, &b, uflow, super); | |
1060 | mf_subfield_swap(&a, &b, uflow, NULL); | |
1061 | } | |
1062 | ||
1063 | static void | |
1064 | trace__(const struct ovntrace_datapath *dp, struct flow *uflow, | |
1065 | uint8_t table_id, enum ovntrace_pipeline pipeline, | |
1066 | struct ovs_list *super); | |
1067 | ||
1068 | static void | |
1069 | trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, | |
1070 | const struct ovntrace_datapath *dp, struct flow *uflow, | |
1071 | uint8_t table_id, enum ovntrace_pipeline pipeline, | |
1072 | struct ovs_list *super); | |
1073 | static void | |
1074 | execute_output(const struct ovntrace_datapath *dp, struct flow *uflow, | |
1075 | enum ovntrace_pipeline pipeline, struct ovs_list *super) | |
1076 | { | |
1077 | uint16_t key = uflow->regs[MFF_LOG_OUTPORT - MFF_REG0]; | |
1078 | if (!key) { | |
1079 | ovntrace_node_append(super, OVNTRACE_NODE_ERROR, | |
1080 | "*** output to null logical port"); | |
1081 | return; | |
1082 | } | |
1083 | ||
1084 | const struct ovntrace_port *port = ovntrace_port_find_by_key(dp, key); | |
1085 | const struct ovntrace_mcgroup *mcgroup = ovntrace_mcgroup_find_by_key(dp, | |
1086 | key); | |
1087 | const char *out_name = (port ? port->name | |
1088 | : mcgroup ? mcgroup->name | |
1089 | : "(unnamed)"); | |
1090 | if (!port && !mcgroup) { | |
1091 | ovntrace_node_append(super, OVNTRACE_NODE_ERROR, | |
1092 | "*** unknown port or multicast group %"PRIu16, | |
1093 | key); | |
1094 | } | |
1095 | ||
1096 | if (pipeline == P_EGRESS) { | |
1097 | ovntrace_node_append(super, OVNTRACE_NODE_OUTPUT, | |
1098 | "/* output to \"%s\", type \"%s\" */", | |
1099 | out_name, port ? port->type : ""); | |
1100 | if (port && port->peer) { | |
1101 | const struct ovntrace_port *peer = port->peer; | |
1102 | ||
1103 | struct ovntrace_node *node = ovntrace_node_append( | |
1104 | super, OVNTRACE_NODE_PIPELINE, | |
1105 | "ingress(dp=\"%s\", inport=\"%s\")", | |
1106 | peer->dp->name, peer->name); | |
1107 | ||
1108 | struct flow new_uflow = *uflow; | |
1109 | new_uflow.regs[MFF_LOG_INPORT - MFF_REG0] = peer->tunnel_key; | |
1110 | new_uflow.regs[MFF_LOG_OUTPORT - MFF_REG0] = 0; | |
1111 | trace__(peer->dp, &new_uflow, 0, P_INGRESS, &node->subs); | |
1112 | } else { | |
1113 | ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, | |
1114 | "output(\"%s\")", out_name); | |
1115 | ||
1116 | } | |
1117 | return; | |
1118 | } | |
1119 | ||
1120 | struct flow egress_uflow = *uflow; | |
1121 | for (int i = 0; i < FLOW_N_REGS; i++) { | |
1122 | if (i != MFF_LOG_INPORT - MFF_REG0 && | |
1123 | i != MFF_LOG_OUTPORT - MFF_REG0) { | |
1124 | egress_uflow.regs[i] = 0; | |
1125 | } | |
1126 | } | |
1127 | ||
1128 | uint16_t in_key = uflow->regs[MFF_LOG_INPORT - MFF_REG0]; | |
1129 | const struct ovntrace_port *inport = ovntrace_port_find_by_key(dp, in_key); | |
1130 | const char *inport_name = !in_key ? "" : inport ? inport->name : "(unnamed)"; | |
1131 | uint32_t flags = uflow->regs[MFF_LOG_FLAGS - MFF_REG0]; | |
1132 | bool allow_loopback = (flags & MLF_ALLOW_LOOPBACK) != 0; | |
1133 | ||
1134 | if (mcgroup) { | |
1135 | struct ovntrace_node *mcnode = ovntrace_node_append( | |
1136 | super, OVNTRACE_NODE_PIPELINE, | |
1137 | "multicast(dp=\"%s\", mcgroup=\"%s\")", | |
1138 | dp->name, mcgroup->name); | |
1139 | for (size_t i = 0; i < mcgroup->n_ports; i++) { | |
1140 | const struct ovntrace_port *p = mcgroup->ports[i]; | |
1141 | ||
1142 | struct ovntrace_node *node = ovntrace_node_append( | |
1143 | &mcnode->subs, OVNTRACE_NODE_PIPELINE, | |
1144 | "egress(dp=\"%s\", inport=\"%s\", outport=\"%s\")", | |
1145 | dp->name, inport_name, p->name); | |
1146 | ||
1147 | if (p->tunnel_key != in_key || allow_loopback) { | |
1148 | node->always_indent = true; | |
1149 | ||
1150 | egress_uflow.regs[MFF_LOG_OUTPORT - MFF_REG0] = p->tunnel_key; | |
1151 | trace__(dp, &egress_uflow, 0, P_EGRESS, &node->subs); | |
1152 | } else { | |
1153 | ovntrace_node_append(&node->subs, OVNTRACE_NODE_OUTPUT, | |
1154 | "/* omitting output because inport == outport && !flags.loopback */"); | |
1155 | } | |
1156 | } | |
1157 | } else if (port->tunnel_key != in_key || allow_loopback) { | |
1158 | struct ovntrace_node *node = ovntrace_node_append( | |
1159 | super, OVNTRACE_NODE_PIPELINE, | |
1160 | "egress(dp=\"%s\", inport=\"%s\", outport=\"%s\")", | |
1161 | dp->name, inport_name, out_name); | |
1162 | ||
1163 | trace__(dp, &egress_uflow, 0, P_EGRESS, &node->subs); | |
1164 | } else { | |
1165 | ovntrace_node_append(super, OVNTRACE_NODE_OUTPUT, | |
1166 | "/* omitting output because inport == outport && !flags.loopback */"); | |
1167 | } | |
1168 | } | |
1169 | ||
1170 | static void | |
1171 | execute_arp(const struct ovnact_nest *on, const struct ovntrace_datapath *dp, | |
1172 | const struct flow *uflow, uint8_t table_id, | |
1173 | enum ovntrace_pipeline pipeline, struct ovs_list *super) | |
1174 | { | |
1175 | struct flow arp_flow = *uflow; | |
1176 | ||
1177 | /* Zero fields that are no longer relevant. */ | |
1178 | arp_flow.nw_frag = 0; | |
1179 | arp_flow.nw_tos = 0; | |
1180 | arp_flow.nw_ttl = 0; | |
1181 | arp_flow.tcp_flags = 0; | |
1182 | ||
1183 | /* Update fields for ARP. */ | |
1184 | arp_flow.dl_type = htons(ETH_TYPE_ARP); | |
1185 | arp_flow.nw_proto = ARP_OP_REQUEST; | |
1186 | arp_flow.arp_sha = arp_flow.dl_src; | |
1187 | arp_flow.arp_tha = eth_addr_zero; | |
1188 | /* ARP SPA is already in arp_flow.nw_src. */ | |
1189 | /* ARP TPA is already in arp_flow.nw_dst. */ | |
1190 | ||
1191 | struct ovntrace_node *node = ovntrace_node_append( | |
1192 | super, OVNTRACE_NODE_TRANSFORMATION, "arp"); | |
1193 | ||
1194 | trace_actions(on->nested, on->nested_len, dp, &arp_flow, | |
1195 | table_id, pipeline, &node->subs); | |
1196 | } | |
1197 | ||
1198 | static void | |
1199 | execute_nd_na(const struct ovnact_nest *on, const struct ovntrace_datapath *dp, | |
1200 | const struct flow *uflow, uint8_t table_id, | |
1201 | enum ovntrace_pipeline pipeline, struct ovs_list *super) | |
1202 | { | |
1203 | struct flow na_flow = *uflow; | |
1204 | ||
1205 | /* Update fields for NA. */ | |
1206 | na_flow.dl_src = uflow->dl_dst; | |
1207 | na_flow.dl_dst = uflow->dl_src; | |
1208 | na_flow.ipv6_dst = uflow->ipv6_src; | |
1209 | na_flow.ipv6_src = uflow->nd_target; | |
1210 | na_flow.tp_src = htons(136); | |
1211 | na_flow.arp_sha = eth_addr_zero; | |
1212 | na_flow.arp_tha = uflow->dl_dst; | |
1213 | ||
1214 | struct ovntrace_node *node = ovntrace_node_append( | |
1215 | super, OVNTRACE_NODE_TRANSFORMATION, "nd_na"); | |
1216 | ||
1217 | trace_actions(on->nested, on->nested_len, dp, &na_flow, | |
1218 | table_id, pipeline, &node->subs); | |
1219 | } | |
1220 | ||
1221 | static void | |
1222 | execute_get_mac_bind(const struct ovnact_get_mac_bind *bind, | |
1223 | const struct ovntrace_datapath *dp, | |
1224 | struct flow *uflow, struct ovs_list *super) | |
1225 | { | |
1226 | /* Get logical port number.*/ | |
1227 | struct mf_subfield port_sf = expr_resolve_field(&bind->port); | |
1228 | ovs_assert(port_sf.n_bits == 32); | |
1229 | uint32_t port_key = mf_get_subfield(&port_sf, uflow); | |
1230 | ||
1231 | /* Get IP address. */ | |
1232 | struct mf_subfield ip_sf = expr_resolve_field(&bind->ip); | |
1233 | ovs_assert(ip_sf.n_bits == 32 || ip_sf.n_bits == 128); | |
1234 | union mf_subvalue ip_sv; | |
1235 | mf_read_subfield(&ip_sf, uflow, &ip_sv); | |
1236 | struct in6_addr ip = (ip_sf.n_bits == 32 | |
1237 | ? in6_addr_mapped_ipv4(ip_sv.ipv4) | |
1238 | : ip_sv.ipv6); | |
1239 | ||
1240 | const struct ovntrace_mac_binding *binding | |
1241 | = ovntrace_mac_binding_find(dp, port_key, &ip); | |
1242 | ||
a9ba22b7 | 1243 | uflow->dl_dst = binding ? binding->mac : eth_addr_zero; |
4acd1e87 BP |
1244 | if (binding) { |
1245 | ovntrace_node_append(super, OVNTRACE_NODE_ACTION, | |
1246 | "/* MAC binding to "ETH_ADDR_FMT". */", | |
a9ba22b7 | 1247 | ETH_ADDR_ARGS(uflow->dl_dst)); |
4acd1e87 BP |
1248 | } else { |
1249 | ovntrace_node_append(super, OVNTRACE_NODE_ACTION, | |
1250 | "/* No MAC binding. */"); | |
1251 | } | |
1252 | ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, | |
a9ba22b7 BP |
1253 | "eth.dst = "ETH_ADDR_FMT, |
1254 | ETH_ADDR_ARGS(uflow->dl_dst)); | |
4acd1e87 BP |
1255 | } |
1256 | ||
1257 | static void | |
1258 | execute_put_dhcp_opts(const struct ovnact_put_dhcp_opts *pdo, | |
1585ff63 BP |
1259 | const char *name, struct flow *uflow, |
1260 | struct ovs_list *super) | |
4acd1e87 | 1261 | { |
1585ff63 BP |
1262 | ovntrace_node_append( |
1263 | super, OVNTRACE_NODE_ERROR, | |
1264 | "/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */"); | |
1265 | ||
1266 | /* Format the put_dhcp_opts action. */ | |
1267 | struct ds s = DS_EMPTY_INITIALIZER; | |
1268 | for (const struct ovnact_dhcp_option *o = pdo->options; | |
1269 | o < &pdo->options[pdo->n_options]; o++) { | |
1270 | if (o != pdo->options) { | |
1271 | ds_put_cstr(&s, ", "); | |
1272 | } | |
1273 | ds_put_format(&s, "%s = ", o->option->name); | |
1274 | expr_constant_set_format(&o->value, &s); | |
1275 | } | |
1276 | ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s(%s)", | |
1277 | name, ds_cstr(&s)); | |
1278 | ds_destroy(&s); | |
1279 | ||
1280 | struct mf_subfield dst = expr_resolve_field(&pdo->dst); | |
1281 | if (!mf_is_register(dst.field->id)) { | |
1282 | /* Format assignment. */ | |
1283 | struct ds s = DS_EMPTY_INITIALIZER; | |
1284 | expr_field_format(&pdo->dst, &s); | |
1285 | ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, | |
1286 | "%s = 1", ds_cstr(&s)); | |
1287 | ds_destroy(&s); | |
1288 | } | |
1289 | ||
4acd1e87 BP |
1290 | struct mf_subfield sf = expr_resolve_field(&pdo->dst); |
1291 | union mf_subvalue sv = { .u8_val = 1 }; | |
1292 | mf_write_subfield_flow(&sf, &sv, uflow); | |
1293 | } | |
1294 | ||
1295 | static void | |
1296 | trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, | |
1297 | const struct ovntrace_datapath *dp, struct flow *uflow, | |
1298 | uint8_t table_id, enum ovntrace_pipeline pipeline, | |
1299 | struct ovs_list *super) | |
1300 | { | |
1301 | if (!ovnacts_len) { | |
1302 | ovntrace_node_append(super, OVNTRACE_NODE_ACTION, "drop;"); | |
1303 | return; | |
1304 | } | |
1305 | ||
1306 | struct ds s = DS_EMPTY_INITIALIZER; | |
1307 | const struct ovnact *a; | |
1308 | OVNACT_FOR_EACH (a, ovnacts, ovnacts_len) { | |
1309 | ds_clear(&s); | |
1310 | ovnacts_format(a, sizeof *a * (ovnact_next(a) - a), &s); | |
1311 | ovntrace_node_append(super, OVNTRACE_NODE_ACTION, "%s", ds_cstr(&s)); | |
1312 | ||
1313 | switch (a->type) { | |
1314 | case OVNACT_OUTPUT: | |
1315 | execute_output(dp, uflow, pipeline, super); | |
1316 | break; | |
1317 | ||
1318 | case OVNACT_NEXT: | |
1319 | trace__(dp, uflow, table_id + 1, pipeline, super); | |
1320 | break; | |
1321 | ||
1322 | case OVNACT_LOAD: | |
1323 | execute_load(ovnact_get_LOAD(a), dp, uflow, super); | |
1324 | break; | |
1325 | ||
1326 | case OVNACT_MOVE: | |
1327 | execute_move(ovnact_get_MOVE(a), uflow, super); | |
1328 | break; | |
1329 | ||
1330 | case OVNACT_EXCHANGE: | |
1331 | execute_exchange(ovnact_get_EXCHANGE(a), uflow, super); | |
1332 | break; | |
1333 | ||
1334 | case OVNACT_DEC_TTL: | |
1335 | if (is_ip_any(uflow)) { | |
1336 | if (uflow->nw_ttl) { | |
1337 | uflow->nw_ttl--; | |
1338 | ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, | |
1339 | "ip.ttl--"); | |
1340 | } else { | |
1341 | ovntrace_node_append(super, OVNTRACE_NODE_ERROR, | |
1342 | "*** TTL underflow"); | |
1343 | } | |
1344 | } else { | |
1345 | ovntrace_node_append(super, OVNTRACE_NODE_ERROR, | |
1346 | "*** TTL decrement of non-IP packet"); | |
1347 | } | |
1348 | break; | |
1349 | ||
1350 | case OVNACT_CT_NEXT: | |
1351 | case OVNACT_CT_COMMIT: | |
1352 | case OVNACT_CT_DNAT: | |
1353 | case OVNACT_CT_SNAT: | |
1354 | case OVNACT_CT_LB: | |
1355 | ovntrace_node_append(super, OVNTRACE_NODE_ERROR, | |
1356 | "*** ct_* actions not implemented"); | |
1357 | break; | |
1358 | ||
1359 | case OVNACT_ARP: | |
1360 | execute_arp(ovnact_get_ARP(a), dp, uflow, table_id, pipeline, | |
1361 | super); | |
1362 | break; | |
1363 | ||
1364 | case OVNACT_ND_NA: | |
1365 | execute_nd_na(ovnact_get_ND_NA(a), dp, uflow, table_id, pipeline, | |
1366 | super); | |
1367 | break; | |
1368 | ||
1369 | case OVNACT_GET_ARP: | |
1370 | execute_get_mac_bind(ovnact_get_GET_ARP(a), dp, uflow, super); | |
1371 | break; | |
1372 | ||
1373 | case OVNACT_GET_ND: | |
1374 | execute_get_mac_bind(ovnact_get_GET_ND(a), dp, uflow, super); | |
1375 | break; | |
1376 | ||
1377 | case OVNACT_PUT_ARP: | |
1378 | case OVNACT_PUT_ND: | |
1379 | /* Nothing to do for tracing. */ | |
1380 | break; | |
1381 | ||
1382 | case OVNACT_PUT_DHCPV4_OPTS: | |
1585ff63 BP |
1383 | execute_put_dhcp_opts(ovnact_get_PUT_DHCPV4_OPTS(a), |
1384 | "put_dhcp_opts", uflow, super); | |
4acd1e87 BP |
1385 | break; |
1386 | ||
1387 | case OVNACT_PUT_DHCPV6_OPTS: | |
1585ff63 BP |
1388 | execute_put_dhcp_opts(ovnact_get_PUT_DHCPV6_OPTS(a), |
1389 | "put_dhcpv6_opts", uflow, super); | |
4acd1e87 | 1390 | break; |
a6095f81 BS |
1391 | |
1392 | case OVNACT_SET_QUEUE: | |
1393 | /* The set_queue action is slippery from a logical perspective. It | |
1394 | * has no visible effect as long as the packet remains on the same | |
1395 | * chassis: it can bounce from one logical datapath to another | |
1396 | * retaining the queue and even end up at a VM on the same chassis. | |
1397 | * Without taking the physical arrangement into account, we can't | |
1398 | * do anything with this action other than just to note that it | |
1399 | * happened. If we ever add some physical knowledge to ovn-trace, | |
1400 | * though, it would be easy enough to track the queue information | |
1401 | * by adjusting uflow->skb_priority. */ | |
1402 | break; | |
4acd1e87 BP |
1403 | } |
1404 | ||
1405 | } | |
1406 | ds_destroy(&s); | |
1407 | } | |
1408 | ||
1409 | static bool | |
1410 | may_omit_stage(const struct ovntrace_flow *f, uint8_t table_id) | |
1411 | { | |
1412 | return (f | |
1413 | && f->match->type == EXPR_T_BOOLEAN && f->match->boolean | |
1414 | && f->ovnacts_len == OVNACT_NEXT_SIZE | |
1415 | && f->ovnacts->type == OVNACT_NEXT | |
1416 | && ovnact_get_NEXT(f->ovnacts)->ltable == table_id + 1); | |
1417 | } | |
1418 | ||
d444a914 BP |
1419 | static void |
1420 | trace_openflow(const struct ovntrace_flow *f, struct ovs_list *super) | |
1421 | { | |
1422 | struct ofputil_flow_stats_request fsr = { | |
1423 | .cookie = htonll(f->uuid.parts[0]), | |
1424 | .cookie_mask = OVS_BE64_MAX, | |
1425 | .out_port = OFPP_ANY, | |
1426 | .out_group = OFPG_ANY, | |
1427 | .table_id = OFPTT_ALL, | |
1428 | }; | |
1429 | ||
1430 | struct ofputil_flow_stats *fses; | |
1431 | size_t n_fses; | |
1432 | int error = vconn_dump_flows(vconn, &fsr, OFPUTIL_P_OF13_OXM, | |
1433 | &fses, &n_fses); | |
1434 | if (error) { | |
1435 | ovntrace_node_append(super, OVNTRACE_NODE_ERROR, | |
1436 | "*** error obtaining flow stats (%s)", | |
1437 | ovs_strerror(error)); | |
1438 | VLOG_WARN("%s: error obtaining flow stats (%s)", | |
1439 | ovs, ovs_strerror(error)); | |
1440 | return; | |
1441 | } | |
1442 | ||
1443 | if (n_fses) { | |
1444 | struct ds s = DS_EMPTY_INITIALIZER; | |
1445 | for (size_t i = 0; i < n_fses; i++) { | |
1446 | ds_clear(&s); | |
1447 | ofp_print_flow_stats(&s, &fses[i]); | |
1448 | ||
1449 | /* ofp_print_flow_stats() indents its output with a space. | |
1450 | * Omit it. */ | |
1451 | const char *p = ds_cstr(&s); | |
1452 | p += strspn(p, " "); | |
1453 | ovntrace_node_append(super, OVNTRACE_NODE_ACTION, "%s", p); | |
1454 | } | |
1455 | ds_destroy(&s); | |
1456 | } else { | |
1457 | ovntrace_node_append(super, OVNTRACE_NODE_ERROR, | |
1458 | "*** no OpenFlow flows"); | |
1459 | } | |
1460 | ||
1461 | for (size_t i = 0; i < n_fses; i++) { | |
1462 | free(CONST_CAST(struct ofpact *, fses[i].ofpacts)); | |
1463 | } | |
1464 | free(fses); | |
1465 | } | |
1466 | ||
4acd1e87 BP |
1467 | static void |
1468 | trace__(const struct ovntrace_datapath *dp, struct flow *uflow, | |
1469 | uint8_t table_id, enum ovntrace_pipeline pipeline, | |
1470 | struct ovs_list *super) | |
1471 | { | |
1472 | const struct ovntrace_flow *f; | |
1473 | for (;;) { | |
1474 | f = ovntrace_flow_lookup(dp, uflow, table_id, pipeline); | |
1475 | if (!may_omit_stage(f, table_id)) { | |
1476 | break; | |
1477 | } | |
1478 | table_id++; | |
1479 | } | |
1480 | ||
1481 | struct ds s = DS_EMPTY_INITIALIZER; | |
1482 | ds_put_format(&s, "%2d. ", table_id); | |
1483 | if (f) { | |
d8026bbf BP |
1484 | if (f->stage_name && f->source) { |
1485 | ds_put_format(&s, "%s (%s): ", f->stage_name, f->source); | |
1486 | } else if (f->stage_name) { | |
4acd1e87 | 1487 | ds_put_format(&s, "%s: ", f->stage_name); |
d8026bbf BP |
1488 | } else if (f->source) { |
1489 | ds_put_format(&s, "(%s): ", f->source); | |
4acd1e87 | 1490 | } |
d444a914 BP |
1491 | ds_put_format(&s, "%s, priority %d, uuid %08x", |
1492 | f->match_s, f->priority, f->uuid.parts[0]); | |
4acd1e87 | 1493 | } else { |
4c62f3b0 | 1494 | char *stage_name = ovntrace_stage_name(dp, table_id, pipeline); |
b8d6c3e8 | 1495 | ds_put_format(&s, "%s%sno match (implicit drop)", |
4c62f3b0 RB |
1496 | stage_name ? stage_name : "", |
1497 | stage_name ? ": " : ""); | |
1498 | free(stage_name); | |
4acd1e87 BP |
1499 | } |
1500 | struct ovntrace_node *node = ovntrace_node_append( | |
1501 | super, OVNTRACE_NODE_TABLE, "%s", ds_cstr(&s)); | |
1502 | ds_destroy(&s); | |
1503 | ||
1504 | if (f) { | |
d444a914 BP |
1505 | if (vconn) { |
1506 | trace_openflow(f, &node->subs); | |
1507 | } | |
4acd1e87 BP |
1508 | trace_actions(f->ovnacts, f->ovnacts_len, dp, uflow, table_id, |
1509 | pipeline, &node->subs); | |
1510 | } | |
1511 | } | |
1512 | ||
1513 | static char * | |
1514 | trace(const char *dp_s, const char *flow_s) | |
1515 | { | |
1516 | const struct ovntrace_datapath *dp = ovntrace_datapath_find_by_name(dp_s); | |
1517 | if (!dp) { | |
88a4e856 | 1518 | return xasprintf("unknown datapath \"%s\"\n", dp_s); |
4acd1e87 BP |
1519 | } |
1520 | ||
1521 | struct flow uflow; | |
1522 | char *error = expr_parse_microflow(flow_s, &symtab, &address_sets, | |
1523 | ovntrace_lookup_port, dp, &uflow); | |
1524 | if (error) { | |
7967bc77 JP |
1525 | char *s = xasprintf("error parsing flow: %s\n", error); |
1526 | free(error); | |
1527 | return s; | |
4acd1e87 BP |
1528 | } |
1529 | ||
1530 | uint32_t in_key = uflow.regs[MFF_LOG_INPORT - MFF_REG0]; | |
1531 | if (!in_key) { | |
1532 | VLOG_WARN("microflow does not specify ingress port"); | |
1533 | } | |
1534 | const struct ovntrace_port *inport = ovntrace_port_find_by_key(dp, in_key); | |
1535 | const char *inport_name = inport ? inport->name : "(unnamed)"; | |
1536 | ||
1537 | struct ds output = DS_EMPTY_INITIALIZER; | |
1538 | ||
1539 | ds_put_cstr(&output, "# "); | |
1540 | flow_format(&output, &uflow); | |
1541 | ds_put_char(&output, '\n'); | |
1542 | ||
d444a914 BP |
1543 | if (ovs) { |
1544 | int retval = vconn_open_block(ovs, 1 << OFP13_VERSION, 0, &vconn); | |
1545 | if (retval) { | |
1546 | VLOG_WARN("%s: connection failed (%s)", ovs, ovs_strerror(retval)); | |
1547 | } | |
1548 | } | |
1549 | ||
4acd1e87 BP |
1550 | struct ovs_list root = OVS_LIST_INITIALIZER(&root); |
1551 | struct ovntrace_node *node = ovntrace_node_append( | |
1552 | &root, OVNTRACE_NODE_PIPELINE, "ingress(dp=\"%s\", inport=\"%s\")", | |
1553 | dp->name, inport_name); | |
1554 | trace__(dp, &uflow, 0, P_INGRESS, &node->subs); | |
1555 | ||
1556 | bool multiple = (detailed + summary + minimal) > 1; | |
1557 | if (detailed) { | |
1558 | if (multiple) { | |
1559 | ds_put_cstr(&output, "# Detailed trace.\n"); | |
1560 | } | |
1561 | ovntrace_node_print_details(&output, &root, 0); | |
1562 | } | |
1563 | ||
1564 | if (summary) { | |
1565 | if (multiple) { | |
1566 | ds_put_cstr(&output, "# Summary trace.\n"); | |
1567 | } | |
1568 | struct ovs_list clone = OVS_LIST_INITIALIZER(&clone); | |
1569 | ovntrace_node_clone(&root, &clone); | |
1570 | ovntrace_node_prune_summary(&clone); | |
1571 | ovntrace_node_print_summary(&output, &clone, 0); | |
1572 | } | |
1573 | ||
1574 | if (minimal) { | |
1575 | if (multiple) { | |
1576 | ds_put_cstr(&output, "# Minimal trace.\n"); | |
1577 | } | |
1578 | ovntrace_node_prune_hard(&root); | |
1579 | ovntrace_node_print_summary(&output, &root, 0); | |
1580 | } | |
d444a914 BP |
1581 | |
1582 | vconn_close(vconn); | |
1583 | ||
4acd1e87 BP |
1584 | return ds_steal_cstr(&output); |
1585 | } | |
1586 | \f | |
1587 | static void | |
1588 | ovntrace_exit(struct unixctl_conn *conn, int argc OVS_UNUSED, | |
1589 | const char *argv[] OVS_UNUSED, void *exiting_) | |
1590 | { | |
1591 | bool *exiting = exiting_; | |
1592 | *exiting = true; | |
1593 | unixctl_command_reply(conn, NULL); | |
1594 | } | |
1595 | ||
1596 | static void | |
1597 | ovntrace_trace(struct unixctl_conn *conn, int argc, | |
1598 | const char *argv[], void *aux OVS_UNUSED) | |
1599 | { | |
1600 | detailed = summary = minimal = false; | |
1601 | while (argc > 1 && argv[1][0] == '-') { | |
1602 | if (!strcmp(argv[1], "--detailed")) { | |
1603 | detailed = true; | |
1604 | } else if (!strcmp(argv[1], "--summary")) { | |
1605 | summary = true; | |
1606 | } else if (!strcmp(argv[1], "--minimal")) { | |
1607 | minimal = true; | |
1608 | } else if (!strcmp(argv[1], "--all")) { | |
1609 | detailed = summary = minimal = true; | |
1610 | } else { | |
1611 | unixctl_command_reply_error(conn, "unknown option"); | |
1612 | return; | |
1613 | } | |
1614 | argc--; | |
1615 | argv++; | |
1616 | } | |
1617 | if (!detailed && !summary && !minimal) { | |
1618 | detailed = true; | |
1619 | } | |
1620 | ||
1621 | if (argc != 3) { | |
1622 | unixctl_command_reply_error( | |
1623 | conn, "exactly 2 non-option arguments are required"); | |
1624 | return; | |
1625 | } | |
1626 | ||
1627 | char *output = trace(argv[1], argv[2]); | |
1628 | unixctl_command_reply(conn, output); | |
1629 | free(output); | |
1630 | } |