• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* SPDX-License-Identifier: LGPL-2.1-only */
2 /*
3  * Copyright (c) 2017 David Ahern <dsa@cumulusnetworks.com>
4  */
5 
6 /**
7  * @ingroup rtnl
8  * @defgroup netconf Netconf
9  * @brief
10  *
11  * @{
12  */
13 
14 #include <netlink-private/netlink.h>
15 #include <netlink/netlink.h>
16 #include <netlink/utils.h>
17 #include <netlink/route/netconf.h>
18 #include <linux/netconf.h>
19 #include <linux/socket.h>
20 #include <netlink/hashtable.h>
21 
22 /** @cond SKIP */
23 #define NETCONF_ATTR_FAMILY		0x0001
24 #define NETCONF_ATTR_IFINDEX		0x0002
25 #define NETCONF_ATTR_RP_FILTER		0x0004
26 #define NETCONF_ATTR_FWDING		0x0008
27 #define NETCONF_ATTR_MC_FWDING		0x0010
28 #define NETCONF_ATTR_PROXY_NEIGH	0x0020
29 #define NETCONF_ATTR_IGNORE_RT_LINKDWN	0x0040
30 #define NETCONF_ATTR_INPUT		0x0080
31 
32 struct rtnl_netconf
33 {
34 	NLHDR_COMMON
35 
36 	int	family;
37 	int	ifindex;
38 	int	rp_filter;
39 	int	forwarding;
40 	int	mc_forwarding;
41 	int	proxy_neigh;
42 	int	ignore_routes_linkdown;
43 	int	input;
44 };
45 
46 static struct nl_cache_ops rtnl_netconf_ops;
47 static struct nl_object_ops netconf_obj_ops;
48 /** @endcond */
49 
50 static struct nla_policy devconf_ipv4_policy[NETCONFA_MAX+1] = {
51 	[NETCONFA_IFINDEX]	 = { .type = NLA_S32 },
52 	[NETCONFA_FORWARDING]	 = { .type = NLA_S32 },
53 	[NETCONFA_MC_FORWARDING] = { .type = NLA_S32 },
54 	[NETCONFA_RP_FILTER]	 = { .type = NLA_S32 },
55 	[NETCONFA_PROXY_NEIGH]	 = { .type = NLA_S32 },
56 	[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN]  = { .type = NLA_S32 },
57 };
58 
59 static struct nla_policy devconf_ipv6_policy[NETCONFA_MAX+1] = {
60 	[NETCONFA_IFINDEX]	 = { .type = NLA_S32 },
61 	[NETCONFA_FORWARDING]	 = { .type = NLA_S32 },
62 	[NETCONFA_MC_FORWARDING] = { .type = NLA_S32 },
63 	[NETCONFA_PROXY_NEIGH]	 = { .type = NLA_S32 },
64 	[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN]  = { .type = NLA_S32 },
65 };
66 
67 static struct nla_policy devconf_mpls_policy[NETCONFA_MAX+1] = {
68 	[NETCONFA_IFINDEX]	 = { .type = NLA_S32 },
69 	[NETCONFA_INPUT]	 = { .type = NLA_S32 },
70 };
71 
rtnl_netconf_alloc(void)72 static struct rtnl_netconf *rtnl_netconf_alloc(void)
73 {
74 	return (struct rtnl_netconf *) nl_object_alloc(&netconf_obj_ops);
75 }
76 
netconf_msg_parser(struct nl_cache_ops * ops,struct sockaddr_nl * who,struct nlmsghdr * nlh,struct nl_parser_param * pp)77 static int netconf_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
78 			      struct nlmsghdr *nlh, struct nl_parser_param *pp)
79 {
80 	struct nlattr *tb[NETCONFA_MAX+1], *attr;
81 	struct rtnl_netconf *nc;
82 	struct netconfmsg *ncm;
83 	int err;
84 
85 	ncm = nlmsg_data(nlh);
86 	switch (ncm->ncm_family) {
87 	case AF_INET:
88 		err = nlmsg_parse(nlh, sizeof(*ncm), tb, NETCONFA_MAX,
89 				  devconf_ipv4_policy);
90 		if (err < 0)
91 			return err;
92 		break;
93 	case AF_INET6:
94 		err = nlmsg_parse(nlh, sizeof(*ncm), tb, NETCONFA_MAX,
95 				  devconf_ipv6_policy);
96 		if (err < 0)
97 			return err;
98 		break;
99 	case AF_MPLS:
100 		err = nlmsg_parse(nlh, sizeof(*ncm), tb, NETCONFA_MAX,
101 				  devconf_mpls_policy);
102 		if (err < 0)
103 			return err;
104 		break;
105 	default:
106 		printf("unexpected netconf family: %d\n", ncm->ncm_family);
107 		return -1;
108 	}
109 
110 	if (!tb[NETCONFA_IFINDEX])
111 		return -1;
112 
113 	nc = rtnl_netconf_alloc();
114 	if (!nc)
115 		return -NLE_NOMEM;
116 
117 	nc->ce_msgtype = nlh->nlmsg_type;
118 	nc->family = ncm->ncm_family;
119 	nc->ifindex = nla_get_s32(tb[NETCONFA_IFINDEX]);
120 
121 	nc->ce_mask = NETCONF_ATTR_FAMILY | NETCONF_ATTR_IFINDEX;
122 
123 
124 	if (tb[NETCONFA_RP_FILTER]) {
125 		attr = tb[NETCONFA_RP_FILTER];
126 		nc->rp_filter = nla_get_s32(attr);
127 		nc->ce_mask |= NETCONF_ATTR_RP_FILTER;
128 	}
129 
130 	if (tb[NETCONFA_FORWARDING]) {
131 		attr = tb[NETCONFA_FORWARDING];
132 		nc->forwarding = nla_get_s32(attr);
133 		nc->ce_mask |= NETCONF_ATTR_FWDING;
134 	}
135 
136 	if (tb[NETCONFA_MC_FORWARDING]) {
137 		attr = tb[NETCONFA_MC_FORWARDING];
138 		nc->mc_forwarding = nla_get_s32(attr);
139 		nc->ce_mask |= NETCONF_ATTR_MC_FWDING;
140 	}
141 
142 	if (tb[NETCONFA_PROXY_NEIGH]) {
143 		attr = tb[NETCONFA_PROXY_NEIGH];
144 		nc->proxy_neigh = nla_get_s32(attr);
145 		nc->ce_mask |= NETCONF_ATTR_PROXY_NEIGH;
146 	}
147 
148 	if (tb[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN]) {
149 		attr = tb[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN];
150 		nc->ignore_routes_linkdown = nla_get_s32(attr);
151 		nc->ce_mask |= NETCONF_ATTR_IGNORE_RT_LINKDWN;
152 	}
153 
154 	if (tb[NETCONFA_INPUT]) {
155 		attr = tb[NETCONFA_INPUT];
156 		nc->input = nla_get_s32(attr);
157 		nc->ce_mask |= NETCONF_ATTR_INPUT;
158 	}
159 
160 	err = pp->pp_cb((struct nl_object *) nc, pp);
161 
162 	rtnl_netconf_put(nc);
163 	return err;
164 }
165 
netconf_request_update(struct nl_cache * cache,struct nl_sock * sk)166 static int netconf_request_update(struct nl_cache *cache, struct nl_sock *sk)
167 {
168 	struct netconfmsg nc = {
169 		.ncm_family = cache->c_iarg1,
170 	};
171 
172 	return nl_send_simple(sk, RTM_GETNETCONF, NLM_F_DUMP, &nc, sizeof(nc));
173 }
174 
netconf_dump_line(struct nl_object * obj,struct nl_dump_params * p)175 static void netconf_dump_line(struct nl_object *obj, struct nl_dump_params *p)
176 {
177 	struct rtnl_netconf *nc = (struct rtnl_netconf *) obj;
178 	struct nl_cache *link_cache;
179 	char buf[64];
180 
181 	switch(nc->family) {
182 	case AF_INET:
183 		nl_dump(p, "ipv4 ");
184 		break;
185 	case AF_INET6:
186 		nl_dump(p, "ipv6 ");
187 		break;
188 	case AF_MPLS:
189 		nl_dump(p, "mpls ");
190 		break;
191 	default:
192 		return;
193 	}
194 
195 	switch(nc->ifindex) {
196 	case NETCONFA_IFINDEX_ALL:
197 		nl_dump(p, "all ");
198 		break;
199 	case NETCONFA_IFINDEX_DEFAULT:
200 		nl_dump(p, "default ");
201 		break;
202 	default:
203 		link_cache = nl_cache_mngt_require_safe("route/link");
204 		if (link_cache) {
205 			nl_dump(p, "dev %s ",
206 				rtnl_link_i2name(link_cache, nc->ifindex,
207 						 buf, sizeof(buf)));
208 			nl_cache_put(link_cache);
209 		} else
210 			nl_dump(p, "dev %d ", nc->ifindex);
211 	}
212 
213 	if (nc->ce_mask & NETCONF_ATTR_FWDING) {
214 		nl_dump(p, "forwarding %s ",
215 			nc->forwarding ? "on" : "off");
216 	}
217 
218 	if (nc->ce_mask & NETCONF_ATTR_RP_FILTER) {
219 		if (nc->rp_filter == 0)
220 			nl_dump(p, "rp_filter off ");
221 		else if (nc->rp_filter == 1)
222 			nl_dump(p, "rp_filter strict ");
223 		else if (nc->rp_filter == 2)
224 			nl_dump(p, "rp_filter loose ");
225 		else
226 			nl_dump(p, "rp_filter unknown-mode ");
227 	}
228 
229 	if (nc->ce_mask & NETCONF_ATTR_MC_FWDING) {
230 		nl_dump(p, "mc_forwarding %s ",
231 			nc->mc_forwarding ? "on" : "off");
232 	}
233 
234 	if (nc->ce_mask & NETCONF_ATTR_PROXY_NEIGH)
235 		nl_dump(p, "proxy_neigh %d ", nc->proxy_neigh);
236 
237 	if (nc->ce_mask & NETCONF_ATTR_IGNORE_RT_LINKDWN) {
238 		nl_dump(p, "ignore_routes_with_linkdown %s ",
239 			nc->ignore_routes_linkdown ? "on" : "off");
240 	}
241 
242 	if (nc->ce_mask & NETCONF_ATTR_INPUT)
243 		nl_dump(p, "input %s ", nc->input ? "on" : "off");
244 
245 	nl_dump(p, "\n");
246 }
247 
248 static const struct trans_tbl netconf_attrs[] = {
249 	__ADD(NETCONF_ATTR_FAMILY, family),
250 	__ADD(NETCONF_ATTR_IFINDEX, ifindex),
251 	__ADD(NETCONF_ATTR_RP_FILTER, rp_filter),
252 	__ADD(NETCONF_ATTR_FWDING, forwarding),
253 	__ADD(NETCONF_ATTR_MC_FWDING, mc_forwarding),
254 	__ADD(NETCONF_ATTR_PROXY_NEIGH, proxy_neigh),
255 	__ADD(NETCONF_ATTR_IGNORE_RT_LINKDWN, ignore_routes_with_linkdown),
256 	__ADD(NETCONF_ATTR_INPUT, input),
257 };
258 
netconf_attrs2str(int attrs,char * buf,size_t len)259 static char *netconf_attrs2str(int attrs, char *buf, size_t len)
260 {
261 	return __flags2str(attrs, buf, len, netconf_attrs,
262 			   ARRAY_SIZE(netconf_attrs));
263 }
264 
netconf_keygen(struct nl_object * obj,uint32_t * hashkey,uint32_t table_sz)265 static void netconf_keygen(struct nl_object *obj, uint32_t *hashkey,
266 			   uint32_t table_sz)
267 {
268 	struct rtnl_netconf *nc = (struct rtnl_netconf *) obj;
269 	unsigned int nckey_sz;
270 	struct nc_hash_key {
271 		int        nc_family;
272 		int        nc_index;
273 	} __attribute__((packed)) nckey;
274 
275 	nckey_sz = sizeof(nckey);
276 	nckey.nc_family = nc->family;
277 	nckey.nc_index = nc->ifindex;
278 
279 	*hashkey = nl_hash(&nckey, nckey_sz, 0) % table_sz;
280 
281 	NL_DBG(5, "netconf %p key (dev %d fam %d) keysz %d, hash 0x%x\n",
282 	       nc, nckey.nc_index, nckey.nc_family, nckey_sz, *hashkey);
283 }
284 
netconf_compare(struct nl_object * _a,struct nl_object * _b,uint64_t attrs,int flags)285 static uint64_t netconf_compare(struct nl_object *_a, struct nl_object *_b,
286 			     uint64_t attrs, int flags)
287 {
288 	struct rtnl_netconf *a = (struct rtnl_netconf *) _a;
289 	struct rtnl_netconf *b = (struct rtnl_netconf *) _b;
290 	uint64_t diff = 0;
291 
292 #define NETCONF_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, NETCONF_ATTR_##ATTR, a, b, EXPR)
293 
294 	diff |= NETCONF_DIFF(FAMILY,	a->family != b->family);
295 	diff |= NETCONF_DIFF(IFINDEX,	a->ifindex != b->ifindex);
296 	diff |= NETCONF_DIFF(RP_FILTER,	a->rp_filter != b->rp_filter);
297 	diff |= NETCONF_DIFF(FWDING,	a->forwarding != b->forwarding);
298 	diff |= NETCONF_DIFF(MC_FWDING,	a->mc_forwarding != b->mc_forwarding);
299 	diff |= NETCONF_DIFF(PROXY_NEIGH, a->proxy_neigh != b->proxy_neigh);
300 	diff |= NETCONF_DIFF(IGNORE_RT_LINKDWN,
301 			a->ignore_routes_linkdown != b->ignore_routes_linkdown);
302 	diff |= NETCONF_DIFF(INPUT,	a->input != b->input);
303 
304 #undef NETCONF_DIFF
305 
306 	return diff;
307 }
308 
netconf_update(struct nl_object * old_obj,struct nl_object * new_obj)309 static int netconf_update(struct nl_object *old_obj, struct nl_object *new_obj)
310 {
311 	struct rtnl_netconf *new_nc = (struct rtnl_netconf *) new_obj;
312 	struct rtnl_netconf *old_nc = (struct rtnl_netconf *) old_obj;
313 	int action = new_obj->ce_msgtype;
314 
315 	switch(action) {
316 	case RTM_NEWNETCONF:
317 		if (new_nc->family != old_nc->family ||
318 		    new_nc->ifindex != old_nc->ifindex)
319 			return -NLE_OPNOTSUPP;
320 
321 		if (new_nc->ce_mask & NETCONF_ATTR_RP_FILTER)
322 			old_nc->rp_filter = new_nc->rp_filter;
323 		if (new_nc->ce_mask & NETCONF_ATTR_FWDING)
324 			old_nc->forwarding = new_nc->forwarding;
325 		if (new_nc->ce_mask & NETCONF_ATTR_MC_FWDING)
326 			old_nc->mc_forwarding = new_nc->mc_forwarding;
327 		if (new_nc->ce_mask & NETCONF_ATTR_PROXY_NEIGH)
328 			old_nc->proxy_neigh = new_nc->proxy_neigh;
329 		if (new_nc->ce_mask & NETCONF_ATTR_IGNORE_RT_LINKDWN)
330 			old_nc->ignore_routes_linkdown = new_nc->ignore_routes_linkdown;
331 
332 		break;
333 	default:
334 		return -NLE_OPNOTSUPP;
335 	}
336 
337 	return NLE_SUCCESS;
338 }
339 
340 /**
341  * @name Cache Management
342  * @{
343  */
344 
rtnl_netconf_alloc_cache(struct nl_sock * sk,struct nl_cache ** result)345 int rtnl_netconf_alloc_cache(struct nl_sock *sk, struct nl_cache **result)
346 {
347 	return nl_cache_alloc_and_fill(&rtnl_netconf_ops, sk, result);
348 }
349 
350 /**
351  * Search netconf in cache
352  * @arg cache		netconf cache
353  * @arg family		Address family of interest
354  * @arg ifindex		Interface index of interest
355  *
356  * Searches netconf cache previously allocated with rtnl_netconf_alloc_cache()
357  * for given index and family
358  *
359  * The reference counter is incremented before returning the netconf entry,
360  * therefore the reference must be given back with rtnl_netconf_put() after
361  * usage.
362  *
363  * @return netconf object or NULL if no match was found.
364  */
rtnl_netconf_get_by_idx(struct nl_cache * cache,int family,int ifindex)365 struct rtnl_netconf *rtnl_netconf_get_by_idx(struct nl_cache *cache, int family,
366 					     int ifindex)
367 {
368 	struct rtnl_netconf *nc;
369 
370 	if (!ifindex || !family || cache->c_ops != &rtnl_netconf_ops)
371 		return NULL;
372 
373 	nl_list_for_each_entry(nc, &cache->c_items, ce_list) {
374 		if (nc->ifindex == ifindex &&
375 		    nc->family == family) {
376 			nl_object_get((struct nl_object *) nc);
377 			return nc;
378 		}
379 	}
380 
381 	return NULL;
382 }
383 
rtnl_netconf_put(struct rtnl_netconf * nc)384 void rtnl_netconf_put(struct rtnl_netconf *nc)
385 {
386 	nl_object_put((struct nl_object *) nc);
387 }
388 
389 /**
390  * Search netconf in cache
391  * @arg cache		netconf cache
392  * @arg family		Address family of interest
393  *
394  * Searches netconf cache previously allocated with rtnl_netconf_alloc_cache()
395  * for "all" netconf settings for given family
396  *
397  * The reference counter is incremented before returning the netconf entry,
398  * therefore the reference must be given back with rtnl_netconf_put() after
399  * usage.
400  *
401  * @return netconf object or NULL if no match was found.
402  */
rtnl_netconf_get_all(struct nl_cache * cache,int family)403 struct rtnl_netconf *rtnl_netconf_get_all(struct nl_cache *cache, int family)
404 {
405 	return rtnl_netconf_get_by_idx(cache, family, NETCONFA_IFINDEX_ALL);
406 }
407 
408 /**
409  * Search netconf in cache
410  * @arg cache		netconf cache
411  * @arg family		Address family of interest
412  *
413  * Searches netconf cache previously allocated with rtnl_netconf_alloc_cache()
414  * for "default" netconf settings for given family
415  *
416  * The reference counter is incremented before returning the netconf entry,
417  * therefore the reference must be given back with rtnl_netconf_put() after
418  * usage.
419  *
420  * @return netconf object or NULL if no match was found.
421  */
rtnl_netconf_get_default(struct nl_cache * cache,int family)422 struct rtnl_netconf *rtnl_netconf_get_default(struct nl_cache *cache, int family)
423 {
424 	return rtnl_netconf_get_by_idx(cache, family, NETCONFA_IFINDEX_DEFAULT);
425 }
426 
427 /** @} */
428 
429 /**
430  * @name Attributes
431  * @{
432  */
433 
rtnl_netconf_get_family(struct rtnl_netconf * nc,int * val)434 int rtnl_netconf_get_family(struct rtnl_netconf *nc, int *val)
435 {
436 	if (!nc)
437 		return -NLE_INVAL;
438 	if (!(nc->ce_mask & NETCONF_ATTR_FAMILY))
439 		return -NLE_MISSING_ATTR;
440 	if (val)
441 		*val = nc->family;
442 	return 0;
443 }
rtnl_netconf_get_ifindex(struct rtnl_netconf * nc,int * val)444 int rtnl_netconf_get_ifindex(struct rtnl_netconf *nc, int *val)
445 {
446 	if (!nc)
447 		return -NLE_INVAL;
448 	if (!(nc->ce_mask & NETCONF_ATTR_IFINDEX))
449 		return -NLE_MISSING_ATTR;
450 	if (val)
451 		*val = nc->ifindex;
452 	return 0;
453 }
rtnl_netconf_get_forwarding(struct rtnl_netconf * nc,int * val)454 int rtnl_netconf_get_forwarding(struct rtnl_netconf *nc, int *val)
455 {
456 	if (!nc)
457 		return -NLE_INVAL;
458 	if (!(nc->ce_mask & NETCONF_ATTR_FWDING))
459 		return -NLE_MISSING_ATTR;
460 	if (val)
461 		*val = nc->forwarding;
462 	return 0;
463 }
rtnl_netconf_get_mc_forwarding(struct rtnl_netconf * nc,int * val)464 int rtnl_netconf_get_mc_forwarding(struct rtnl_netconf *nc, int *val)
465 {
466 	if (!nc)
467 		return -NLE_INVAL;
468 	if (!(nc->ce_mask & NETCONF_ATTR_MC_FWDING))
469 		return -NLE_MISSING_ATTR;
470 	if (val)
471 		*val = nc->mc_forwarding;
472 	return 0;
473 }
rtnl_netconf_get_rp_filter(struct rtnl_netconf * nc,int * val)474 int rtnl_netconf_get_rp_filter(struct rtnl_netconf *nc, int *val)
475 {
476 	if (!nc)
477 		return -NLE_INVAL;
478 	if (!(nc->ce_mask & NETCONF_ATTR_RP_FILTER))
479 		return -NLE_MISSING_ATTR;
480 	if (val)
481 		*val = nc->rp_filter;
482 	return 0;
483 }
rtnl_netconf_get_proxy_neigh(struct rtnl_netconf * nc,int * val)484 int rtnl_netconf_get_proxy_neigh(struct rtnl_netconf *nc, int *val)
485 {
486 	if (!nc)
487 		return -NLE_INVAL;
488 	if (!(nc->ce_mask & NETCONF_ATTR_PROXY_NEIGH))
489 		return -NLE_MISSING_ATTR;
490 	if (val)
491 		*val = nc->proxy_neigh;
492 	return 0;
493 }
rtnl_netconf_get_ignore_routes_linkdown(struct rtnl_netconf * nc,int * val)494 int rtnl_netconf_get_ignore_routes_linkdown(struct rtnl_netconf *nc, int *val)
495 {
496 	if (!nc)
497 		return -NLE_INVAL;
498 	if (!(nc->ce_mask & NETCONF_ATTR_IGNORE_RT_LINKDWN))
499 		return -NLE_MISSING_ATTR;
500 	if (val)
501 		*val = nc->ignore_routes_linkdown;
502 	return 0;
503 }
rtnl_netconf_get_input(struct rtnl_netconf * nc,int * val)504 int rtnl_netconf_get_input(struct rtnl_netconf *nc, int *val)
505 {
506 	if (!nc)
507 		return -NLE_INVAL;
508 	if (!(nc->ce_mask & NETCONF_ATTR_INPUT))
509 		return -NLE_MISSING_ATTR;
510 	if (val)
511 		*val = nc->input;
512 	return 0;
513 }
514 
515 
516 /** @} */
517 
518 static struct nl_object_ops netconf_obj_ops = {
519 	.oo_name		= "route/netconf",
520 	.oo_size		= sizeof(struct rtnl_netconf),
521 	.oo_dump = {
522 	    [NL_DUMP_LINE] 	= netconf_dump_line,
523 	    [NL_DUMP_DETAILS] 	= netconf_dump_line,
524 	},
525 	.oo_compare		= netconf_compare,
526 	.oo_keygen		= netconf_keygen,
527 	.oo_update		= netconf_update,
528 	.oo_attrs2str		= netconf_attrs2str,
529 	.oo_id_attrs		= (NETCONF_ATTR_FAMILY      |
530 				   NETCONF_ATTR_IFINDEX)
531 };
532 
533 static struct nl_af_group netconf_groups[] = {
534 	{ AF_INET,	RTNLGRP_IPV4_NETCONF },
535 	{ AF_INET6,	RTNLGRP_IPV6_NETCONF },
536 	{ AF_MPLS,	RTNLGRP_MPLS_NETCONF },
537 	{ END_OF_GROUP_LIST },
538 };
539 
540 static struct nl_cache_ops rtnl_netconf_ops = {
541 	.co_name		= "route/netconf",
542 	.co_hdrsize		= sizeof(struct netconfmsg),
543 	.co_msgtypes		= {
544 					{ RTM_NEWNETCONF, NL_ACT_NEW, "new" },
545 					{ RTM_DELNETCONF, NL_ACT_DEL, "del" },
546 					{ RTM_GETNETCONF, NL_ACT_GET, "get" },
547 					END_OF_MSGTYPES_LIST,
548 				  },
549 	.co_protocol		= NETLINK_ROUTE,
550 	.co_groups		= netconf_groups,
551 	.co_request_update      = netconf_request_update,
552 	.co_msg_parser          = netconf_msg_parser,
553 	.co_obj_ops		= &netconf_obj_ops,
554 };
555 
netconf_init(void)556 static void __init netconf_init(void)
557 {
558 	nl_cache_mngt_register(&rtnl_netconf_ops);
559 }
560 
netconf_exit(void)561 static void __exit netconf_exit(void)
562 {
563 	nl_cache_mngt_unregister(&rtnl_netconf_ops);
564 }
565 
566 /** @} */
567