fib: allow route delete with no paths and multipath=0 to remove the
[vpp.git] / test / vpp_ip_route.py
1 """
2   IP Routes
3
4   object abstractions for representing IP routes in VPP
5 """
6
7 from vpp_object import VppObject
8 from socket import inet_pton, inet_ntop, AF_INET, AF_INET6
9 from vpp_ip import DpoProto, VppIpPrefix, INVALID_INDEX, VppIpAddressUnion, \
10     VppIpMPrefix
11 from ipaddress import ip_address, IPv4Network, IPv6Network
12
13 # from vnet/vnet/mpls/mpls_types.h
14 MPLS_IETF_MAX_LABEL = 0xfffff
15 MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1
16
17 try:
18     text_type = unicode
19 except NameError:
20     text_type = str
21
22
23 class MRouteItfFlags:
24     MFIB_ITF_FLAG_NONE = 0
25     MFIB_ITF_FLAG_NEGATE_SIGNAL = 1
26     MFIB_ITF_FLAG_ACCEPT = 2
27     MFIB_ITF_FLAG_FORWARD = 4
28     MFIB_ITF_FLAG_SIGNAL_PRESENT = 8
29     MFIB_ITF_FLAG_INTERNAL_COPY = 16
30
31
32 class MRouteEntryFlags:
33     MFIB_ENTRY_FLAG_NONE = 0
34     MFIB_ENTRY_FLAG_SIGNAL = 1
35     MFIB_ENTRY_FLAG_DROP = 2
36     MFIB_ENTRY_FLAG_CONNECTED = 4
37     MFIB_ENTRY_FLAG_INHERIT_ACCEPT = 8
38
39
40 class FibPathProto:
41     FIB_PATH_NH_PROTO_IP4 = 0
42     FIB_PATH_NH_PROTO_IP6 = 1
43     FIB_PATH_NH_PROTO_MPLS = 2
44     FIB_PATH_NH_PROTO_ETHERNET = 3
45     FIB_PATH_NH_PROTO_BIER = 4
46     FIB_PATH_NH_PROTO_NSH = 5
47
48
49 class FibPathType:
50     FIB_PATH_TYPE_NORMAL = 0
51     FIB_PATH_TYPE_LOCAL = 1
52     FIB_PATH_TYPE_DROP = 2
53     FIB_PATH_TYPE_UDP_ENCAP = 3
54     FIB_PATH_TYPE_BIER_IMP = 4
55     FIB_PATH_TYPE_ICMP_UNREACH = 5
56     FIB_PATH_TYPE_ICMP_PROHIBIT = 6
57     FIB_PATH_TYPE_SOURCE_LOOKUP = 7
58     FIB_PATH_TYPE_DVR = 8
59     FIB_PATH_TYPE_INTERFACE_RX = 9
60     FIB_PATH_TYPE_CLASSIFY = 10
61
62
63 class FibPathFlags:
64     FIB_PATH_FLAG_NONE = 0
65     FIB_PATH_FLAG_RESOLVE_VIA_ATTACHED = 1
66     FIB_PATH_FLAG_RESOLVE_VIA_HOST = 2
67
68
69 class MplsLspMode:
70     PIPE = 0
71     UNIFORM = 1
72
73
74 def ip_to_dpo_proto(addr):
75     if addr.version == 6:
76         return DpoProto.DPO_PROTO_IP6
77     else:
78         return DpoProto.DPO_PROTO_IP4
79
80
81 def address_proto(ip_addr):
82     if ip_addr.ip_addr.version is 4:
83         return FibPathProto.FIB_PATH_NH_PROTO_IP4
84     else:
85         return FibPathProto.FIB_PATH_NH_PROTO_IP6
86
87
88 def find_route(test, addr, len, table_id=0):
89     ip_addr = ip_address(text_type(addr))
90
91     if 4 is ip_addr.version:
92         routes = test.vapi.ip_route_dump(table_id, False)
93         prefix = IPv4Network("%s/%d" % (text_type(addr), len), strict=False)
94     else:
95         routes = test.vapi.ip_route_dump(table_id, True)
96         prefix = IPv6Network("%s/%d" % (text_type(addr), len), strict=False)
97
98     for e in routes:
99         if table_id == e.route.table_id \
100            and prefix == e.route.prefix:
101             return True
102     return False
103
104
105 def find_mroute(test, grp_addr, src_addr, grp_addr_len,
106                 table_id=0):
107     ip_mprefix = VppIpMPrefix(text_type(src_addr),
108                               text_type(grp_addr),
109                               grp_addr_len)
110
111     if 4 is ip_mprefix.version:
112         routes = test.vapi.ip_mroute_dump(table_id, False)
113     else:
114         routes = test.vapi.ip_mroute_dump(table_id, True)
115
116     for e in routes:
117         if table_id == e.route.table_id and ip_mprefix == e.route.prefix:
118             return True
119     return False
120
121
122 def find_mpls_route(test, table_id, label, eos_bit, paths=None):
123     dump = test.vapi.mpls_route_dump(table_id)
124     for e in dump:
125         if label == e.mr_route.mr_label \
126            and eos_bit == e.mr_route.mr_eos \
127            and table_id == e.mr_route.mr_table_id:
128             if not paths:
129                 return True
130             else:
131                 if (len(paths) != len(e.mr_route.mr_paths)):
132                     return False
133                 for i in range(len(paths)):
134                     if (paths[i] != e.mr_route.mr_paths[i]):
135                         return False
136                 return True
137     return False
138
139
140 def fib_interface_ip_prefix(test, address, length, sw_if_index):
141     ip_addr = ip_address(text_type(address))
142
143     if 4 is ip_addr.version:
144         addrs = test.vapi.ip_address_dump(sw_if_index)
145         prefix = IPv4Network("%s/%d" % (text_type(address), length),
146                              strict=False)
147     else:
148         addrs = test.vapi.ip_address_dump(sw_if_index, is_ipv6=1)
149         prefix = IPv6Network("%s/%d" % (text_type(address), length),
150                              strict=False)
151
152     # TODO: refactor this to VppIpPrefix.__eq__
153     for a in addrs:
154         if a.sw_if_index == sw_if_index and \
155            a.prefix == prefix:
156             return True
157     return False
158
159
160 class VppIpTable(VppObject):
161
162     def __init__(self,
163                  test,
164                  table_id,
165                  is_ip6=0):
166         self._test = test
167         self.table_id = table_id
168         self.is_ip6 = is_ip6
169
170     def add_vpp_config(self):
171         self._test.vapi.ip_table_add_del(is_ipv6=self.is_ip6, is_add=1,
172                                          table_id=self.table_id)
173         self._test.registry.register(self, self._test.logger)
174
175     def remove_vpp_config(self):
176         self._test.vapi.ip_table_add_del(is_ipv6=self.is_ip6, is_add=0,
177                                          table_id=self.table_id)
178
179     def query_vpp_config(self):
180         if self.table_id == 0:
181             # the default table always exists
182             return False
183         # find the default route
184         return find_route(self._test,
185                           "::" if self.is_ip6 else "0.0.0.0",
186                           0,
187                           self.table_id)
188
189     def object_id(self):
190         return ("table-%s-%d" %
191                 ("v6" if self.is_ip6 == 1 else "v4",
192                  self.table_id))
193
194
195 class VppIpInterfaceAddress(VppObject):
196
197     def __init__(self, test, intf, addr, len):
198         self._test = test
199         self.intf = intf
200         self.prefix = VppIpPrefix(addr, len)
201
202     def add_vpp_config(self):
203         self._test.vapi.sw_interface_add_del_address(
204             sw_if_index=self.intf.sw_if_index, address=self.prefix.bytes,
205             address_length=self.prefix.length, is_ipv6=self.prefix.is_ip6,
206             is_add=1)
207         self._test.registry.register(self, self._test.logger)
208
209     def remove_vpp_config(self):
210         self._test.vapi.sw_interface_add_del_address(
211             sw_if_index=self.intf.sw_if_index, address=self.prefix.bytes,
212             address_length=self.prefix.length, is_ipv6=self.prefix.is_ip6,
213             is_add=0)
214
215     def query_vpp_config(self):
216         return fib_interface_ip_prefix(self._test,
217                                        self.prefix.address,
218                                        self.prefix.length,
219                                        self.intf.sw_if_index)
220
221     def object_id(self):
222         return "interface-ip-%s-%s" % (self.intf, self.prefix)
223
224
225 class VppIpInterfaceBind(VppObject):
226
227     def __init__(self, test, intf, table):
228         self._test = test
229         self.intf = intf
230         self.table = table
231
232     def add_vpp_config(self):
233         if self.table.is_ip6:
234             self.intf.set_table_ip6(self.table.table_id)
235         else:
236             self.intf.set_table_ip4(self.table.table_id)
237         self._test.registry.register(self, self._test.logger)
238
239     def remove_vpp_config(self):
240         if 0 == self.table.table_id:
241             return
242         if self.table.is_ip6:
243             self.intf.set_table_ip6(0)
244         else:
245             self.intf.set_table_ip4(0)
246
247     def query_vpp_config(self):
248         if 0 == self.table.table_id:
249             return False
250         return self._test.vapi.sw_interface_get_table(
251             self.intf.sw_if_index,
252             self.table.is_ip6).vrf_id == self.table.table_id
253
254     def object_id(self):
255         return "interface-bind-%s-%s" % (self.intf, self.table)
256
257
258 class VppMplsLabel(object):
259     def __init__(self, value, mode=MplsLspMode.PIPE, ttl=64, exp=0):
260         self.value = value
261         self.mode = mode
262         self.ttl = ttl
263         self.exp = exp
264
265     def encode(self):
266         is_uniform = 0 if self.mode is MplsLspMode.PIPE else 1
267         return {'label': self.value,
268                 'ttl': self.ttl,
269                 'exp': self.exp,
270                 'is_uniform': is_uniform}
271
272     def __eq__(self, other):
273         if isinstance(other, self.__class__):
274             return (self.value == other.value and
275                     self.ttl == other.ttl and
276                     self.exp == other.exp and
277                     self.mode == other.mode)
278         elif hasattr(other, 'label'):
279             return (self.value == other.label and
280                     self.ttl == other.ttl and
281                     self.exp == other.exp and
282                     (self.mode == MplsLspMode.UNIFORM) == other.is_uniform)
283         else:
284             return False
285
286     def __ne__(self, other):
287         return not (self == other)
288
289
290 class VppFibPathNextHop(object):
291     def __init__(self, addr,
292                  via_label=MPLS_LABEL_INVALID,
293                  next_hop_id=INVALID_INDEX):
294         self.addr = VppIpAddressUnion(addr)
295         self.via_label = via_label
296         self.obj_id = next_hop_id
297
298     def encode(self):
299         if self.via_label is not MPLS_LABEL_INVALID:
300             return {'via_label': self.via_label}
301         if self.obj_id is not INVALID_INDEX:
302             return {'obj_id': self.obj_id}
303         else:
304             return {'address': self.addr.encode()}
305
306     def proto(self):
307         if self.via_label is MPLS_LABEL_INVALID:
308             return address_proto(self.addr)
309         else:
310             return FibPathProto.FIB_PATH_NH_PROTO_MPLS
311
312     def __eq__(self, other):
313         if not isinstance(other, self.__class__):
314             # try the other instance's __eq__.
315             return NotImplemented
316         return (self.addr == other.addr and
317                 self.via_label == other.via_label and
318                 self.obj_id == other.obj_id)
319
320
321 class VppRoutePath(object):
322
323     def __init__(
324             self,
325             nh_addr,
326             nh_sw_if_index,
327             nh_table_id=0,
328             labels=[],
329             nh_via_label=MPLS_LABEL_INVALID,
330             rpf_id=0,
331             next_hop_id=INVALID_INDEX,
332             proto=None,
333             flags=FibPathFlags.FIB_PATH_FLAG_NONE,
334             type=FibPathType.FIB_PATH_TYPE_NORMAL):
335         self.nh_itf = nh_sw_if_index
336         self.nh_table_id = nh_table_id
337         self.nh_labels = labels
338         self.weight = 1
339         self.rpf_id = rpf_id
340         self.proto = proto
341         self.flags = flags
342         self.type = type
343         self.nh = VppFibPathNextHop(nh_addr, nh_via_label, next_hop_id)
344         if proto is None:
345             self.proto = self.nh.proto()
346         else:
347             self.proto = proto
348         self.next_hop_id = next_hop_id
349
350     def encode_labels(self):
351         lstack = []
352         for l in self.nh_labels:
353             if type(l) == VppMplsLabel:
354                 lstack.append(l.encode())
355             else:
356                 lstack.append({'label': l,
357                                'ttl': 255})
358         while (len(lstack) < 16):
359             lstack.append({})
360
361         return lstack
362
363     def encode(self):
364         return {'weight': 1,
365                 'preference': 0,
366                 'table_id': self.nh_table_id,
367                 'nh': self.nh.encode(),
368                 'next_hop_id': self.next_hop_id,
369                 'sw_if_index': self.nh_itf,
370                 'rpf_id': self.rpf_id,
371                 'proto': self.proto,
372                 'type': self.type,
373                 'flags': self.flags,
374                 'n_labels': len(self.nh_labels),
375                 'label_stack': self.encode_labels()}
376
377     def __eq__(self, other):
378         if isinstance(other, self.__class__):
379             return self.nh == other.nh
380         elif hasattr(other, 'sw_if_index'):
381             # vl_api_fib_path_t
382             if (len(self.nh_labels) != other.n_labels):
383                 return False
384             for i in range(len(self.nh_labels)):
385                 if (self.nh_labels[i] != other.label_stack[i]):
386                     return False
387             return self.nh_itf == other.sw_if_index
388         else:
389             return False
390
391     def __ne__(self, other):
392         return not (self == other)
393
394
395 class VppMRoutePath(VppRoutePath):
396
397     def __init__(self, nh_sw_if_index, flags,
398                  nh=None,
399                  proto=FibPathProto.FIB_PATH_NH_PROTO_IP4,
400                  type=FibPathType.FIB_PATH_TYPE_NORMAL,
401                  bier_imp=INVALID_INDEX):
402         if not nh:
403             nh = "::" if proto is FibPathProto.FIB_PATH_NH_PROTO_IP6 \
404                  else "0.0.0.0"
405         super(VppMRoutePath, self).__init__(nh,
406                                             nh_sw_if_index,
407                                             proto=proto,
408                                             type=type,
409                                             next_hop_id=bier_imp)
410         self.nh_i_flags = flags
411         self.bier_imp = bier_imp
412
413     def encode(self):
414         return {'path': super(VppMRoutePath, self).encode(),
415                 'itf_flags': self.nh_i_flags}
416
417
418 class VppIpRoute(VppObject):
419     """
420     IP Route
421     """
422
423     def __init__(self, test, dest_addr,
424                  dest_addr_len, paths, table_id=0, register=True):
425         self._test = test
426         self.paths = paths
427         self.table_id = table_id
428         self.prefix = VppIpPrefix(dest_addr, dest_addr_len)
429         self.register = register
430         self.stats_index = None
431         self.modified = False
432
433         self.encoded_paths = []
434         for path in self.paths:
435             self.encoded_paths.append(path.encode())
436
437     def __eq__(self, other):
438         if self.table_id == other.table_id and \
439            self.prefix == other.prefix:
440             return True
441         return False
442
443     def modify(self, paths):
444         self.paths = paths
445         self.encoded_paths = []
446         for path in self.paths:
447             self.encoded_paths.append(path.encode())
448         self.modified = True
449
450         self._test.vapi.ip_route_add_del(route={'table_id': self.table_id,
451                                                 'prefix': self.prefix.encode(),
452                                                 'n_paths': len(
453                                                     self.encoded_paths),
454                                                 'paths': self.encoded_paths,
455                                                 },
456                                          is_add=1,
457                                          is_multipath=0)
458
459     def add_vpp_config(self):
460         r = self._test.vapi.ip_route_add_del(
461             route={'table_id': self.table_id,
462                    'prefix': self.prefix.encode(),
463                    'n_paths': len(self.encoded_paths),
464                    'paths': self.encoded_paths,
465                    },
466             is_add=1,
467             is_multipath=0)
468         self.stats_index = r.stats_index
469         if self.register:
470             self._test.registry.register(self, self._test.logger)
471
472     def remove_vpp_config(self):
473         # there's no need to issue different deletes for modified routes
474         # we do this only to test the two different ways to delete routes
475         # eiter by passing all the paths to remove and mutlipath=1 or
476         # passing no paths and multipath=0
477         if self.modified:
478             self._test.vapi.ip_route_add_del(
479                 route={'table_id': self.table_id,
480                        'prefix': self.prefix.encode(),
481                        'n_paths': len(
482                            self.encoded_paths),
483                        'paths': self.encoded_paths},
484                 is_add=0,
485                 is_multipath=1)
486         else:
487             self._test.vapi.ip_route_add_del(
488                 route={'table_id': self.table_id,
489                        'prefix': self.prefix.encode(),
490                        'n_paths': 0},
491                 is_add=0,
492                 is_multipath=0)
493
494     def query_vpp_config(self):
495         return find_route(self._test,
496                           self.prefix.address,
497                           self.prefix.len,
498                           self.table_id)
499
500     def object_id(self):
501         return ("%s:table-%d-%s/%d" % (
502             'ip6-route' if self.prefix.addr.version == 6 else 'ip-route',
503                 self.table_id,
504                 self.prefix.address,
505                 self.prefix.len))
506
507     def get_stats_to(self):
508         c = self._test.statistics.get_counter("/net/route/to")
509         return c[0][self.stats_index]
510
511     def get_stats_via(self):
512         c = self._test.statistics.get_counter("/net/route/via")
513         return c[0][self.stats_index]
514
515
516 class VppIpMRoute(VppObject):
517     """
518     IP Multicast Route
519     """
520
521     def __init__(self, test, src_addr, grp_addr,
522                  grp_addr_len, e_flags, paths, table_id=0,
523                  rpf_id=0):
524         self._test = test
525         self.paths = paths
526         self.table_id = table_id
527         self.e_flags = e_flags
528         self.rpf_id = rpf_id
529
530         self.prefix = VppIpMPrefix(src_addr, grp_addr, grp_addr_len)
531         self.encoded_paths = []
532         for path in self.paths:
533             self.encoded_paths.append(path.encode())
534
535     def add_vpp_config(self):
536         r = self._test.vapi.ip_mroute_add_del(self.table_id,
537                                               self.prefix.encode(),
538                                               self.e_flags,
539                                               self.rpf_id,
540                                               self.encoded_paths,
541                                               is_add=1)
542         self.stats_index = r.stats_index
543         self._test.registry.register(self, self._test.logger)
544
545     def remove_vpp_config(self):
546         self._test.vapi.ip_mroute_add_del(self.table_id,
547                                           self.prefix.encode(),
548                                           self.e_flags,
549                                           self.rpf_id,
550                                           self.encoded_paths,
551                                           is_add=0)
552
553     def update_entry_flags(self, flags):
554         self.e_flags = flags
555         self._test.vapi.ip_mroute_add_del(self.table_id,
556                                           self.prefix.encode(),
557                                           self.e_flags,
558                                           self.rpf_id,
559                                           [],
560                                           is_add=1)
561
562     def update_rpf_id(self, rpf_id):
563         self.rpf_id = rpf_id
564         self._test.vapi.ip_mroute_add_del(self.table_id,
565                                           self.prefix.encode(),
566                                           self.e_flags,
567                                           self.rpf_id,
568                                           [],
569                                           is_add=1)
570
571     def update_path_flags(self, itf, flags):
572         for p in range(len(self.paths)):
573             if self.paths[p].nh_itf == itf:
574                 self.paths[p].nh_i_flags = flags
575             self.encoded_paths[p] = self.paths[p].encode()
576             break
577
578         self._test.vapi.ip_mroute_add_del(self.table_id,
579                                           self.prefix.encode(),
580                                           self.e_flags,
581                                           self.rpf_id,
582                                           [self.encoded_paths[p]],
583                                           is_add=1,
584                                           is_multipath=0)
585
586     def query_vpp_config(self):
587         return find_mroute(self._test,
588                            self.prefix.gaddr,
589                            self.prefix.saddr,
590                            self.prefix.length,
591                            self.table_id)
592
593     def object_id(self):
594         return ("%d:(%s,%s/%d)" % (self.table_id,
595                                    self.prefix.saddr,
596                                    self.prefix.gaddr,
597                                    self.prefix.length))
598
599     def get_stats(self):
600         c = self._test.statistics.get_counter("/net/mroute")
601         return c[0][self.stats_index]
602
603
604 class VppMFibSignal(object):
605     def __init__(self, test, route, interface, packet):
606         self.route = route
607         self.interface = interface
608         self.packet = packet
609         self.test = test
610
611     def compare(self, signal):
612         self.test.assertEqual(self.interface, signal.sw_if_index)
613         self.test.assertEqual(self.route.table_id, signal.table_id)
614         self.test.assertEqual(self.route.prefix, signal.prefix)
615
616
617 class VppMplsIpBind(VppObject):
618     """
619     MPLS to IP Binding
620     """
621
622     def __init__(self, test, local_label, dest_addr, dest_addr_len,
623                  table_id=0, ip_table_id=0, is_ip6=0):
624         self._test = test
625         self.dest_addr_len = dest_addr_len
626         self.dest_addr = dest_addr
627         self.ip_addr = ip_address(text_type(dest_addr))
628         self.local_label = local_label
629         self.table_id = table_id
630         self.ip_table_id = ip_table_id
631         self.prefix = VppIpPrefix(dest_addr, dest_addr_len)
632
633     def add_vpp_config(self):
634         self._test.vapi.mpls_ip_bind_unbind(self.local_label,
635                                             self.prefix.encode(),
636                                             table_id=self.table_id,
637                                             ip_table_id=self.ip_table_id)
638         self._test.registry.register(self, self._test.logger)
639
640     def remove_vpp_config(self):
641         self._test.vapi.mpls_ip_bind_unbind(self.local_label,
642                                             self.prefix.encode(),
643                                             table_id=self.table_id,
644                                             ip_table_id=self.ip_table_id,
645                                             is_bind=0)
646
647     def query_vpp_config(self):
648         dump = self._test.vapi.mpls_route_dump(self.table_id)
649         for e in dump:
650             if self.local_label == e.mr_route.mr_label \
651                and self.table_id == e.mr_route.mr_table_id:
652                 return True
653         return False
654
655     def object_id(self):
656         return ("%d:%s binds %d:%s/%d"
657                 % (self.table_id,
658                    self.local_label,
659                    self.ip_table_id,
660                    self.dest_addr,
661                    self.dest_addr_len))
662
663
664 class VppMplsTable(VppObject):
665
666     def __init__(self,
667                  test,
668                  table_id):
669         self._test = test
670         self.table_id = table_id
671
672     def add_vpp_config(self):
673         self._test.vapi.mpls_table_add_del(
674             self.table_id,
675             is_add=1)
676         self._test.registry.register(self, self._test.logger)
677
678     def remove_vpp_config(self):
679         self._test.vapi.mpls_table_add_del(
680             self.table_id,
681             is_add=0)
682
683     def query_vpp_config(self):
684         dump = self._test.vapi.mpls_table_dump()
685         for d in dump:
686             if d.mt_table.mt_table_id == self.table_id:
687                 return True
688         return False
689
690     def object_id(self):
691         return ("table-mpls-%d" % (self.table_id))
692
693
694 class VppMplsRoute(VppObject):
695     """
696     MPLS Route/LSP
697     """
698
699     def __init__(self, test, local_label, eos_bit, paths, table_id=0,
700                  is_multicast=0,
701                  eos_proto=FibPathProto.FIB_PATH_NH_PROTO_IP4):
702         self._test = test
703         self.paths = paths
704         self.local_label = local_label
705         self.eos_bit = eos_bit
706         self.eos_proto = eos_proto
707         self.table_id = table_id
708         self.is_multicast = is_multicast
709
710     def add_vpp_config(self):
711         paths = []
712         for path in self.paths:
713             paths.append(path.encode())
714
715         r = self._test.vapi.mpls_route_add_del(self.table_id,
716                                                self.local_label,
717                                                self.eos_bit,
718                                                self.eos_proto,
719                                                self.is_multicast,
720                                                paths, 1, 0)
721         self.stats_index = r.stats_index
722         self._test.registry.register(self, self._test.logger)
723
724     def remove_vpp_config(self):
725         paths = []
726         for path in self.paths:
727             paths.append(path.encode())
728
729         self._test.vapi.mpls_route_add_del(self.table_id,
730                                            self.local_label,
731                                            self.eos_bit,
732                                            self.eos_proto,
733                                            self.is_multicast,
734                                            paths, 0, 0)
735
736     def query_vpp_config(self):
737         return find_mpls_route(self._test, self.table_id,
738                                self.local_label, self.eos_bit)
739
740     def object_id(self):
741         return ("mpls-route-%d:%s/%d"
742                 % (self.table_id,
743                    self.local_label,
744                    20 + self.eos_bit))
745
746     def get_stats_to(self):
747         c = self._test.statistics.get_counter("/net/route/to")
748         return c[0][self.stats_index]
749
750     def get_stats_via(self):
751         c = self._test.statistics.get_counter("/net/route/via")
752         return c[0][self.stats_index]