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