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