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