1 /* SPDX-License-Identifier: LGPL-2.1-only */
2 /*
3 * Copyright (c) 2003-2011 Thomas Graf <[email protected]>
4 */
5
6 /**
7 * @ingroup tc
8 * @defgroup qdisc Queueing Disciplines
9 * @{
10 */
11
12 #include "nl-default.h"
13
14 #include <netlink/netlink.h>
15 #include <netlink/utils.h>
16 #include <netlink/route/link.h>
17 #include <netlink/route/qdisc.h>
18 #include <netlink/route/class.h>
19 #include <netlink/route/classifier.h>
20
21 #include "tc-api.h"
22
23 static struct nl_cache_ops rtnl_qdisc_ops;
24 static struct nl_object_ops qdisc_obj_ops;
25
qdisc_msg_parser(struct nl_cache_ops * ops,struct sockaddr_nl * who,struct nlmsghdr * n,struct nl_parser_param * pp)26 static int qdisc_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
27 struct nlmsghdr *n, struct nl_parser_param *pp)
28 {
29 struct rtnl_qdisc *qdisc;
30 int err;
31
32 if (!(qdisc = rtnl_qdisc_alloc()))
33 return -NLE_NOMEM;
34
35 if ((err = rtnl_tc_msg_parse(n, TC_CAST(qdisc))) < 0)
36 goto errout;
37
38 err = pp->pp_cb(OBJ_CAST(qdisc), pp);
39 errout:
40 rtnl_qdisc_put(qdisc);
41
42 return err;
43 }
44
qdisc_request_update(struct nl_cache * c,struct nl_sock * sk)45 static int qdisc_request_update(struct nl_cache *c, struct nl_sock *sk)
46 {
47 struct tcmsg tchdr = {
48 .tcm_family = AF_UNSPEC,
49 .tcm_ifindex = c->c_iarg1,
50 };
51
52 return nl_send_simple(sk, RTM_GETQDISC, NLM_F_DUMP, &tchdr,
53 sizeof(tchdr));
54 }
55
56 /**
57 * @name Allocation/Freeing
58 * @{
59 */
60
rtnl_qdisc_alloc(void)61 struct rtnl_qdisc *rtnl_qdisc_alloc(void)
62 {
63 struct rtnl_tc *tc;
64
65 tc = TC_CAST(nl_object_alloc(&qdisc_obj_ops));
66 if (tc)
67 tc->tc_type = RTNL_TC_TYPE_QDISC;
68
69 return (struct rtnl_qdisc *) tc;
70 }
71
rtnl_qdisc_put(struct rtnl_qdisc * qdisc)72 void rtnl_qdisc_put(struct rtnl_qdisc *qdisc)
73 {
74 nl_object_put((struct nl_object *) qdisc);
75 }
76
77 /** @} */
78
79 /**
80 * @name Addition / Modification / Deletion
81 * @{
82 */
83
build_qdisc_msg(struct rtnl_qdisc * qdisc,int type,int flags,struct nl_msg ** result)84 static int build_qdisc_msg(struct rtnl_qdisc *qdisc, int type, int flags,
85 struct nl_msg **result)
86 {
87 if (!(qdisc->ce_mask & TCA_ATTR_IFINDEX)) {
88 APPBUG("ifindex must be specified");
89 return -NLE_MISSING_ATTR;
90 }
91
92 return rtnl_tc_msg_build(TC_CAST(qdisc), type, flags, result);
93 }
94
95 /**
96 * Build a netlink message requesting the addition of a qdisc
97 * @arg qdisc Qdisc to add
98 * @arg flags Additional netlink message flags
99 * @arg result Pointer to store resulting netlink message
100 *
101 * The behaviour of this function is identical to rtnl_qdisc_add() with
102 * the exception that it will not send the message but return it int the
103 * provided return pointer instead.
104 *
105 * @see rtnl_qdisc_add()
106 *
107 * @return 0 on success or a negative error code.
108 */
rtnl_qdisc_build_add_request(struct rtnl_qdisc * qdisc,int flags,struct nl_msg ** result)109 int rtnl_qdisc_build_add_request(struct rtnl_qdisc *qdisc, int flags,
110 struct nl_msg **result)
111 {
112 if (!(qdisc->ce_mask & (TCA_ATTR_HANDLE | TCA_ATTR_PARENT))) {
113 APPBUG("handle or parent must be specified");
114 return -NLE_MISSING_ATTR;
115 }
116
117 return build_qdisc_msg(qdisc, RTM_NEWQDISC, flags, result);
118 }
119
120 /**
121 * Add qdisc
122 * @arg sk Netlink socket
123 * @arg qdisc Qdisc to add
124 * @arg flags Additional netlink message flags
125 *
126 * Builds a \c RTM_NEWQDISC netlink message requesting the addition
127 * of a new qdisc and sends the message to the kernel. The configuration
128 * of the qdisc is derived from the attributes of the specified qdisc.
129 *
130 * The following flags may be specified:
131 * - \c NLM_F_CREATE: Create qdisc if it does not exist, otherwise
132 * -NLE_OBJ_NOTFOUND is returned.
133 * - \c NLM_F_REPLACE: If another qdisc is already attached to the
134 * parent, replace it even if the handles mismatch.
135 * - \c NLM_F_EXCL: Return -NLE_EXISTS if a qdisc with matching
136 * handle exists already.
137 *
138 * Existing qdiscs with matching handles will be updated, unless the
139 * flag \c NLM_F_EXCL is specified. If their handles do not match, the
140 * error -NLE_EXISTS is returned unless the flag \c NLM_F_REPLACE is
141 * specified in which case the existing qdisc is replaced with the new
142 * one. If no matching qdisc exists, it will be created if the flag
143 * \c NLM_F_CREATE is set, otherwise the error -NLE_OBJ_NOTFOUND is
144 * returned.
145 *
146 * After sending, the function will wait for the ACK or an eventual
147 * error message to be received and will therefore block until the
148 * operation has been completed.
149 *
150 * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
151 * this function to return immediately after sending. In this case,
152 * it is the responsibility of the caller to handle any error
153 * messages returned.
154 *
155 * @return 0 on success or a negative error code.
156 */
rtnl_qdisc_add(struct nl_sock * sk,struct rtnl_qdisc * qdisc,int flags)157 int rtnl_qdisc_add(struct nl_sock *sk, struct rtnl_qdisc *qdisc, int flags)
158 {
159 struct nl_msg *msg;
160 int err;
161
162 if ((err = rtnl_qdisc_build_add_request(qdisc, flags, &msg)) < 0)
163 return err;
164
165 return nl_send_sync(sk, msg);
166 }
167
168 /**
169 * Build netlink message requesting the update of a qdisc
170 * @arg qdisc Qdisc to update
171 * @arg new Qdisc with updated attributes
172 * @arg flags Additional netlink message flags
173 * @arg result Pointer to store resulting netlink message
174 *
175 * The behaviour of this function is identical to rtnl_qdisc_update() with
176 * the exception that it will not send the message but return it in the
177 * provided return pointer instead.
178 *
179 * @see rtnl_qdisc_update()
180 *
181 * @return 0 on success or a negative error code.
182 */
rtnl_qdisc_build_update_request(struct rtnl_qdisc * qdisc,struct rtnl_qdisc * new,int flags,struct nl_msg ** result)183 int rtnl_qdisc_build_update_request(struct rtnl_qdisc *qdisc,
184 struct rtnl_qdisc *new, int flags,
185 struct nl_msg **result)
186 {
187 if (flags & (NLM_F_CREATE | NLM_F_EXCL)) {
188 APPBUG("NLM_F_CREATE and NLM_F_EXCL may not be used here, "
189 "use rtnl_qdisc_add()");
190 return -NLE_INVAL;
191 }
192
193 if (!(qdisc->ce_mask & TCA_ATTR_IFINDEX)) {
194 APPBUG("ifindex must be specified");
195 return -NLE_MISSING_ATTR;
196 }
197
198 if (!(qdisc->ce_mask & (TCA_ATTR_HANDLE | TCA_ATTR_PARENT))) {
199 APPBUG("handle or parent must be specified");
200 return -NLE_MISSING_ATTR;
201 }
202
203 rtnl_tc_set_ifindex(TC_CAST(new), qdisc->q_ifindex);
204
205 if (qdisc->ce_mask & TCA_ATTR_HANDLE)
206 rtnl_tc_set_handle(TC_CAST(new), qdisc->q_handle);
207
208 if (qdisc->ce_mask & TCA_ATTR_PARENT)
209 rtnl_tc_set_parent(TC_CAST(new), qdisc->q_parent);
210
211 return build_qdisc_msg(new, RTM_NEWQDISC, flags, result);
212 }
213
214 /**
215 * Update qdisc
216 * @arg sk Netlink socket
217 * @arg qdisc Qdisc to update
218 * @arg new Qdisc with updated attributes
219 * @arg flags Additional netlink message flags
220 *
221 * Builds a \c RTM_NEWQDISC netlink message requesting the update
222 * of an existing qdisc and sends the message to the kernel.
223 *
224 * This function is a varation of rtnl_qdisc_add() to update qdiscs
225 * if the qdisc to be updated is available as qdisc object. The
226 * behaviour is identical to the one of rtnl_qdisc_add except that
227 * before constructing the message, it copies the \c ifindex,
228 * \c handle, and \c parent from the original \p qdisc to the \p new
229 * qdisc.
230 *
231 * After sending, the function will wait for the ACK or an eventual
232 * error message to be received and will therefore block until the
233 * operation has been completed.
234 *
235 * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
236 * this function to return immediately after sending. In this case,
237 * it is the responsibility of the caller to handle any error
238 * messages returned.
239 *
240 * @return 0 on success or a negative error code.
241 */
rtnl_qdisc_update(struct nl_sock * sk,struct rtnl_qdisc * qdisc,struct rtnl_qdisc * new,int flags)242 int rtnl_qdisc_update(struct nl_sock *sk, struct rtnl_qdisc *qdisc,
243 struct rtnl_qdisc *new, int flags)
244 {
245 struct nl_msg *msg;
246 int err;
247
248 err = rtnl_qdisc_build_update_request(qdisc, new, flags, &msg);
249 if (err < 0)
250 return err;
251
252 return nl_send_sync(sk, msg);
253 }
254
255 /**
256 * Build netlink message requesting the deletion of a qdisc
257 * @arg qdisc Qdisc to delete
258 * @arg result Pointer to store resulting netlink message
259 *
260 * The behaviour of this function is identical to rtnl_qdisc_delete() with
261 * the exception that it will not send the message but return it in the
262 * provided return pointer instead.
263 *
264 * @see rtnl_qdisc_delete()
265 *
266 * @return 0 on success or a negative error code.
267 */
rtnl_qdisc_build_delete_request(struct rtnl_qdisc * qdisc,struct nl_msg ** result)268 int rtnl_qdisc_build_delete_request(struct rtnl_qdisc *qdisc,
269 struct nl_msg **result)
270 {
271 struct nl_msg *msg;
272 struct tcmsg tchdr;
273 uint32_t required = TCA_ATTR_IFINDEX | TCA_ATTR_PARENT;
274
275 if ((qdisc->ce_mask & required) != required) {
276 APPBUG("ifindex and parent must be specified");
277 return -NLE_MISSING_ATTR;
278 }
279
280 if (!(msg = nlmsg_alloc_simple(RTM_DELQDISC, 0)))
281 return -NLE_NOMEM;
282
283 memset(&tchdr, 0, sizeof(tchdr));
284
285 tchdr.tcm_family = AF_UNSPEC;
286 tchdr.tcm_ifindex = qdisc->q_ifindex;
287 tchdr.tcm_parent = qdisc->q_parent;
288
289 if (qdisc->ce_mask & TCA_ATTR_HANDLE)
290 tchdr.tcm_handle = qdisc->q_handle;
291
292 if (nlmsg_append(msg, &tchdr, sizeof(tchdr), NLMSG_ALIGNTO) < 0)
293 goto nla_put_failure;
294
295 if (qdisc->ce_mask & TCA_ATTR_KIND)
296 NLA_PUT_STRING(msg, TCA_KIND, qdisc->q_kind);
297
298 *result = msg;
299 return 0;
300
301 nla_put_failure:
302 nlmsg_free(msg);
303 return -NLE_MSGSIZE;
304 }
305
306 /**
307 * Delete qdisc
308 * @arg sk Netlink socket
309 * @arg qdisc Qdisc to add
310 *
311 * Builds a \c RTM_NEWQDISC netlink message requesting the deletion
312 * of a qdisc and sends the message to the kernel.
313 *
314 * The message is constructed out of the following attributes:
315 * - \c ifindex and \c parent
316 * - \c handle (optional, must match if provided)
317 * - \c kind (optional, must match if provided)
318 *
319 * All other qdisc attributes including all qdisc type specific
320 * attributes are ignored.
321 *
322 * After sending, the function will wait for the ACK or an eventual
323 * error message to be received and will therefore block until the
324 * operation has been completed.
325 *
326 * @note It is not possible to delete default qdiscs.
327 *
328 * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
329 * this function to return immediately after sending. In this case,
330 * it is the responsibility of the caller to handle any error
331 * messages returned.
332 *
333 * @return 0 on success or a negative error code.
334 */
rtnl_qdisc_delete(struct nl_sock * sk,struct rtnl_qdisc * qdisc)335 int rtnl_qdisc_delete(struct nl_sock *sk, struct rtnl_qdisc *qdisc)
336 {
337 struct nl_msg *msg;
338 int err;
339
340 if ((err = rtnl_qdisc_build_delete_request(qdisc, &msg)) < 0)
341 return err;
342
343 return nl_send_sync(sk, msg);
344 }
345
346 /** @} */
347
348 /**
349 * @name Cache Related Functions
350 * @{
351 */
352
353 /**
354 * Allocate a cache and fill it with all configured qdiscs
355 * @arg sk Netlink socket
356 * @arg result Pointer to store the created cache
357 *
358 * Allocates a new qdisc cache and fills it with a list of all configured
359 * qdiscs on all network devices. Release the cache with nl_cache_free().
360 *
361 * @return 0 on success or a negative error code.
362 */
rtnl_qdisc_alloc_cache(struct nl_sock * sk,struct nl_cache ** result)363 int rtnl_qdisc_alloc_cache(struct nl_sock *sk, struct nl_cache **result)
364 {
365 return nl_cache_alloc_and_fill(&rtnl_qdisc_ops, sk, result);
366 }
367
368 /**
369 * Search qdisc by interface index and parent
370 * @arg cache Qdisc cache
371 * @arg ifindex Interface index
372 * @arg parent Handle of parent qdisc
373 *
374 * Searches a qdisc cache previously allocated with rtnl_qdisc_alloc_cache()
375 * and searches for a qdisc matching the interface index and parent qdisc.
376 *
377 * The reference counter is incremented before returning the qdisc, therefore
378 * the reference must be given back with rtnl_qdisc_put() after usage.
379 *
380 * @return pointer to qdisc inside the cache or NULL if no match was found.
381 */
rtnl_qdisc_get_by_parent(struct nl_cache * cache,int ifindex,uint32_t parent)382 struct rtnl_qdisc *rtnl_qdisc_get_by_parent(struct nl_cache *cache,
383 int ifindex, uint32_t parent)
384 {
385 struct rtnl_qdisc *q;
386
387 if (cache->c_ops != &rtnl_qdisc_ops)
388 return NULL;
389
390 nl_list_for_each_entry(q, &cache->c_items, ce_list) {
391 if (q->q_parent == parent &&
392 q->q_ifindex == ((unsigned)ifindex)) {
393 nl_object_get((struct nl_object *) q);
394 return q;
395 }
396 }
397
398 return NULL;
399 }
400
401 /**
402 * Search qdisc by kind
403 * @arg cache Qdisc cache
404 * @arg ifindex Interface index
405 * @arg kind Qdisc kind (tbf, htb, cbq, etc)
406 *
407 * Searches a qdisc cache previously allocated with rtnl_qdisc_alloc_cache()
408 * and searches for a qdisc matching the interface index and kind.
409 *
410 * The reference counter is incremented before returning the qdisc, therefore
411 * the reference must be given back with rtnl_qdisc_put() after usage.
412 *
413 * @return pointer to qdisc inside the cache or NULL if no match was found.
414 */
rtnl_qdisc_get_by_kind(struct nl_cache * cache,int ifindex,char * kind)415 struct rtnl_qdisc *rtnl_qdisc_get_by_kind(struct nl_cache *cache,
416 int ifindex, char *kind)
417 {
418 struct rtnl_qdisc *q;
419
420 if (cache->c_ops != &rtnl_qdisc_ops)
421 return NULL;
422
423 nl_list_for_each_entry(q, &cache->c_items, ce_list) {
424 if ((q->q_ifindex == ((unsigned)ifindex)) &&
425 (!strcmp(q->q_kind, kind))) {
426 nl_object_get((struct nl_object *) q);
427 return q;
428 }
429 }
430
431 return NULL;
432 }
433
434 /**
435 * Search qdisc by interface index and handle
436 * @arg cache Qdisc cache
437 * @arg ifindex Interface index
438 * @arg handle Handle
439 *
440 * Searches a qdisc cache previously allocated with rtnl_qdisc_alloc_cache()
441 * and searches for a qdisc matching the interface index and handle.
442 *
443 * The reference counter is incremented before returning the qdisc, therefore
444 * the reference must be given back with rtnl_qdisc_put() after usage.
445 *
446 * @return Qdisc or NULL if no match was found.
447 */
rtnl_qdisc_get(struct nl_cache * cache,int ifindex,uint32_t handle)448 struct rtnl_qdisc *rtnl_qdisc_get(struct nl_cache *cache, int ifindex,
449 uint32_t handle)
450 {
451 struct rtnl_qdisc *q;
452
453 if (cache->c_ops != &rtnl_qdisc_ops)
454 return NULL;
455
456 nl_list_for_each_entry(q, &cache->c_items, ce_list) {
457 if (q->q_handle == handle &&
458 q->q_ifindex == ((unsigned)ifindex)) {
459 nl_object_get((struct nl_object *) q);
460 return q;
461 }
462 }
463
464 return NULL;
465 }
466
467 /** @} */
468
469 /**
470 * @name Deprecated Functions
471 * @{
472 */
473
474 /**
475 * Call a callback for each child class of a qdisc (deprecated)
476 *
477 * @deprecated Use of this function is deprecated, it does not allow
478 * to handle the out of memory situation that can occur.
479 */
rtnl_qdisc_foreach_child(struct rtnl_qdisc * qdisc,struct nl_cache * cache,void (* cb)(struct nl_object *,void *),void * arg)480 void rtnl_qdisc_foreach_child(struct rtnl_qdisc *qdisc, struct nl_cache *cache,
481 void (*cb)(struct nl_object *, void *), void *arg)
482 {
483 struct rtnl_class *filter;
484
485 filter = rtnl_class_alloc();
486 if (!filter)
487 return;
488
489 rtnl_tc_set_parent(TC_CAST(filter), qdisc->q_handle);
490 rtnl_tc_set_ifindex(TC_CAST(filter), qdisc->q_ifindex);
491 rtnl_tc_set_kind(TC_CAST(filter), qdisc->q_kind);
492
493 nl_cache_foreach_filter(cache, OBJ_CAST(filter), cb, arg);
494
495 rtnl_class_put(filter);
496 }
497
498 /**
499 * Call a callback for each filter attached to the qdisc (deprecated)
500 *
501 * @deprecated Use of this function is deprecated, it does not allow
502 * to handle the out of memory situation that can occur.
503 */
rtnl_qdisc_foreach_cls(struct rtnl_qdisc * qdisc,struct nl_cache * cache,void (* cb)(struct nl_object *,void *),void * arg)504 void rtnl_qdisc_foreach_cls(struct rtnl_qdisc *qdisc, struct nl_cache *cache,
505 void (*cb)(struct nl_object *, void *), void *arg)
506 {
507 struct rtnl_cls *filter;
508
509 if (!(filter = rtnl_cls_alloc()))
510 return;
511
512 rtnl_tc_set_ifindex(TC_CAST(filter), qdisc->q_ifindex);
513 rtnl_tc_set_parent(TC_CAST(filter), qdisc->q_parent);
514
515 nl_cache_foreach_filter(cache, OBJ_CAST(filter), cb, arg);
516 rtnl_cls_put(filter);
517 }
518
519 /**
520 * Build a netlink message requesting the update of a qdisc
521 *
522 * @deprecated Use of this function is deprecated in favour of
523 * rtnl_qdisc_build_update_request() due to the missing
524 * possibility of specifying additional flags.
525 */
rtnl_qdisc_build_change_request(struct rtnl_qdisc * qdisc,struct rtnl_qdisc * new,struct nl_msg ** result)526 int rtnl_qdisc_build_change_request(struct rtnl_qdisc *qdisc,
527 struct rtnl_qdisc *new,
528 struct nl_msg **result)
529 {
530 return rtnl_qdisc_build_update_request(qdisc, new, NLM_F_REPLACE,
531 result);
532 }
533
534 /**
535 * Change attributes of a qdisc
536 *
537 * @deprecated Use of this function is deprecated in favour of
538 * rtnl_qdisc_update() due to the missing possibility of
539 * specifying additional flags.
540 */
rtnl_qdisc_change(struct nl_sock * sk,struct rtnl_qdisc * qdisc,struct rtnl_qdisc * new)541 int rtnl_qdisc_change(struct nl_sock *sk, struct rtnl_qdisc *qdisc,
542 struct rtnl_qdisc *new)
543 {
544 return rtnl_qdisc_update(sk, qdisc, new, NLM_F_REPLACE);
545 }
546
547 /** @} */
548
qdisc_dump_details(struct rtnl_tc * tc,struct nl_dump_params * p)549 static void qdisc_dump_details(struct rtnl_tc *tc, struct nl_dump_params *p)
550 {
551 struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) tc;
552
553 nl_dump(p, "refcnt %u", qdisc->q_info);
554 }
555
556 static struct rtnl_tc_type_ops qdisc_ops = {
557 .tt_type = RTNL_TC_TYPE_QDISC,
558 .tt_dump_prefix = "qdisc",
559 .tt_dump = {
560 [NL_DUMP_DETAILS] = qdisc_dump_details,
561 },
562 };
563
564 static struct nl_cache_ops rtnl_qdisc_ops = {
565 .co_name = "route/qdisc",
566 .co_hdrsize = sizeof(struct tcmsg),
567 .co_msgtypes = {
568 { RTM_NEWQDISC, NL_ACT_NEW, "new" },
569 { RTM_DELQDISC, NL_ACT_DEL, "del" },
570 { RTM_GETQDISC, NL_ACT_GET, "get" },
571 END_OF_MSGTYPES_LIST,
572 },
573 .co_protocol = NETLINK_ROUTE,
574 .co_groups = tc_groups,
575 .co_request_update = qdisc_request_update,
576 .co_msg_parser = qdisc_msg_parser,
577 .co_obj_ops = &qdisc_obj_ops,
578 };
579
580 static struct nl_object_ops qdisc_obj_ops = {
581 .oo_name = "route/qdisc",
582 .oo_size = sizeof(struct rtnl_qdisc),
583 .oo_free_data = rtnl_tc_free_data,
584 .oo_clone = rtnl_tc_clone,
585 .oo_dump = {
586 [NL_DUMP_LINE] = rtnl_tc_dump_line,
587 [NL_DUMP_DETAILS] = rtnl_tc_dump_details,
588 [NL_DUMP_STATS] = rtnl_tc_dump_stats,
589 },
590 .oo_compare = rtnl_tc_compare,
591 .oo_id_attrs = (TCA_ATTR_IFINDEX | TCA_ATTR_HANDLE),
592 };
593
qdisc_init(void)594 static void _nl_init qdisc_init(void)
595 {
596 rtnl_tc_type_register(&qdisc_ops);
597 nl_cache_mngt_register(&rtnl_qdisc_ops);
598 }
599
qdisc_exit(void)600 static void _nl_exit qdisc_exit(void)
601 {
602 nl_cache_mngt_unregister(&rtnl_qdisc_ops);
603 rtnl_tc_type_unregister(&qdisc_ops);
604 }
605
606 /** @} */
607