/src/openvswitch/lib/dpif-offload-tc.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2025 Red Hat, 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 | | #include <errno.h> |
19 | | |
20 | | #include "dpif-offload.h" |
21 | | #include "dpif-offload-provider.h" |
22 | | #include "dpif-offload-tc-private.h" |
23 | | #include "netdev-provider.h" |
24 | | #include "netdev-vport.h" |
25 | | #include "odp-util.h" |
26 | | #include "tc.h" |
27 | | #include "util.h" |
28 | | |
29 | | #include "openvswitch/json.h" |
30 | | #include "openvswitch/match.h" |
31 | | #include "openvswitch/vlog.h" |
32 | | |
33 | | VLOG_DEFINE_THIS_MODULE(dpif_offload_tc); |
34 | | |
35 | | /* dpif offload interface for the tc implementation. */ |
36 | | struct tc_offload { |
37 | | struct dpif_offload offload; |
38 | | |
39 | | /* Configuration specific variables. */ |
40 | | struct ovsthread_once once_enable; /* Track first-time enablement. */ |
41 | | bool recirc_id_shared; |
42 | | }; |
43 | | |
44 | | /* tc's flow dump specific data structures. */ |
45 | | struct tc_flow_dump { |
46 | | struct dpif_offload_flow_dump dump; |
47 | | struct ovs_mutex netdev_dump_mutex; |
48 | | size_t netdev_dump_index; |
49 | | size_t netdev_dump_count; |
50 | | struct netdev_tc_flow_dump *netdev_dumps[]; |
51 | | }; |
52 | | |
53 | | #define FLOW_DUMP_MAX_BATCH 50 |
54 | | |
55 | | struct tc_flow_dump_thread { |
56 | | struct dpif_offload_flow_dump_thread thread; |
57 | | struct tc_flow_dump *dump; |
58 | | bool netdev_dump_done; |
59 | | size_t netdev_dump_index; |
60 | | |
61 | | /* (Flows/Key/Mask/Actions) Buffers for netdev dumping. */ |
62 | | struct ofpbuf nl_flows; |
63 | | struct odputil_keybuf keybuf[FLOW_DUMP_MAX_BATCH]; |
64 | | struct odputil_keybuf maskbuf[FLOW_DUMP_MAX_BATCH]; |
65 | | struct odputil_keybuf actbuf[FLOW_DUMP_MAX_BATCH]; |
66 | | }; |
67 | | |
68 | | static struct tc_offload * |
69 | | tc_offload_cast(const struct dpif_offload *offload) |
70 | 0 | { |
71 | 0 | dpif_offload_assert_class(offload, &dpif_offload_tc_class); |
72 | 0 | return CONTAINER_OF(offload, struct tc_offload, offload); |
73 | 0 | } |
74 | | |
75 | | static int |
76 | | tc_offload_enable(struct dpif_offload *dpif_offload, |
77 | | struct dpif_offload_port *port) |
78 | 0 | { |
79 | 0 | int ret = tc_netdev_init(port->netdev); |
80 | |
|
81 | 0 | if (ret) { |
82 | 0 | VLOG_WARN("%s: Failed assigning flow API 'tc', error %d", |
83 | 0 | netdev_get_name(port->netdev), ret); |
84 | 0 | return ret; |
85 | 0 | } |
86 | 0 | dpif_offload_set_netdev_offload(port->netdev, dpif_offload); |
87 | 0 | VLOG_INFO("%s: Assigned flow API 'tc'", netdev_get_name(port->netdev)); |
88 | 0 | return 0; |
89 | 0 | } |
90 | | |
91 | | static int |
92 | | tc_offload_cleanup(struct dpif_offload *dpif_offload OVS_UNUSED, |
93 | | struct dpif_offload_port *port) |
94 | 0 | { |
95 | 0 | dpif_offload_set_netdev_offload(port->netdev, NULL); |
96 | 0 | return 0; |
97 | 0 | } |
98 | | |
99 | | static int |
100 | | tc_port_add(struct dpif_offload *dpif_offload, struct netdev *netdev, |
101 | | odp_port_t port_no) |
102 | 0 | { |
103 | 0 | struct dpif_offload_port *port = xmalloc(sizeof *port); |
104 | |
|
105 | 0 | if (dpif_offload_port_mgr_add(dpif_offload, port, netdev, port_no, true)) { |
106 | 0 | if (dpif_offload_enabled()) { |
107 | 0 | return tc_offload_enable(dpif_offload, port); |
108 | 0 | } |
109 | 0 | return 0; |
110 | 0 | } |
111 | | |
112 | 0 | free(port); |
113 | 0 | return EEXIST; |
114 | 0 | } |
115 | | |
116 | | static void |
117 | | tc_free_port(struct dpif_offload_port *port) |
118 | 0 | { |
119 | 0 | netdev_close(port->netdev); |
120 | 0 | free(port); |
121 | 0 | } |
122 | | |
123 | | static int |
124 | | tc_port_del(struct dpif_offload *dpif_offload, odp_port_t port_no) |
125 | 0 | { |
126 | 0 | struct dpif_offload_port *port; |
127 | 0 | int ret = 0; |
128 | |
|
129 | 0 | port = dpif_offload_port_mgr_remove(dpif_offload, port_no); |
130 | 0 | if (port) { |
131 | 0 | if (dpif_offload_enabled()) { |
132 | 0 | ret = tc_offload_cleanup(dpif_offload, port); |
133 | 0 | } |
134 | 0 | ovsrcu_postpone(tc_free_port, port); |
135 | 0 | } |
136 | 0 | return ret; |
137 | 0 | } |
138 | | |
139 | | static struct netdev * |
140 | | tc_get_netdev(const struct dpif_offload *dpif_offload, odp_port_t port_no) |
141 | 0 | { |
142 | 0 | struct dpif_offload_port *port; |
143 | |
|
144 | 0 | port = dpif_offload_port_mgr_find_by_odp_port(dpif_offload, port_no); |
145 | 0 | if (!port) { |
146 | 0 | return NULL; |
147 | 0 | } |
148 | | |
149 | 0 | return port->netdev; |
150 | 0 | } |
151 | | |
152 | | static int |
153 | | tc_offload_open(const struct dpif_offload_class *offload_class, |
154 | | struct dpif *dpif, struct dpif_offload **dpif_offload) |
155 | 0 | { |
156 | 0 | struct tc_offload *offload = xmalloc(sizeof *offload); |
157 | |
|
158 | 0 | dpif_offload_init(&offload->offload, offload_class, dpif); |
159 | 0 | offload->once_enable = (struct ovsthread_once) OVSTHREAD_ONCE_INITIALIZER; |
160 | 0 | offload->recirc_id_shared = !!(dpif_get_features(dpif) |
161 | 0 | & OVS_DP_F_TC_RECIRC_SHARING); |
162 | |
|
163 | 0 | VLOG_DBG("Datapath %s recirculation id sharing ", |
164 | 0 | offload->recirc_id_shared ? "supports" : "does not support"); |
165 | |
|
166 | 0 | tc_meter_init(); |
167 | |
|
168 | 0 | *dpif_offload = &offload->offload; |
169 | 0 | return 0; |
170 | 0 | } |
171 | | |
172 | | static void |
173 | | tc_offload_close(struct dpif_offload *dpif_offload) |
174 | 0 | { |
175 | 0 | struct tc_offload *offload = tc_offload_cast(dpif_offload); |
176 | 0 | struct dpif_offload_port *port; |
177 | |
|
178 | 0 | DPIF_OFFLOAD_PORT_FOR_EACH (port, dpif_offload) { |
179 | 0 | tc_port_del(dpif_offload, port->port_no); |
180 | 0 | } |
181 | |
|
182 | 0 | ovsthread_once_destroy(&offload->once_enable); |
183 | 0 | dpif_offload_destroy(dpif_offload); |
184 | 0 | free(offload); |
185 | 0 | } |
186 | | |
187 | | static void |
188 | | tc_offload_set_config(struct dpif_offload *offload_, |
189 | | const struct smap *other_cfg) |
190 | 0 | { |
191 | 0 | struct tc_offload *offload = tc_offload_cast(offload_); |
192 | |
|
193 | 0 | if (smap_get_bool(other_cfg, "hw-offload", false)) { |
194 | 0 | if (ovsthread_once_start(&offload->once_enable)) { |
195 | 0 | struct dpif_offload_port *port; |
196 | |
|
197 | 0 | tc_set_policy(smap_get_def(other_cfg, "tc-policy", |
198 | 0 | TC_POLICY_DEFAULT)); |
199 | |
|
200 | 0 | DPIF_OFFLOAD_PORT_FOR_EACH (port, offload_) { |
201 | 0 | tc_offload_enable(offload_, port); |
202 | 0 | } |
203 | |
|
204 | 0 | ovsthread_once_done(&offload->once_enable); |
205 | 0 | } |
206 | 0 | } |
207 | 0 | } |
208 | | |
209 | | static void |
210 | | tc_offload_get_debug(const struct dpif_offload *offload, struct ds *ds, |
211 | | struct json *json) |
212 | 0 | { |
213 | 0 | if (json) { |
214 | 0 | struct json *json_ports = json_object_create(); |
215 | 0 | struct dpif_offload_port *port; |
216 | |
|
217 | 0 | DPIF_OFFLOAD_PORT_FOR_EACH (port, offload) { |
218 | 0 | struct json *json_port = json_object_create(); |
219 | |
|
220 | 0 | json_object_put(json_port, "port_no", |
221 | 0 | json_integer_create(odp_to_u32(port->port_no))); |
222 | 0 | json_object_put(json_port, "ifindex", |
223 | 0 | json_integer_create(port->ifindex)); |
224 | |
|
225 | 0 | json_object_put(json_ports, netdev_get_name(port->netdev), |
226 | 0 | json_port); |
227 | 0 | } |
228 | |
|
229 | 0 | if (!json_object_is_empty(json_ports)) { |
230 | 0 | json_object_put(json, "ports", json_ports); |
231 | 0 | } else { |
232 | 0 | json_destroy(json_ports); |
233 | 0 | } |
234 | 0 | } else if (ds) { |
235 | 0 | struct dpif_offload_port *port; |
236 | |
|
237 | 0 | DPIF_OFFLOAD_PORT_FOR_EACH (port, offload) { |
238 | 0 | ds_put_format(ds, " - %s: port_no: %u, ifindex: %d\n", |
239 | 0 | netdev_get_name(port->netdev), |
240 | 0 | port->port_no, port->ifindex); |
241 | 0 | } |
242 | 0 | } |
243 | 0 | } |
244 | | |
245 | | static bool |
246 | | tc_can_offload(struct dpif_offload *dpif_offload OVS_UNUSED, |
247 | | struct netdev *netdev) |
248 | 0 | { |
249 | 0 | if (netdev_vport_is_vport_class(netdev->netdev_class) && |
250 | 0 | strcmp(netdev_get_dpif_type(netdev), "system")) { |
251 | 0 | VLOG_DBG("%s: vport doesn't belong to the system datapath, skipping", |
252 | 0 | netdev_get_name(netdev)); |
253 | 0 | return false; |
254 | 0 | } |
255 | 0 | return true; |
256 | 0 | } |
257 | | |
258 | | static int |
259 | | tc_flow_flush(const struct dpif_offload *offload) |
260 | 0 | { |
261 | 0 | struct dpif_offload_port *port; |
262 | 0 | int error = 0; |
263 | |
|
264 | 0 | DPIF_OFFLOAD_PORT_FOR_EACH (port, offload) { |
265 | 0 | int rc = tc_netdev_flow_flush(port->netdev); |
266 | |
|
267 | 0 | if (rc && !error) { |
268 | 0 | error = rc; |
269 | 0 | } |
270 | 0 | } |
271 | 0 | return error; |
272 | 0 | } |
273 | | |
274 | | static struct tc_flow_dump * |
275 | | tc_flow_dump_cast(struct dpif_offload_flow_dump *dump) |
276 | 0 | { |
277 | 0 | return CONTAINER_OF(dump, struct tc_flow_dump, dump); |
278 | 0 | } |
279 | | |
280 | | static struct tc_flow_dump_thread * |
281 | | tc_flow_dump_thread_cast( |
282 | | struct dpif_offload_flow_dump_thread *thread) |
283 | 0 | { |
284 | 0 | return CONTAINER_OF(thread, struct tc_flow_dump_thread, thread); |
285 | 0 | } |
286 | | |
287 | | static struct dpif_offload_flow_dump * |
288 | | tc_flow_dump_create(const struct dpif_offload *offload, bool terse) |
289 | 0 | { |
290 | 0 | struct dpif_offload_port *port; |
291 | 0 | size_t added_port_count = 0; |
292 | 0 | struct tc_flow_dump *dump; |
293 | 0 | size_t port_count; |
294 | |
|
295 | 0 | port_count = dpif_offload_port_mgr_port_count(offload); |
296 | |
|
297 | 0 | dump = xmalloc(sizeof *dump + |
298 | 0 | (port_count * sizeof(struct netdev_tc_flow_dump))); |
299 | |
|
300 | 0 | dpif_offload_flow_dump_init(&dump->dump, offload, terse); |
301 | |
|
302 | 0 | DPIF_OFFLOAD_PORT_FOR_EACH (port, offload) { |
303 | 0 | if (added_port_count >= port_count) { |
304 | 0 | break; |
305 | 0 | } |
306 | 0 | if (tc_netdev_flow_dump_create( |
307 | 0 | port->netdev, &dump->netdev_dumps[added_port_count], terse)) { |
308 | 0 | continue; |
309 | 0 | } |
310 | 0 | dump->netdev_dumps[added_port_count]->port = port->port_no; |
311 | 0 | added_port_count++; |
312 | 0 | } |
313 | 0 | dump->netdev_dump_count = added_port_count; |
314 | 0 | dump->netdev_dump_index = 0; |
315 | 0 | ovs_mutex_init(&dump->netdev_dump_mutex); |
316 | 0 | return &dump->dump; |
317 | 0 | } |
318 | | |
319 | | static int |
320 | | tc_match_to_dpif_flow(struct match *match, struct ofpbuf *key_buf, |
321 | | struct ofpbuf *mask_buf, struct nlattr *actions, |
322 | | struct dpif_flow_stats *stats, |
323 | | struct dpif_flow_attrs *attrs, ovs_u128 *ufid, |
324 | | struct dpif_flow *flow, bool terse) |
325 | 0 | { |
326 | 0 | memset(flow, 0, sizeof *flow); |
327 | |
|
328 | 0 | if (!terse) { |
329 | 0 | struct odp_flow_key_parms odp_parms = { |
330 | 0 | .flow = &match->flow, |
331 | 0 | .mask = &match->wc.masks, |
332 | 0 | .support = { |
333 | 0 | .max_vlan_headers = 2, |
334 | 0 | .recirc = true, |
335 | 0 | .ct_state = true, |
336 | 0 | .ct_zone = true, |
337 | 0 | .ct_mark = true, |
338 | 0 | .ct_label = true, |
339 | 0 | }, |
340 | 0 | }; |
341 | 0 | size_t offset; |
342 | | |
343 | | /* Key */ |
344 | 0 | offset = key_buf->size; |
345 | 0 | flow->key = ofpbuf_tail(key_buf); |
346 | 0 | odp_flow_key_from_flow(&odp_parms, key_buf); |
347 | 0 | flow->key_len = key_buf->size - offset; |
348 | | |
349 | | /* Mask */ |
350 | 0 | offset = mask_buf->size; |
351 | 0 | flow->mask = ofpbuf_tail(mask_buf); |
352 | 0 | odp_parms.key_buf = key_buf; |
353 | 0 | odp_flow_key_from_mask(&odp_parms, mask_buf); |
354 | 0 | flow->mask_len = mask_buf->size - offset; |
355 | | |
356 | | /* Actions */ |
357 | 0 | flow->actions = nl_attr_get(actions); |
358 | 0 | flow->actions_len = nl_attr_get_size(actions); |
359 | 0 | } |
360 | | |
361 | | /* Stats */ |
362 | 0 | memcpy(&flow->stats, stats, sizeof *stats); |
363 | | |
364 | | /* UFID */ |
365 | 0 | flow->ufid_present = true; |
366 | 0 | flow->ufid = *ufid; |
367 | |
|
368 | 0 | flow->pmd_id = PMD_ID_NULL; |
369 | |
|
370 | 0 | memcpy(&flow->attrs, attrs, sizeof *attrs); |
371 | |
|
372 | 0 | return 0; |
373 | 0 | } |
374 | | |
375 | | static void |
376 | | tc_advance_provider_dump(struct tc_flow_dump_thread *thread) |
377 | 0 | { |
378 | 0 | struct tc_flow_dump *dump = thread->dump; |
379 | |
|
380 | 0 | ovs_mutex_lock(&dump->netdev_dump_mutex); |
381 | | |
382 | | /* If we haven't finished (dumped all providers). */ |
383 | 0 | if (dump->netdev_dump_index < dump->netdev_dump_count) { |
384 | | /* If we are the first to find that current dump is finished |
385 | | * advance it. */ |
386 | 0 | if (thread->netdev_dump_index == dump->netdev_dump_index) { |
387 | 0 | thread->netdev_dump_index = ++dump->netdev_dump_index; |
388 | | /* Did we just finish the last dump? If so we are done. */ |
389 | 0 | if (dump->netdev_dump_index == dump->netdev_dump_count) { |
390 | 0 | thread->netdev_dump_done = true; |
391 | 0 | } |
392 | 0 | } else { |
393 | | /* Otherwise, we are behind, catch up. */ |
394 | 0 | thread->netdev_dump_index = dump->netdev_dump_index; |
395 | 0 | } |
396 | 0 | } else { |
397 | | /* Some other thread finished. */ |
398 | 0 | thread->netdev_dump_done = true; |
399 | 0 | } |
400 | |
|
401 | 0 | ovs_mutex_unlock(&dump->netdev_dump_mutex); |
402 | 0 | } |
403 | | |
404 | | static int |
405 | | tc_flow_dump_next(struct dpif_offload_flow_dump_thread *thread_, |
406 | | struct dpif_flow *flows, int max_flows) |
407 | 0 | { |
408 | 0 | struct tc_flow_dump_thread *thread = tc_flow_dump_thread_cast(thread_); |
409 | 0 | int n_flows = 0; |
410 | |
|
411 | 0 | max_flows = MIN(max_flows, FLOW_DUMP_MAX_BATCH); |
412 | |
|
413 | 0 | while (!thread->netdev_dump_done && n_flows < max_flows) { |
414 | 0 | struct odputil_keybuf *maskbuf = &thread->maskbuf[n_flows]; |
415 | 0 | struct odputil_keybuf *keybuf = &thread->keybuf[n_flows]; |
416 | 0 | struct odputil_keybuf *actbuf = &thread->actbuf[n_flows]; |
417 | 0 | struct netdev_tc_flow_dump *netdev_dump; |
418 | 0 | struct dpif_flow *f = &flows[n_flows]; |
419 | 0 | int cur = thread->netdev_dump_index; |
420 | 0 | struct ofpbuf key, mask, act; |
421 | 0 | struct dpif_flow_stats stats; |
422 | 0 | struct dpif_flow_attrs attrs; |
423 | 0 | struct nlattr *actions; |
424 | 0 | struct match match; |
425 | 0 | ovs_u128 ufid; |
426 | 0 | bool has_next; |
427 | |
|
428 | 0 | netdev_dump = thread->dump->netdev_dumps[cur]; |
429 | 0 | ofpbuf_use_stack(&key, keybuf, sizeof *keybuf); |
430 | 0 | ofpbuf_use_stack(&act, actbuf, sizeof *actbuf); |
431 | 0 | ofpbuf_use_stack(&mask, maskbuf, sizeof *maskbuf); |
432 | 0 | has_next = tc_netdev_flow_dump_next(netdev_dump, &match, &actions, |
433 | 0 | &stats, &attrs, &ufid, |
434 | 0 | &thread->nl_flows, &act); |
435 | 0 | if (has_next) { |
436 | 0 | tc_match_to_dpif_flow(&match, &key, &mask, actions, &stats, |
437 | 0 | &attrs, &ufid, f, thread->dump->dump.terse); |
438 | 0 | n_flows++; |
439 | 0 | } else { |
440 | 0 | tc_advance_provider_dump(thread); |
441 | 0 | } |
442 | 0 | } |
443 | 0 | return n_flows; |
444 | 0 | } |
445 | | |
446 | | static int |
447 | | tc_flow_dump_destroy(struct dpif_offload_flow_dump *dump_) |
448 | 0 | { |
449 | 0 | struct tc_flow_dump *dump = tc_flow_dump_cast(dump_); |
450 | 0 | int error = 0; |
451 | |
|
452 | 0 | for (int i = 0; i < dump->netdev_dump_count; i++) { |
453 | 0 | struct netdev_tc_flow_dump *dump_netdev = dump->netdev_dumps[i]; |
454 | 0 | int rc = tc_netdev_flow_dump_destroy(dump_netdev); |
455 | |
|
456 | 0 | if (rc && !error) { |
457 | 0 | error = rc; |
458 | 0 | } |
459 | 0 | } |
460 | 0 | ovs_mutex_destroy(&dump->netdev_dump_mutex); |
461 | 0 | free(dump); |
462 | 0 | return error; |
463 | 0 | } |
464 | | |
465 | | static struct dpif_offload_flow_dump_thread * |
466 | | tc_flow_dump_thread_create(struct dpif_offload_flow_dump *dump) |
467 | 0 | { |
468 | 0 | struct tc_flow_dump_thread *thread; |
469 | |
|
470 | 0 | thread = xmalloc(sizeof *thread); |
471 | 0 | dpif_offload_flow_dump_thread_init(&thread->thread, dump); |
472 | 0 | thread->dump = tc_flow_dump_cast(dump); |
473 | 0 | thread->netdev_dump_index = 0; |
474 | 0 | thread->netdev_dump_done = !thread->dump->netdev_dump_count; |
475 | 0 | ofpbuf_init(&thread->nl_flows, NL_DUMP_BUFSIZE); |
476 | 0 | return &thread->thread; |
477 | 0 | } |
478 | | |
479 | | static void |
480 | | tc_flow_dump_thread_destroy(struct dpif_offload_flow_dump_thread *thread_) |
481 | 0 | { |
482 | 0 | struct tc_flow_dump_thread *thread = tc_flow_dump_thread_cast(thread_); |
483 | |
|
484 | 0 | ofpbuf_uninit(&thread->nl_flows); |
485 | 0 | free(thread); |
486 | 0 | } |
487 | | |
488 | | static int |
489 | | tc_parse_flow_put(struct tc_offload *offload_tc, struct dpif *dpif, |
490 | | struct dpif_flow_put *put) |
491 | 0 | { |
492 | 0 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); |
493 | 0 | struct dpif_offload_port *port; |
494 | 0 | const struct nlattr *nla; |
495 | 0 | struct tc_offload_info info; |
496 | 0 | struct match match; |
497 | 0 | odp_port_t in_port; |
498 | 0 | size_t left; |
499 | 0 | int err; |
500 | |
|
501 | 0 | info.tc_modify_flow_deleted = false; |
502 | 0 | info.tc_modify_flow = false; |
503 | |
|
504 | 0 | if (put->flags & DPIF_FP_PROBE) { |
505 | 0 | return EOPNOTSUPP; |
506 | 0 | } |
507 | | |
508 | 0 | err = parse_key_and_mask_to_match(put->key, put->key_len, put->mask, |
509 | 0 | put->mask_len, &match); |
510 | 0 | if (err) { |
511 | 0 | return err; |
512 | 0 | } |
513 | | |
514 | 0 | in_port = match.flow.in_port.odp_port; |
515 | 0 | port = dpif_offload_port_mgr_find_by_odp_port(&offload_tc->offload, |
516 | 0 | in_port); |
517 | 0 | if (!port) { |
518 | 0 | return EOPNOTSUPP; |
519 | 0 | } |
520 | | |
521 | | /* Check the output port for a tunnel. */ |
522 | 0 | NL_ATTR_FOR_EACH (nla, left, put->actions, put->actions_len) { |
523 | 0 | if (nl_attr_type(nla) == OVS_ACTION_ATTR_OUTPUT) { |
524 | 0 | struct dpif_offload_port *mgr_port; |
525 | 0 | odp_port_t out_port; |
526 | |
|
527 | 0 | out_port = nl_attr_get_odp_port(nla); |
528 | 0 | mgr_port = dpif_offload_port_mgr_find_by_odp_port( |
529 | 0 | &offload_tc->offload, out_port); |
530 | |
|
531 | 0 | if (!mgr_port) { |
532 | 0 | err = EOPNOTSUPP; |
533 | 0 | goto out; |
534 | 0 | } |
535 | 0 | } |
536 | 0 | } |
537 | | |
538 | 0 | info.recirc_id_shared_with_tc = offload_tc->recirc_id_shared; |
539 | |
|
540 | 0 | err = tc_netdev_flow_put(dpif, port->netdev, &match, |
541 | 0 | CONST_CAST(struct nlattr *, put->actions), |
542 | 0 | put->actions_len, |
543 | 0 | CONST_CAST(ovs_u128 *, put->ufid), |
544 | 0 | &info, put->stats); |
545 | |
|
546 | 0 | if (!err) { |
547 | 0 | if (put->flags & DPIF_FP_MODIFY && !info.tc_modify_flow) { |
548 | 0 | struct dpif_op *opp; |
549 | 0 | struct dpif_op op; |
550 | |
|
551 | 0 | op.type = DPIF_OP_FLOW_DEL; |
552 | 0 | op.flow_del.key = put->key; |
553 | 0 | op.flow_del.key_len = put->key_len; |
554 | 0 | op.flow_del.ufid = put->ufid; |
555 | 0 | op.flow_del.pmd_id = put->pmd_id; |
556 | 0 | op.flow_del.stats = NULL; |
557 | 0 | op.flow_del.terse = false; |
558 | |
|
559 | 0 | opp = &op; |
560 | 0 | dpif_operate(dpif, &opp, 1, DPIF_OFFLOAD_NEVER); |
561 | 0 | } |
562 | |
|
563 | 0 | VLOG_DBG("added flow"); |
564 | 0 | } else if (err != EEXIST) { |
565 | 0 | struct netdev *oor_netdev = NULL; |
566 | 0 | enum vlog_level level; |
567 | |
|
568 | 0 | if (err == ENOSPC && dpif_offload_rebalance_policy_enabled()) { |
569 | | /* |
570 | | * We need to set OOR on the input netdev (i.e, 'dev') for the |
571 | | * flow. But if the flow has a tunnel attribute (i.e, decap |
572 | | * action, with a virtual device like a VxLAN interface as its |
573 | | * in-port), then lookup and set OOR on the underlying tunnel |
574 | | * (real) netdev. */ |
575 | 0 | oor_netdev = flow_get_tunnel_netdev(&match.flow.tunnel); |
576 | 0 | if (!oor_netdev) { |
577 | | /* Not a 'tunnel' flow. */ |
578 | 0 | oor_netdev = port->netdev; |
579 | 0 | } |
580 | 0 | netdev_set_hw_info(oor_netdev, HW_INFO_TYPE_OOR, true); |
581 | 0 | } |
582 | 0 | level = (err == ENOSPC || err == EOPNOTSUPP) ? VLL_DBG : VLL_ERR; |
583 | 0 | VLOG_RL(&rl, level, "failed to offload flow: %s: %s", |
584 | 0 | ovs_strerror(err), |
585 | 0 | (oor_netdev ? netdev_get_name(oor_netdev) : |
586 | 0 | netdev_get_name(port->netdev))); |
587 | 0 | } |
588 | |
|
589 | 0 | out: |
590 | 0 | if (err && err != EEXIST && (put->flags & DPIF_FP_MODIFY)) { |
591 | | /* Modified rule can't be offloaded, try and delete from HW. */ |
592 | 0 | int del_err = 0; |
593 | |
|
594 | 0 | if (!info.tc_modify_flow_deleted) { |
595 | 0 | del_err = tc_netdev_flow_del(put->ufid, put->stats); |
596 | 0 | } |
597 | |
|
598 | 0 | if (!del_err) { |
599 | | /* Delete from hw success, so old flow was offloaded. |
600 | | * Change flags to create the flow at the dpif level. */ |
601 | 0 | put->flags &= ~DPIF_FP_MODIFY; |
602 | 0 | put->flags |= DPIF_FP_CREATE; |
603 | 0 | } else if (del_err != ENOENT) { |
604 | 0 | VLOG_ERR_RL(&rl, "failed to delete offloaded flow: %s", |
605 | 0 | ovs_strerror(del_err)); |
606 | | /* Stop processing the flow in kernel. */ |
607 | 0 | err = 0; |
608 | 0 | } |
609 | 0 | } |
610 | |
|
611 | 0 | return err; |
612 | 0 | } |
613 | | |
614 | | static int |
615 | | tc_parse_flow_get(struct tc_offload *offload_tc, struct dpif_flow_get *get) |
616 | 0 | { |
617 | 0 | struct dpif_flow *dpif_flow = get->flow; |
618 | 0 | struct dpif_offload_port *port; |
619 | 0 | struct odputil_keybuf maskbuf; |
620 | 0 | struct odputil_keybuf keybuf; |
621 | 0 | struct odputil_keybuf actbuf; |
622 | 0 | struct ofpbuf key, mask, act; |
623 | 0 | struct dpif_flow_stats stats; |
624 | 0 | struct dpif_flow_attrs attrs; |
625 | 0 | uint64_t act_buf[1024 / 8]; |
626 | 0 | struct nlattr *actions; |
627 | 0 | struct match match; |
628 | 0 | struct ofpbuf buf; |
629 | 0 | int err = ENOENT; |
630 | |
|
631 | 0 | ofpbuf_use_stack(&buf, &act_buf, sizeof act_buf); |
632 | |
|
633 | 0 | DPIF_OFFLOAD_PORT_FOR_EACH (port, &offload_tc->offload) { |
634 | 0 | if (!tc_netdev_flow_get(port->netdev, &match, &actions, get->ufid, |
635 | 0 | &stats, &attrs, &buf)) { |
636 | 0 | err = 0; |
637 | 0 | break; |
638 | 0 | } |
639 | 0 | } |
640 | |
|
641 | 0 | if (err) { |
642 | 0 | return err; |
643 | 0 | } |
644 | | |
645 | 0 | VLOG_DBG("found flow from netdev, translating to dpif flow"); |
646 | |
|
647 | 0 | ofpbuf_use_stack(&key, &keybuf, sizeof keybuf); |
648 | 0 | ofpbuf_use_stack(&act, &actbuf, sizeof actbuf); |
649 | 0 | ofpbuf_use_stack(&mask, &maskbuf, sizeof maskbuf); |
650 | 0 | tc_match_to_dpif_flow(&match, &key, &mask, actions, &stats, &attrs, |
651 | 0 | (ovs_u128 *) get->ufid, dpif_flow, false); |
652 | 0 | ofpbuf_put(get->buffer, nl_attr_get(actions), nl_attr_get_size(actions)); |
653 | 0 | dpif_flow->actions = ofpbuf_at(get->buffer, 0, 0); |
654 | 0 | dpif_flow->actions_len = nl_attr_get_size(actions); |
655 | |
|
656 | 0 | return 0; |
657 | 0 | } |
658 | | |
659 | | static void |
660 | | tc_operate(struct dpif *dpif, const struct dpif_offload *offload_, |
661 | | struct dpif_op **ops, size_t n_ops) |
662 | 0 | { |
663 | 0 | struct tc_offload *offload = tc_offload_cast(offload_); |
664 | |
|
665 | 0 | for (size_t i = 0; i < n_ops; i++) { |
666 | 0 | struct dpif_op *op = ops[i]; |
667 | 0 | int error = EOPNOTSUPP; |
668 | |
|
669 | 0 | if (op->error >= 0) { |
670 | 0 | continue; |
671 | 0 | } |
672 | | |
673 | 0 | switch (op->type) { |
674 | 0 | case DPIF_OP_FLOW_PUT: { |
675 | 0 | struct dpif_flow_put *put = &op->flow_put; |
676 | |
|
677 | 0 | if (!put->ufid) { |
678 | 0 | break; |
679 | 0 | } |
680 | | |
681 | 0 | error = tc_parse_flow_put(offload, dpif, put); |
682 | 0 | break; |
683 | 0 | } |
684 | 0 | case DPIF_OP_FLOW_DEL: { |
685 | 0 | struct dpif_flow_del *del = &op->flow_del; |
686 | |
|
687 | 0 | if (!del->ufid) { |
688 | 0 | break; |
689 | 0 | } |
690 | | |
691 | 0 | error = tc_netdev_flow_del(del->ufid, del->stats); |
692 | 0 | break; |
693 | 0 | } |
694 | 0 | case DPIF_OP_FLOW_GET: { |
695 | 0 | struct dpif_flow_get *get = &op->flow_get; |
696 | |
|
697 | 0 | if (!get->ufid) { |
698 | 0 | break; |
699 | 0 | } |
700 | | |
701 | 0 | error = tc_parse_flow_get(offload, get); |
702 | 0 | break; |
703 | 0 | } |
704 | 0 | case DPIF_OP_EXECUTE: |
705 | 0 | break; |
706 | 0 | } /* End of 'switch (op->type)'. */ |
707 | | |
708 | 0 | if (error != EOPNOTSUPP && error != ENOENT) { |
709 | | /* If the operation is unsupported or the entry was not found, |
710 | | * we are skipping this flow operation. Otherwise, it was |
711 | | * processed and we should report the result. */ |
712 | 0 | op->error = error; |
713 | 0 | } |
714 | 0 | } |
715 | 0 | } |
716 | | |
717 | | odp_port_t |
718 | | tc_get_port_id_by_ifindex(const struct dpif_offload *offload, int ifindex) |
719 | 0 | { |
720 | 0 | struct dpif_offload_port *port; |
721 | |
|
722 | 0 | port = dpif_offload_port_mgr_find_by_ifindex(offload, ifindex); |
723 | 0 | if (port) { |
724 | 0 | return port->port_no; |
725 | 0 | } |
726 | 0 | return ODPP_NONE; |
727 | 0 | } |
728 | | |
729 | | struct dpif_offload_class dpif_offload_tc_class = { |
730 | | .type = "tc", |
731 | | .impl_type = DPIF_OFFLOAD_IMPL_FLOWS_PROVIDER_ONLY, |
732 | | .supported_dpif_types = (const char *const[]) {"system", NULL}, |
733 | | .open = tc_offload_open, |
734 | | .close = tc_offload_close, |
735 | | .set_config = tc_offload_set_config, |
736 | | .get_debug = tc_offload_get_debug, |
737 | | .can_offload = tc_can_offload, |
738 | | .port_add = tc_port_add, |
739 | | .port_del = tc_port_del, |
740 | | .flow_flush = tc_flow_flush, |
741 | | .flow_dump_create = tc_flow_dump_create, |
742 | | .flow_dump_next = tc_flow_dump_next, |
743 | | .flow_dump_destroy = tc_flow_dump_destroy, |
744 | | .flow_dump_thread_create = tc_flow_dump_thread_create, |
745 | | .flow_dump_thread_destroy = tc_flow_dump_thread_destroy, |
746 | | .operate = tc_operate, |
747 | | .flow_count = tc_flow_count, |
748 | | .meter_set = tc_meter_set, |
749 | | .meter_get = tc_meter_get, |
750 | | .meter_del = tc_meter_del, |
751 | | .get_netdev = tc_get_netdev, |
752 | | }; |