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