xref: /aosp_15_r20/external/libnl/lib/route/qdisc.c (revision 4dc78e53d49367fa8e61b07018507c90983a077d)
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