libnl  3.2.19
route_obj.c
1 /*
2  * lib/route/route_obj.c Route Object
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation version 2.1
7  * of the License.
8  *
9  * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
10  */
11 
12 /**
13  * @ingroup route
14  * @defgroup route_obj Route Object
15  *
16  * @par Attributes
17  * @code
18  * Name Default
19  * -------------------------------------------------------------
20  * routing table RT_TABLE_MAIN
21  * scope RT_SCOPE_NOWHERE
22  * tos 0
23  * protocol RTPROT_STATIC
24  * prio 0
25  * family AF_UNSPEC
26  * type RTN_UNICAST
27  * iif NULL
28  * @endcode
29  *
30  * @{
31  */
32 
33 #include <netlink-local.h>
34 #include <netlink/netlink.h>
35 #include <netlink/cache.h>
36 #include <netlink/utils.h>
37 #include <netlink/data.h>
38 #include <netlink/hashtable.h>
39 #include <netlink/route/rtnl.h>
40 #include <netlink/route/route.h>
41 #include <netlink/route/link.h>
42 #include <netlink/route/nexthop.h>
43 
44 /** @cond SKIP */
45 #define ROUTE_ATTR_FAMILY 0x000001
46 #define ROUTE_ATTR_TOS 0x000002
47 #define ROUTE_ATTR_TABLE 0x000004
48 #define ROUTE_ATTR_PROTOCOL 0x000008
49 #define ROUTE_ATTR_SCOPE 0x000010
50 #define ROUTE_ATTR_TYPE 0x000020
51 #define ROUTE_ATTR_FLAGS 0x000040
52 #define ROUTE_ATTR_DST 0x000080
53 #define ROUTE_ATTR_SRC 0x000100
54 #define ROUTE_ATTR_IIF 0x000200
55 #define ROUTE_ATTR_OIF 0x000400
56 #define ROUTE_ATTR_GATEWAY 0x000800
57 #define ROUTE_ATTR_PRIO 0x001000
58 #define ROUTE_ATTR_PREF_SRC 0x002000
59 #define ROUTE_ATTR_METRICS 0x004000
60 #define ROUTE_ATTR_MULTIPATH 0x008000
61 #define ROUTE_ATTR_REALMS 0x010000
62 #define ROUTE_ATTR_CACHEINFO 0x020000
63 /** @endcond */
64 
65 static void route_constructor(struct nl_object *c)
66 {
67  struct rtnl_route *r = (struct rtnl_route *) c;
68 
69  r->rt_family = AF_UNSPEC;
70  r->rt_scope = RT_SCOPE_NOWHERE;
71  r->rt_table = RT_TABLE_MAIN;
72  r->rt_protocol = RTPROT_STATIC;
73  r->rt_type = RTN_UNICAST;
74 
75  nl_init_list_head(&r->rt_nexthops);
76 }
77 
78 static void route_free_data(struct nl_object *c)
79 {
80  struct rtnl_route *r = (struct rtnl_route *) c;
81  struct rtnl_nexthop *nh, *tmp;
82 
83  if (r == NULL)
84  return;
85 
86  nl_addr_put(r->rt_dst);
87  nl_addr_put(r->rt_src);
88  nl_addr_put(r->rt_pref_src);
89 
90  nl_list_for_each_entry_safe(nh, tmp, &r->rt_nexthops, rtnh_list) {
91  rtnl_route_remove_nexthop(r, nh);
92  rtnl_route_nh_free(nh);
93  }
94 }
95 
96 static int route_clone(struct nl_object *_dst, struct nl_object *_src)
97 {
98  struct rtnl_route *dst = (struct rtnl_route *) _dst;
99  struct rtnl_route *src = (struct rtnl_route *) _src;
100  struct rtnl_nexthop *nh, *new;
101 
102  if (src->rt_dst)
103  if (!(dst->rt_dst = nl_addr_clone(src->rt_dst)))
104  return -NLE_NOMEM;
105 
106  if (src->rt_src)
107  if (!(dst->rt_src = nl_addr_clone(src->rt_src)))
108  return -NLE_NOMEM;
109 
110  if (src->rt_pref_src)
111  if (!(dst->rt_pref_src = nl_addr_clone(src->rt_pref_src)))
112  return -NLE_NOMEM;
113 
114  /* Will be inc'ed again while adding the nexthops of the source */
115  dst->rt_nr_nh = 0;
116 
117  nl_init_list_head(&dst->rt_nexthops);
118  nl_list_for_each_entry(nh, &src->rt_nexthops, rtnh_list) {
119  new = rtnl_route_nh_clone(nh);
120  if (!new)
121  return -NLE_NOMEM;
122 
123  rtnl_route_add_nexthop(dst, new);
124  }
125 
126  return 0;
127 }
128 
129 static void route_dump_line(struct nl_object *a, struct nl_dump_params *p)
130 {
131  struct rtnl_route *r = (struct rtnl_route *) a;
132  int cache = 0, flags;
133  char buf[64];
134 
135  if (r->rt_flags & RTM_F_CLONED)
136  cache = 1;
137 
138  nl_dump_line(p, "%s ", nl_af2str(r->rt_family, buf, sizeof(buf)));
139 
140  if (cache)
141  nl_dump(p, "cache ");
142 
143  if (!(r->ce_mask & ROUTE_ATTR_DST) ||
144  nl_addr_get_len(r->rt_dst) == 0)
145  nl_dump(p, "default ");
146  else
147  nl_dump(p, "%s ", nl_addr2str(r->rt_dst, buf, sizeof(buf)));
148 
149  if (r->ce_mask & ROUTE_ATTR_TABLE && !cache)
150  nl_dump(p, "table %s ",
151  rtnl_route_table2str(r->rt_table, buf, sizeof(buf)));
152 
153  if (r->ce_mask & ROUTE_ATTR_TYPE)
154  nl_dump(p, "type %s ",
155  nl_rtntype2str(r->rt_type, buf, sizeof(buf)));
156 
157  if (r->ce_mask & ROUTE_ATTR_TOS && r->rt_tos != 0)
158  nl_dump(p, "tos %#x ", r->rt_tos);
159 
160  if (r->ce_mask & ROUTE_ATTR_MULTIPATH) {
161  struct rtnl_nexthop *nh;
162 
163  nl_list_for_each_entry(nh, &r->rt_nexthops, rtnh_list) {
164  p->dp_ivar = NH_DUMP_FROM_ONELINE;
165  rtnl_route_nh_dump(nh, p);
166  }
167  }
168 
169  flags = r->rt_flags & ~(RTM_F_CLONED);
170  if (r->ce_mask & ROUTE_ATTR_FLAGS && flags) {
171 
172  nl_dump(p, "<");
173 
174 #define PRINT_FLAG(f) if (flags & RTNH_F_##f) { \
175  flags &= ~RTNH_F_##f; nl_dump(p, #f "%s", flags ? "," : ""); }
176  PRINT_FLAG(DEAD);
177  PRINT_FLAG(ONLINK);
178  PRINT_FLAG(PERVASIVE);
179 #undef PRINT_FLAG
180 
181 #define PRINT_FLAG(f) if (flags & RTM_F_##f) { \
182  flags &= ~RTM_F_##f; nl_dump(p, #f "%s", flags ? "," : ""); }
183  PRINT_FLAG(NOTIFY);
184  PRINT_FLAG(EQUALIZE);
185  PRINT_FLAG(PREFIX);
186 #undef PRINT_FLAG
187 
188 #define PRINT_FLAG(f) if (flags & RTCF_##f) { \
189  flags &= ~RTCF_##f; nl_dump(p, #f "%s", flags ? "," : ""); }
190  PRINT_FLAG(NOTIFY);
191  PRINT_FLAG(REDIRECTED);
192  PRINT_FLAG(DOREDIRECT);
193  PRINT_FLAG(DIRECTSRC);
194  PRINT_FLAG(DNAT);
195  PRINT_FLAG(BROADCAST);
196  PRINT_FLAG(MULTICAST);
197  PRINT_FLAG(LOCAL);
198 #undef PRINT_FLAG
199 
200  nl_dump(p, ">");
201  }
202 
203  nl_dump(p, "\n");
204 }
205 
206 static void route_dump_details(struct nl_object *a, struct nl_dump_params *p)
207 {
208  struct rtnl_route *r = (struct rtnl_route *) a;
209  struct nl_cache *link_cache;
210  char buf[128];
211  int i;
212 
213  link_cache = nl_cache_mngt_require_safe("route/link");
214 
215  route_dump_line(a, p);
216  nl_dump_line(p, " ");
217 
218  if (r->ce_mask & ROUTE_ATTR_PREF_SRC)
219  nl_dump(p, "preferred-src %s ",
220  nl_addr2str(r->rt_pref_src, buf, sizeof(buf)));
221 
222  if (r->ce_mask & ROUTE_ATTR_SCOPE && r->rt_scope != RT_SCOPE_NOWHERE)
223  nl_dump(p, "scope %s ",
224  rtnl_scope2str(r->rt_scope, buf, sizeof(buf)));
225 
226  if (r->ce_mask & ROUTE_ATTR_PRIO)
227  nl_dump(p, "priority %#x ", r->rt_prio);
228 
229  if (r->ce_mask & ROUTE_ATTR_PROTOCOL)
230  nl_dump(p, "protocol %s ",
231  rtnl_route_proto2str(r->rt_protocol, buf, sizeof(buf)));
232 
233  if (r->ce_mask & ROUTE_ATTR_IIF) {
234  if (link_cache) {
235  nl_dump(p, "iif %s ",
236  rtnl_link_i2name(link_cache, r->rt_iif,
237  buf, sizeof(buf)));
238  } else
239  nl_dump(p, "iif %d ", r->rt_iif);
240  }
241 
242  if (r->ce_mask & ROUTE_ATTR_SRC)
243  nl_dump(p, "src %s ", nl_addr2str(r->rt_src, buf, sizeof(buf)));
244 
245  nl_dump(p, "\n");
246 
247  if (r->ce_mask & ROUTE_ATTR_MULTIPATH) {
248  struct rtnl_nexthop *nh;
249 
250  nl_list_for_each_entry(nh, &r->rt_nexthops, rtnh_list) {
251  nl_dump_line(p, " ");
252  p->dp_ivar = NH_DUMP_FROM_DETAILS;
253  rtnl_route_nh_dump(nh, p);
254  nl_dump(p, "\n");
255  }
256  }
257 
258  if ((r->ce_mask & ROUTE_ATTR_CACHEINFO) && r->rt_cacheinfo.rtci_error) {
259  nl_dump_line(p, " cacheinfo error %d (%s)\n",
260  r->rt_cacheinfo.rtci_error,
261  strerror(-r->rt_cacheinfo.rtci_error));
262  }
263 
264  if (r->ce_mask & ROUTE_ATTR_METRICS) {
265  nl_dump_line(p, " metrics [");
266  for (i = 0; i < RTAX_MAX; i++)
267  if (r->rt_metrics_mask & (1 << i))
268  nl_dump(p, "%s %u ",
269  rtnl_route_metric2str(i+1,
270  buf, sizeof(buf)),
271  r->rt_metrics[i]);
272  nl_dump(p, "]\n");
273  }
274 
275  if (link_cache)
276  nl_cache_put(link_cache);
277 }
278 
279 static void route_dump_stats(struct nl_object *obj, struct nl_dump_params *p)
280 {
281  struct rtnl_route *route = (struct rtnl_route *) obj;
282 
283  route_dump_details(obj, p);
284 
285  if (route->ce_mask & ROUTE_ATTR_CACHEINFO) {
286  struct rtnl_rtcacheinfo *ci = &route->rt_cacheinfo;
287 
288  nl_dump_line(p, " used %u refcnt %u last-use %us "
289  "expires %us\n",
290  ci->rtci_used, ci->rtci_clntref,
291  ci->rtci_last_use / nl_get_user_hz(),
292  ci->rtci_expires / nl_get_user_hz());
293  }
294 }
295 
296 static void route_keygen(struct nl_object *obj, uint32_t *hashkey,
297  uint32_t table_sz)
298 {
299  struct rtnl_route *route = (struct rtnl_route *) obj;
300  unsigned int rkey_sz;
301  struct nl_addr *addr = NULL;
302  struct route_hash_key {
303  uint8_t rt_family;
304  uint8_t rt_tos;
305  uint32_t rt_table;
306  char rt_addr[0];
307  } __attribute__((packed)) *rkey;
308  char buf[INET6_ADDRSTRLEN+5];
309 
310  if (route->rt_dst)
311  addr = route->rt_dst;
312 
313  rkey_sz = sizeof(*rkey);
314  if (addr)
315  rkey_sz += nl_addr_get_len(addr);
316  rkey = calloc(1, rkey_sz);
317  if (!rkey) {
318  NL_DBG(2, "Warning: calloc failed for %d bytes...\n", rkey_sz);
319  *hashkey = 0;
320  return;
321  }
322  rkey->rt_family = route->rt_family;
323  rkey->rt_tos = route->rt_tos;
324  rkey->rt_table = route->rt_table;
325  if (addr)
326  memcpy(rkey->rt_addr, nl_addr_get_binary_addr(addr),
327  nl_addr_get_len(addr));
328 
329  *hashkey = nl_hash(rkey, rkey_sz, 0) % table_sz;
330 
331  NL_DBG(5, "route %p key (fam %d tos %d table %d addr %s) keysz %d "
332  "hash 0x%x\n", route, rkey->rt_family, rkey->rt_tos,
333  rkey->rt_table, nl_addr2str(addr, buf, sizeof(buf)),
334  rkey_sz, *hashkey);
335 
336  free(rkey);
337 
338  return;
339 }
340 
341 static int route_compare(struct nl_object *_a, struct nl_object *_b,
342  uint32_t attrs, int flags)
343 {
344  struct rtnl_route *a = (struct rtnl_route *) _a;
345  struct rtnl_route *b = (struct rtnl_route *) _b;
346  struct rtnl_nexthop *nh_a, *nh_b;
347  int i, diff = 0, found;
348 
349 #define ROUTE_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, ROUTE_ATTR_##ATTR, a, b, EXPR)
350 
351  diff |= ROUTE_DIFF(FAMILY, a->rt_family != b->rt_family);
352  diff |= ROUTE_DIFF(TOS, a->rt_tos != b->rt_tos);
353  diff |= ROUTE_DIFF(TABLE, a->rt_table != b->rt_table);
354  diff |= ROUTE_DIFF(PROTOCOL, a->rt_protocol != b->rt_protocol);
355  diff |= ROUTE_DIFF(SCOPE, a->rt_scope != b->rt_scope);
356  diff |= ROUTE_DIFF(TYPE, a->rt_type != b->rt_type);
357  diff |= ROUTE_DIFF(PRIO, a->rt_prio != b->rt_prio);
358  diff |= ROUTE_DIFF(DST, nl_addr_cmp(a->rt_dst, b->rt_dst));
359  diff |= ROUTE_DIFF(SRC, nl_addr_cmp(a->rt_src, b->rt_src));
360  diff |= ROUTE_DIFF(IIF, a->rt_iif != b->rt_iif);
361  diff |= ROUTE_DIFF(PREF_SRC, nl_addr_cmp(a->rt_pref_src,
362  b->rt_pref_src));
363 
364  if (flags & LOOSE_COMPARISON) {
365  nl_list_for_each_entry(nh_b, &b->rt_nexthops, rtnh_list) {
366  found = 0;
367  nl_list_for_each_entry(nh_a, &a->rt_nexthops,
368  rtnh_list) {
369  if (!rtnl_route_nh_compare(nh_a, nh_b,
370  nh_b->ce_mask, 1)) {
371  found = 1;
372  break;
373  }
374  }
375 
376  if (!found)
377  goto nh_mismatch;
378  }
379 
380  for (i = 0; i < RTAX_MAX - 1; i++) {
381  if (a->rt_metrics_mask & (1 << i) &&
382  (!(b->rt_metrics_mask & (1 << i)) ||
383  a->rt_metrics[i] != b->rt_metrics[i]))
384  diff |= ROUTE_DIFF(METRICS, 1);
385  }
386 
387  diff |= ROUTE_DIFF(FLAGS,
388  (a->rt_flags ^ b->rt_flags) & b->rt_flag_mask);
389  } else {
390  if (a->rt_nr_nh != b->rt_nr_nh)
391  goto nh_mismatch;
392 
393  /* search for a dup in each nh of a */
394  nl_list_for_each_entry(nh_a, &a->rt_nexthops, rtnh_list) {
395  found = 0;
396  nl_list_for_each_entry(nh_b, &b->rt_nexthops,
397  rtnh_list) {
398  if (!rtnl_route_nh_compare(nh_a, nh_b, ~0, 0)) {
399  found = 1;
400  break;
401  }
402  }
403  if (!found)
404  goto nh_mismatch;
405  }
406 
407  /* search for a dup in each nh of b, covers case where a has
408  * dupes itself */
409  nl_list_for_each_entry(nh_b, &b->rt_nexthops, rtnh_list) {
410  found = 0;
411  nl_list_for_each_entry(nh_a, &a->rt_nexthops,
412  rtnh_list) {
413  if (!rtnl_route_nh_compare(nh_a, nh_b, ~0, 0)) {
414  found = 1;
415  break;
416  }
417  }
418  if (!found)
419  goto nh_mismatch;
420  }
421 
422  for (i = 0; i < RTAX_MAX - 1; i++) {
423  if ((a->rt_metrics_mask & (1 << i)) ^
424  (b->rt_metrics_mask & (1 << i)))
425  diff |= ROUTE_DIFF(METRICS, 1);
426  else
427  diff |= ROUTE_DIFF(METRICS,
428  a->rt_metrics[i] != b->rt_metrics[i]);
429  }
430 
431  diff |= ROUTE_DIFF(FLAGS, a->rt_flags != b->rt_flags);
432  }
433 
434 out:
435  return diff;
436 
437 nh_mismatch:
438  diff |= ROUTE_DIFF(MULTIPATH, 1);
439  goto out;
440 
441 #undef ROUTE_DIFF
442 }
443 
444 static int route_update(struct nl_object *old_obj, struct nl_object *new_obj)
445 {
446  struct rtnl_route *new_route = (struct rtnl_route *) new_obj;
447  struct rtnl_route *old_route = (struct rtnl_route *) old_obj;
448  struct rtnl_nexthop *new_nh;
449  char buf[INET6_ADDRSTRLEN+5];
450  int action = new_obj->ce_msgtype;
451 
452  /*
453  * ipv6 ECMP route notifications from the kernel come as
454  * separate notifications, one for every nexthop. This update
455  * function collapses such route msgs into a single
456  * route with multiple nexthops. The resulting object looks
457  * similar to a ipv4 ECMP route
458  */
459  if (new_route->rt_family != AF_INET6 ||
460  new_route->rt_table == RT_TABLE_LOCAL)
461  return -NLE_OPNOTSUPP;
462 
463  /*
464  * For routes that are already multipath,
465  * or dont have a nexthop dont do anything
466  */
467  if (rtnl_route_get_nnexthops(new_route) != 1)
468  return -NLE_OPNOTSUPP;
469 
470  /*
471  * Get the only nexthop entry from the new route. For
472  * IPv6 we always get a route with a 0th NH
473  * filled or nothing at all
474  */
475  new_nh = rtnl_route_nexthop_n(new_route, 0);
476  if (!new_nh || !rtnl_route_nh_get_gateway(new_nh))
477  return -NLE_OPNOTSUPP;
478 
479  switch(action) {
480  case RTM_NEWROUTE : {
481  struct rtnl_nexthop *cloned_nh;
482 
483  /*
484  * Add the nexthop to old route
485  */
486  cloned_nh = rtnl_route_nh_clone(new_nh);
487  if (!cloned_nh)
488  return -NLE_NOMEM;
489  rtnl_route_add_nexthop(old_route, cloned_nh);
490 
491  NL_DBG(2, "Route obj %p updated. Added "
492  "nexthop %p via %s\n", old_route, cloned_nh,
493  nl_addr2str(cloned_nh->rtnh_gateway, buf,
494  sizeof(buf)));
495  }
496  break;
497  case RTM_DELROUTE : {
498  struct rtnl_nexthop *old_nh;
499 
500  /*
501  * Only take care of nexthop deletes and not
502  * route deletes. So, if there is only one nexthop
503  * quite likely we did not update it. So dont do
504  * anything and return
505  */
506  if (rtnl_route_get_nnexthops(old_route) <= 1)
507  return -NLE_OPNOTSUPP;
508 
509  /*
510  * Find the next hop in old route and delete it
511  */
512  nl_list_for_each_entry(old_nh, &old_route->rt_nexthops,
513  rtnh_list) {
514  if (!rtnl_route_nh_compare(old_nh, new_nh, ~0, 0)) {
515 
516  rtnl_route_remove_nexthop(old_route, old_nh);
517 
518  NL_DBG(2, "Route obj %p updated. Removed "
519  "nexthop %p via %s\n", old_route,
520  old_nh,
521  nl_addr2str(old_nh->rtnh_gateway, buf,
522  sizeof(buf)));
523 
524  rtnl_route_nh_free(old_nh);
525  break;
526  }
527  }
528  }
529  break;
530  default:
531  NL_DBG(2, "Unknown action associated "
532  "to object %p during route update\n", new_obj);
533  return -NLE_OPNOTSUPP;
534  }
535 
536  return NLE_SUCCESS;
537 }
538 
539 static const struct trans_tbl route_attrs[] = {
540  __ADD(ROUTE_ATTR_FAMILY, family)
541  __ADD(ROUTE_ATTR_TOS, tos)
542  __ADD(ROUTE_ATTR_TABLE, table)
543  __ADD(ROUTE_ATTR_PROTOCOL, protocol)
544  __ADD(ROUTE_ATTR_SCOPE, scope)
545  __ADD(ROUTE_ATTR_TYPE, type)
546  __ADD(ROUTE_ATTR_FLAGS, flags)
547  __ADD(ROUTE_ATTR_DST, dst)
548  __ADD(ROUTE_ATTR_SRC, src)
549  __ADD(ROUTE_ATTR_IIF, iif)
550  __ADD(ROUTE_ATTR_OIF, oif)
551  __ADD(ROUTE_ATTR_GATEWAY, gateway)
552  __ADD(ROUTE_ATTR_PRIO, prio)
553  __ADD(ROUTE_ATTR_PREF_SRC, pref_src)
554  __ADD(ROUTE_ATTR_METRICS, metrics)
555  __ADD(ROUTE_ATTR_MULTIPATH, multipath)
556  __ADD(ROUTE_ATTR_REALMS, realms)
557  __ADD(ROUTE_ATTR_CACHEINFO, cacheinfo)
558 };
559 
560 static char *route_attrs2str(int attrs, char *buf, size_t len)
561 {
562  return __flags2str(attrs, buf, len, route_attrs,
563  ARRAY_SIZE(route_attrs));
564 }
565 
566 /**
567  * @name Allocation/Freeing
568  * @{
569  */
570 
571 struct rtnl_route *rtnl_route_alloc(void)
572 {
573  return (struct rtnl_route *) nl_object_alloc(&route_obj_ops);
574 }
575 
576 void rtnl_route_get(struct rtnl_route *route)
577 {
578  nl_object_get((struct nl_object *) route);
579 }
580 
581 void rtnl_route_put(struct rtnl_route *route)
582 {
583  nl_object_put((struct nl_object *) route);
584 }
585 
586 /** @} */
587 
588 /**
589  * @name Attributes
590  * @{
591  */
592 
593 void rtnl_route_set_table(struct rtnl_route *route, uint32_t table)
594 {
595  route->rt_table = table;
596  route->ce_mask |= ROUTE_ATTR_TABLE;
597 }
598 
599 uint32_t rtnl_route_get_table(struct rtnl_route *route)
600 {
601  return route->rt_table;
602 }
603 
604 void rtnl_route_set_scope(struct rtnl_route *route, uint8_t scope)
605 {
606  route->rt_scope = scope;
607  route->ce_mask |= ROUTE_ATTR_SCOPE;
608 }
609 
610 uint8_t rtnl_route_get_scope(struct rtnl_route *route)
611 {
612  return route->rt_scope;
613 }
614 
615 void rtnl_route_set_tos(struct rtnl_route *route, uint8_t tos)
616 {
617  route->rt_tos = tos;
618  route->ce_mask |= ROUTE_ATTR_TOS;
619 }
620 
621 uint8_t rtnl_route_get_tos(struct rtnl_route *route)
622 {
623  return route->rt_tos;
624 }
625 
626 void rtnl_route_set_protocol(struct rtnl_route *route, uint8_t protocol)
627 {
628  route->rt_protocol = protocol;
629  route->ce_mask |= ROUTE_ATTR_PROTOCOL;
630 }
631 
632 uint8_t rtnl_route_get_protocol(struct rtnl_route *route)
633 {
634  return route->rt_protocol;
635 }
636 
637 void rtnl_route_set_priority(struct rtnl_route *route, uint32_t prio)
638 {
639  route->rt_prio = prio;
640  route->ce_mask |= ROUTE_ATTR_PRIO;
641 }
642 
643 uint32_t rtnl_route_get_priority(struct rtnl_route *route)
644 {
645  return route->rt_prio;
646 }
647 
648 int rtnl_route_set_family(struct rtnl_route *route, uint8_t family)
649 {
650  if (family != AF_INET && family != AF_INET6 && family != AF_DECnet)
651  return -NLE_AF_NOSUPPORT;
652 
653  route->rt_family = family;
654  route->ce_mask |= ROUTE_ATTR_FAMILY;
655 
656  return 0;
657 }
658 
659 uint8_t rtnl_route_get_family(struct rtnl_route *route)
660 {
661  return route->rt_family;
662 }
663 
664 int rtnl_route_set_dst(struct rtnl_route *route, struct nl_addr *addr)
665 {
666  if (route->ce_mask & ROUTE_ATTR_FAMILY) {
667  if (addr->a_family != route->rt_family)
668  return -NLE_AF_MISMATCH;
669  } else
670  route->rt_family = addr->a_family;
671 
672  if (route->rt_dst)
673  nl_addr_put(route->rt_dst);
674 
675  nl_addr_get(addr);
676  route->rt_dst = addr;
677 
678  route->ce_mask |= (ROUTE_ATTR_DST | ROUTE_ATTR_FAMILY);
679 
680  return 0;
681 }
682 
683 struct nl_addr *rtnl_route_get_dst(struct rtnl_route *route)
684 {
685  return route->rt_dst;
686 }
687 
688 int rtnl_route_set_src(struct rtnl_route *route, struct nl_addr *addr)
689 {
690  if (addr->a_family == AF_INET)
691  return -NLE_SRCRT_NOSUPPORT;
692 
693  if (route->ce_mask & ROUTE_ATTR_FAMILY) {
694  if (addr->a_family != route->rt_family)
695  return -NLE_AF_MISMATCH;
696  } else
697  route->rt_family = addr->a_family;
698 
699  if (route->rt_src)
700  nl_addr_put(route->rt_src);
701 
702  nl_addr_get(addr);
703  route->rt_src = addr;
704  route->ce_mask |= (ROUTE_ATTR_SRC | ROUTE_ATTR_FAMILY);
705 
706  return 0;
707 }
708 
709 struct nl_addr *rtnl_route_get_src(struct rtnl_route *route)
710 {
711  return route->rt_src;
712 }
713 
714 int rtnl_route_set_type(struct rtnl_route *route, uint8_t type)
715 {
716  if (type > RTN_MAX)
717  return -NLE_RANGE;
718 
719  route->rt_type = type;
720  route->ce_mask |= ROUTE_ATTR_TYPE;
721 
722  return 0;
723 }
724 
725 uint8_t rtnl_route_get_type(struct rtnl_route *route)
726 {
727  return route->rt_type;
728 }
729 
730 void rtnl_route_set_flags(struct rtnl_route *route, uint32_t flags)
731 {
732  route->rt_flag_mask |= flags;
733  route->rt_flags |= flags;
734  route->ce_mask |= ROUTE_ATTR_FLAGS;
735 }
736 
737 void rtnl_route_unset_flags(struct rtnl_route *route, uint32_t flags)
738 {
739  route->rt_flag_mask |= flags;
740  route->rt_flags &= ~flags;
741  route->ce_mask |= ROUTE_ATTR_FLAGS;
742 }
743 
744 uint32_t rtnl_route_get_flags(struct rtnl_route *route)
745 {
746  return route->rt_flags;
747 }
748 
749 int rtnl_route_set_metric(struct rtnl_route *route, int metric, uint32_t value)
750 {
751  if (metric > RTAX_MAX || metric < 1)
752  return -NLE_RANGE;
753 
754  route->rt_metrics[metric - 1] = value;
755 
756  if (!(route->rt_metrics_mask & (1 << (metric - 1)))) {
757  route->rt_nmetrics++;
758  route->rt_metrics_mask |= (1 << (metric - 1));
759  }
760 
761  route->ce_mask |= ROUTE_ATTR_METRICS;
762 
763  return 0;
764 }
765 
766 int rtnl_route_unset_metric(struct rtnl_route *route, int metric)
767 {
768  if (metric > RTAX_MAX || metric < 1)
769  return -NLE_RANGE;
770 
771  if (route->rt_metrics_mask & (1 << (metric - 1))) {
772  route->rt_nmetrics--;
773  route->rt_metrics_mask &= ~(1 << (metric - 1));
774  }
775 
776  return 0;
777 }
778 
779 int rtnl_route_get_metric(struct rtnl_route *route, int metric, uint32_t *value)
780 {
781  if (metric > RTAX_MAX || metric < 1)
782  return -NLE_RANGE;
783 
784  if (!(route->rt_metrics_mask & (1 << (metric - 1))))
785  return -NLE_OBJ_NOTFOUND;
786 
787  if (value)
788  *value = route->rt_metrics[metric - 1];
789 
790  return 0;
791 }
792 
793 int rtnl_route_set_pref_src(struct rtnl_route *route, struct nl_addr *addr)
794 {
795  if (route->ce_mask & ROUTE_ATTR_FAMILY) {
796  if (addr->a_family != route->rt_family)
797  return -NLE_AF_MISMATCH;
798  } else
799  route->rt_family = addr->a_family;
800 
801  if (route->rt_pref_src)
802  nl_addr_put(route->rt_pref_src);
803 
804  nl_addr_get(addr);
805  route->rt_pref_src = addr;
806  route->ce_mask |= (ROUTE_ATTR_PREF_SRC | ROUTE_ATTR_FAMILY);
807 
808  return 0;
809 }
810 
811 struct nl_addr *rtnl_route_get_pref_src(struct rtnl_route *route)
812 {
813  return route->rt_pref_src;
814 }
815 
816 void rtnl_route_set_iif(struct rtnl_route *route, int ifindex)
817 {
818  route->rt_iif = ifindex;
819  route->ce_mask |= ROUTE_ATTR_IIF;
820 }
821 
822 int rtnl_route_get_iif(struct rtnl_route *route)
823 {
824  return route->rt_iif;
825 }
826 
827 void rtnl_route_add_nexthop(struct rtnl_route *route, struct rtnl_nexthop *nh)
828 {
829  nl_list_add_tail(&nh->rtnh_list, &route->rt_nexthops);
830  route->rt_nr_nh++;
831  route->ce_mask |= ROUTE_ATTR_MULTIPATH;
832 }
833 
834 void rtnl_route_remove_nexthop(struct rtnl_route *route, struct rtnl_nexthop *nh)
835 {
836  if (route->ce_mask & ROUTE_ATTR_MULTIPATH) {
837  route->rt_nr_nh--;
838  nl_list_del(&nh->rtnh_list);
839  }
840 }
841 
842 struct nl_list_head *rtnl_route_get_nexthops(struct rtnl_route *route)
843 {
844  if (route->ce_mask & ROUTE_ATTR_MULTIPATH)
845  return &route->rt_nexthops;
846 
847  return NULL;
848 }
849 
850 int rtnl_route_get_nnexthops(struct rtnl_route *route)
851 {
852  if (route->ce_mask & ROUTE_ATTR_MULTIPATH)
853  return route->rt_nr_nh;
854 
855  return 0;
856 }
857 
858 void rtnl_route_foreach_nexthop(struct rtnl_route *r,
859  void (*cb)(struct rtnl_nexthop *, void *),
860  void *arg)
861 {
862  struct rtnl_nexthop *nh;
863 
864  if (r->ce_mask & ROUTE_ATTR_MULTIPATH) {
865  nl_list_for_each_entry(nh, &r->rt_nexthops, rtnh_list) {
866  cb(nh, arg);
867  }
868  }
869 }
870 
871 struct rtnl_nexthop *rtnl_route_nexthop_n(struct rtnl_route *r, int n)
872 {
873  struct rtnl_nexthop *nh;
874  uint32_t i;
875 
876  if (r->ce_mask & ROUTE_ATTR_MULTIPATH && r->rt_nr_nh > n) {
877  i = 0;
878  nl_list_for_each_entry(nh, &r->rt_nexthops, rtnh_list) {
879  if (i == n) return nh;
880  i++;
881  }
882  }
883  return NULL;
884 }
885 
886 /** @} */
887 
888 /**
889  * @name Utilities
890  * @{
891  */
892 
893 /**
894  * Guess scope of a route object.
895  * @arg route Route object.
896  *
897  * Guesses the scope of a route object, based on the following rules:
898  * @code
899  * 1) Local route -> local scope
900  * 2) At least one nexthop not directly connected -> universe scope
901  * 3) All others -> link scope
902  * @endcode
903  *
904  * @return Scope value.
905  */
906 int rtnl_route_guess_scope(struct rtnl_route *route)
907 {
908  if (route->rt_type == RTN_LOCAL)
909  return RT_SCOPE_HOST;
910 
911  if (!nl_list_empty(&route->rt_nexthops)) {
912  struct rtnl_nexthop *nh;
913 
914  /*
915  * Use scope uiniverse if there is at least one nexthop which
916  * is not directly connected
917  */
918  nl_list_for_each_entry(nh, &route->rt_nexthops, rtnh_list) {
919  if (nh->rtnh_gateway)
920  return RT_SCOPE_UNIVERSE;
921  }
922  }
923 
924  return RT_SCOPE_LINK;
925 }
926 
927 /** @} */
928 
929 static struct nla_policy route_policy[RTA_MAX+1] = {
930  [RTA_IIF] = { .type = NLA_U32 },
931  [RTA_OIF] = { .type = NLA_U32 },
932  [RTA_PRIORITY] = { .type = NLA_U32 },
933  [RTA_FLOW] = { .type = NLA_U32 },
934  [RTA_CACHEINFO] = { .minlen = sizeof(struct rta_cacheinfo) },
935  [RTA_METRICS] = { .type = NLA_NESTED },
936  [RTA_MULTIPATH] = { .type = NLA_NESTED },
937 };
938 
939 static int parse_multipath(struct rtnl_route *route, struct nlattr *attr)
940 {
941  struct rtnl_nexthop *nh = NULL;
942  struct rtnexthop *rtnh = nla_data(attr);
943  size_t tlen = nla_len(attr);
944  int err;
945 
946  while (tlen >= sizeof(*rtnh) && tlen >= rtnh->rtnh_len) {
947  nh = rtnl_route_nh_alloc();
948  if (!nh)
949  return -NLE_NOMEM;
950 
951  rtnl_route_nh_set_weight(nh, rtnh->rtnh_hops);
952  rtnl_route_nh_set_ifindex(nh, rtnh->rtnh_ifindex);
953  rtnl_route_nh_set_flags(nh, rtnh->rtnh_flags);
954 
955  if (rtnh->rtnh_len > sizeof(*rtnh)) {
956  struct nlattr *ntb[RTA_MAX + 1];
957 
958  err = nla_parse(ntb, RTA_MAX, (struct nlattr *)
959  RTNH_DATA(rtnh),
960  rtnh->rtnh_len - sizeof(*rtnh),
961  route_policy);
962  if (err < 0)
963  goto errout;
964 
965  if (ntb[RTA_GATEWAY]) {
966  struct nl_addr *addr;
967 
968  addr = nl_addr_alloc_attr(ntb[RTA_GATEWAY],
969  route->rt_family);
970  if (!addr) {
971  err = -NLE_NOMEM;
972  goto errout;
973  }
974 
975  rtnl_route_nh_set_gateway(nh, addr);
976  nl_addr_put(addr);
977  }
978 
979  if (ntb[RTA_FLOW]) {
980  uint32_t realms;
981 
982  realms = nla_get_u32(ntb[RTA_FLOW]);
983  rtnl_route_nh_set_realms(nh, realms);
984  }
985  }
986 
987  rtnl_route_add_nexthop(route, nh);
988  tlen -= RTNH_ALIGN(rtnh->rtnh_len);
989  rtnh = RTNH_NEXT(rtnh);
990  }
991 
992  err = 0;
993 errout:
994  if (err && nh)
995  rtnl_route_nh_free(nh);
996 
997  return err;
998 }
999 
1000 int rtnl_route_parse(struct nlmsghdr *nlh, struct rtnl_route **result)
1001 {
1002  struct rtmsg *rtm;
1003  struct rtnl_route *route;
1004  struct nlattr *tb[RTA_MAX + 1];
1005  struct nl_addr *src = NULL, *dst = NULL, *addr;
1006  struct rtnl_nexthop *old_nh = NULL;
1007  int err, family;
1008 
1009  route = rtnl_route_alloc();
1010  if (!route) {
1011  err = -NLE_NOMEM;
1012  goto errout;
1013  }
1014 
1015  route->ce_msgtype = nlh->nlmsg_type;
1016 
1017  err = nlmsg_parse(nlh, sizeof(struct rtmsg), tb, RTA_MAX, route_policy);
1018  if (err < 0)
1019  goto errout;
1020 
1021  rtm = nlmsg_data(nlh);
1022  route->rt_family = family = rtm->rtm_family;
1023  route->rt_tos = rtm->rtm_tos;
1024  route->rt_table = rtm->rtm_table;
1025  route->rt_type = rtm->rtm_type;
1026  route->rt_scope = rtm->rtm_scope;
1027  route->rt_protocol = rtm->rtm_protocol;
1028  route->rt_flags = rtm->rtm_flags;
1029 
1030  route->ce_mask |= ROUTE_ATTR_FAMILY | ROUTE_ATTR_TOS |
1031  ROUTE_ATTR_TABLE | ROUTE_ATTR_TYPE |
1032  ROUTE_ATTR_SCOPE | ROUTE_ATTR_PROTOCOL |
1033  ROUTE_ATTR_FLAGS;
1034 
1035  if (tb[RTA_DST]) {
1036  if (!(dst = nl_addr_alloc_attr(tb[RTA_DST], family)))
1037  goto errout_nomem;
1038  } else {
1039  if (!(dst = nl_addr_alloc(0)))
1040  goto errout_nomem;
1041  nl_addr_set_family(dst, rtm->rtm_family);
1042  }
1043 
1044  nl_addr_set_prefixlen(dst, rtm->rtm_dst_len);
1045  err = rtnl_route_set_dst(route, dst);
1046  if (err < 0)
1047  goto errout;
1048 
1049  nl_addr_put(dst);
1050 
1051  if (tb[RTA_SRC]) {
1052  if (!(src = nl_addr_alloc_attr(tb[RTA_SRC], family)))
1053  goto errout_nomem;
1054  } else if (rtm->rtm_src_len)
1055  if (!(src = nl_addr_alloc(0)))
1056  goto errout_nomem;
1057 
1058  if (src) {
1059  nl_addr_set_prefixlen(src, rtm->rtm_src_len);
1060  rtnl_route_set_src(route, src);
1061  nl_addr_put(src);
1062  }
1063 
1064  if (tb[RTA_TABLE])
1065  rtnl_route_set_table(route, nla_get_u32(tb[RTA_TABLE]));
1066 
1067  if (tb[RTA_IIF])
1068  rtnl_route_set_iif(route, nla_get_u32(tb[RTA_IIF]));
1069 
1070  if (tb[RTA_PRIORITY])
1071  rtnl_route_set_priority(route, nla_get_u32(tb[RTA_PRIORITY]));
1072 
1073  if (tb[RTA_PREFSRC]) {
1074  if (!(addr = nl_addr_alloc_attr(tb[RTA_PREFSRC], family)))
1075  goto errout_nomem;
1076  rtnl_route_set_pref_src(route, addr);
1077  nl_addr_put(addr);
1078  }
1079 
1080  if (tb[RTA_METRICS]) {
1081  struct nlattr *mtb[RTAX_MAX + 1];
1082  int i;
1083 
1084  err = nla_parse_nested(mtb, RTAX_MAX, tb[RTA_METRICS], NULL);
1085  if (err < 0)
1086  goto errout;
1087 
1088  for (i = 1; i <= RTAX_MAX; i++) {
1089  if (mtb[i] && nla_len(mtb[i]) >= sizeof(uint32_t)) {
1090  uint32_t m = nla_get_u32(mtb[i]);
1091  if (rtnl_route_set_metric(route, i, m) < 0)
1092  goto errout;
1093  }
1094  }
1095  }
1096 
1097  if (tb[RTA_MULTIPATH])
1098  if ((err = parse_multipath(route, tb[RTA_MULTIPATH])) < 0)
1099  goto errout;
1100 
1101  if (tb[RTA_CACHEINFO]) {
1102  nla_memcpy(&route->rt_cacheinfo, tb[RTA_CACHEINFO],
1103  sizeof(route->rt_cacheinfo));
1104  route->ce_mask |= ROUTE_ATTR_CACHEINFO;
1105  }
1106 
1107  if (tb[RTA_OIF]) {
1108  if (!old_nh && !(old_nh = rtnl_route_nh_alloc()))
1109  goto errout;
1110 
1111  rtnl_route_nh_set_ifindex(old_nh, nla_get_u32(tb[RTA_OIF]));
1112  }
1113 
1114  if (tb[RTA_GATEWAY]) {
1115  if (!old_nh && !(old_nh = rtnl_route_nh_alloc()))
1116  goto errout;
1117 
1118  if (!(addr = nl_addr_alloc_attr(tb[RTA_GATEWAY], family)))
1119  goto errout_nomem;
1120 
1121  rtnl_route_nh_set_gateway(old_nh, addr);
1122  nl_addr_put(addr);
1123  }
1124 
1125  if (tb[RTA_FLOW]) {
1126  if (!old_nh && !(old_nh = rtnl_route_nh_alloc()))
1127  goto errout;
1128 
1129  rtnl_route_nh_set_realms(old_nh, nla_get_u32(tb[RTA_FLOW]));
1130  }
1131 
1132  if (old_nh) {
1133  rtnl_route_nh_set_flags(old_nh, rtm->rtm_flags & 0xff);
1134  if (route->rt_nr_nh == 0) {
1135  /* If no nexthops have been provided via RTA_MULTIPATH
1136  * we add it as regular nexthop to maintain backwards
1137  * compatibility */
1138  rtnl_route_add_nexthop(route, old_nh);
1139  } else {
1140  /* Kernel supports new style nexthop configuration,
1141  * verify that it is a duplicate and discard nexthop. */
1142  struct rtnl_nexthop *first;
1143 
1144  first = nl_list_first_entry(&route->rt_nexthops,
1145  struct rtnl_nexthop,
1146  rtnh_list);
1147  if (!first)
1148  BUG();
1149 
1150  if (rtnl_route_nh_compare(old_nh, first,
1151  old_nh->ce_mask, 0)) {
1152  err = -NLE_INVAL;
1153  goto errout;
1154  }
1155 
1156  rtnl_route_nh_free(old_nh);
1157  }
1158  }
1159 
1160  *result = route;
1161  return 0;
1162 
1163 errout:
1164  rtnl_route_put(route);
1165  return err;
1166 
1167 errout_nomem:
1168  err = -NLE_NOMEM;
1169  goto errout;
1170 }
1171 
1172 int rtnl_route_build_msg(struct nl_msg *msg, struct rtnl_route *route)
1173 {
1174  int i;
1175  struct nlattr *metrics;
1176  struct rtmsg rtmsg = {
1177  .rtm_family = route->rt_family,
1178  .rtm_tos = route->rt_tos,
1179  .rtm_table = route->rt_table,
1180  .rtm_protocol = route->rt_protocol,
1181  .rtm_scope = route->rt_scope,
1182  .rtm_type = route->rt_type,
1183  .rtm_flags = route->rt_flags,
1184  };
1185 
1186  if (route->rt_dst == NULL)
1187  return -NLE_MISSING_ATTR;
1188 
1189  rtmsg.rtm_dst_len = nl_addr_get_prefixlen(route->rt_dst);
1190  if (route->rt_src)
1191  rtmsg.rtm_src_len = nl_addr_get_prefixlen(route->rt_src);
1192 
1193  if (rtmsg.rtm_scope == RT_SCOPE_NOWHERE)
1194  rtmsg.rtm_scope = rtnl_route_guess_scope(route);
1195 
1196  if (rtnl_route_get_nnexthops(route) == 1) {
1197  struct rtnl_nexthop *nh;
1198  nh = rtnl_route_nexthop_n(route, 0);
1199  rtmsg.rtm_flags |= nh->rtnh_flags;
1200  }
1201 
1202  if (nlmsg_append(msg, &rtmsg, sizeof(rtmsg), NLMSG_ALIGNTO) < 0)
1203  goto nla_put_failure;
1204 
1205  /* Additional table attribute replacing the 8bit in the header, was
1206  * required to allow more than 256 tables. */
1207  NLA_PUT_U32(msg, RTA_TABLE, route->rt_table);
1208 
1209  if (nl_addr_get_len(route->rt_dst))
1210  NLA_PUT_ADDR(msg, RTA_DST, route->rt_dst);
1211  NLA_PUT_U32(msg, RTA_PRIORITY, route->rt_prio);
1212 
1213  if (route->ce_mask & ROUTE_ATTR_SRC)
1214  NLA_PUT_ADDR(msg, RTA_SRC, route->rt_src);
1215 
1216  if (route->ce_mask & ROUTE_ATTR_PREF_SRC)
1217  NLA_PUT_ADDR(msg, RTA_PREFSRC, route->rt_pref_src);
1218 
1219  if (route->ce_mask & ROUTE_ATTR_IIF)
1220  NLA_PUT_U32(msg, RTA_IIF, route->rt_iif);
1221 
1222  if (route->rt_nmetrics > 0) {
1223  uint32_t val;
1224 
1225  metrics = nla_nest_start(msg, RTA_METRICS);
1226  if (metrics == NULL)
1227  goto nla_put_failure;
1228 
1229  for (i = 1; i <= RTAX_MAX; i++) {
1230  if (!rtnl_route_get_metric(route, i, &val))
1231  NLA_PUT_U32(msg, i, val);
1232  }
1233 
1234  nla_nest_end(msg, metrics);
1235  }
1236 
1237  if (rtnl_route_get_nnexthops(route) == 1) {
1238  struct rtnl_nexthop *nh;
1239 
1240  nh = rtnl_route_nexthop_n(route, 0);
1241  if (nh->rtnh_gateway)
1242  NLA_PUT_ADDR(msg, RTA_GATEWAY, nh->rtnh_gateway);
1243  if (nh->rtnh_ifindex)
1244  NLA_PUT_U32(msg, RTA_OIF, nh->rtnh_ifindex);
1245  if (nh->rtnh_realms)
1246  NLA_PUT_U32(msg, RTA_FLOW, nh->rtnh_realms);
1247  } else if (rtnl_route_get_nnexthops(route) > 1) {
1248  struct nlattr *multipath;
1249  struct rtnl_nexthop *nh;
1250 
1251  if (!(multipath = nla_nest_start(msg, RTA_MULTIPATH)))
1252  goto nla_put_failure;
1253 
1254  nl_list_for_each_entry(nh, &route->rt_nexthops, rtnh_list) {
1255  struct rtnexthop *rtnh;
1256 
1257  rtnh = nlmsg_reserve(msg, sizeof(*rtnh), NLMSG_ALIGNTO);
1258  if (!rtnh)
1259  goto nla_put_failure;
1260 
1261  rtnh->rtnh_flags = nh->rtnh_flags;
1262  rtnh->rtnh_hops = nh->rtnh_weight;
1263  rtnh->rtnh_ifindex = nh->rtnh_ifindex;
1264 
1265  if (nh->rtnh_gateway)
1266  NLA_PUT_ADDR(msg, RTA_GATEWAY,
1267  nh->rtnh_gateway);
1268 
1269  if (nh->rtnh_realms)
1270  NLA_PUT_U32(msg, RTA_FLOW, nh->rtnh_realms);
1271 
1272  rtnh->rtnh_len = nlmsg_tail(msg->nm_nlh) -
1273  (void *) rtnh;
1274  }
1275 
1276  nla_nest_end(msg, multipath);
1277  }
1278 
1279  return 0;
1280 
1281 nla_put_failure:
1282  return -NLE_MSGSIZE;
1283 }
1284 
1285 /** @cond SKIP */
1286 struct nl_object_ops route_obj_ops = {
1287  .oo_name = "route/route",
1288  .oo_size = sizeof(struct rtnl_route),
1289  .oo_constructor = route_constructor,
1290  .oo_free_data = route_free_data,
1291  .oo_clone = route_clone,
1292  .oo_dump = {
1293  [NL_DUMP_LINE] = route_dump_line,
1294  [NL_DUMP_DETAILS] = route_dump_details,
1295  [NL_DUMP_STATS] = route_dump_stats,
1296  },
1297  .oo_compare = route_compare,
1298  .oo_keygen = route_keygen,
1299  .oo_update = route_update,
1300  .oo_attrs2str = route_attrs2str,
1301  .oo_id_attrs = (ROUTE_ATTR_FAMILY | ROUTE_ATTR_TOS |
1302  ROUTE_ATTR_TABLE | ROUTE_ATTR_DST),
1303 };
1304 /** @endcond */
1305 
1306 /** @} */