txtinfo: show the scaled route cost in the sgw output
[olsrd.git] / lib / txtinfo / src / olsrd_txtinfo.c
1 /*
2  * The olsr.org Optimized Link-State Routing daemon(olsrd)
3  * Copyright (c) 2004
4  *
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above copyright
14  *   notice, this list of conditions and the following disclaimer in
15  *   the documentation and/or other materials provided with the
16  *   distribution.
17  * * Neither the name of olsr.org, olsrd nor the names of its
18  *   contributors may be used to endorse or promote products derived
19  *   from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *
34  * Visit http://www.olsr.org for more information.
35  *
36  * If you find this software useful feel free to make a donation
37  * to the project. For more information see the website or contact
38  * the copyright holders.
39  *
40  */
41
42 #include "olsrd_txtinfo.h"
43
44 #include <unistd.h>
45
46 #include "ipcalc.h"
47 #include "builddata.h"
48 #include "neighbor_table.h"
49 #include "mpr_selector_set.h"
50 #include "mid_set.h"
51 #include "routing_table.h"
52 #include "lq_plugin.h"
53 #include "gateway.h"
54 #include "olsrd_plugin.h"
55 #include "../../info/info_types.h"
56 #include "../../info/http_headers.h"
57 #include "gateway_default_handler.h"
58
59 bool isCommand(const char *str, unsigned long long siw) {
60   const char * cmd;
61   switch (siw) {
62     case SIW_OLSRD_CONF:
63       cmd = "/con";
64       break;
65
66     case SIW_ALL:
67       cmd = "/all";
68       break;
69
70     case SIW_RUNTIME_ALL:
71       cmd = "/runtime";
72       break;
73
74     case SIW_STARTUP_ALL:
75       cmd = "/startup";
76       break;
77
78     case SIW_NEIGHBORS:
79       cmd = "/nei";
80       break;
81
82     case SIW_LINKS:
83       cmd = "/lin";
84       break;
85
86     case SIW_ROUTES:
87       cmd = "/rou";
88       break;
89
90     case SIW_HNA:
91       cmd = "/hna";
92       break;
93
94     case SIW_MID:
95       cmd = "/mid";
96       break;
97
98     case SIW_TOPOLOGY:
99       cmd = "/top";
100       break;
101
102     case SIW_GATEWAYS:
103       cmd = "/gat";
104       break;
105
106     case SIW_INTERFACES:
107       cmd = "/int";
108       break;
109
110     case SIW_2HOP:
111       cmd = "/2ho";
112       break;
113
114     case SIW_SGW:
115       cmd = "/sgw";
116       break;
117
118     case SIW_VERSION:
119       cmd = "/ver";
120       break;
121
122     case SIW_CONFIG:
123       cmd = "/config";
124       return false; /* not implemented */
125
126     case SIW_PLUGINS:
127       cmd = "/plugins";
128       return false; /* not implemented */
129
130     case SIW_NEIGHBORS_FREIFUNK:
131       cmd = "/neighbours";
132       break;
133
134     default:
135       return false;
136   }
137
138   return !strcmp(str, cmd);
139 }
140
141 void output_error(struct autobuf *abuf, unsigned int status, const char * req, bool http_headers) {
142   if (http_headers || (status == INFO_HTTP_OK)) {
143     return;
144   }
145
146   switch (status) {
147     case INFO_HTTP_NOTFOUND:
148       abuf_appendf(abuf, "error: Invalid request '%s'\n", req);
149       break;
150
151     case INFO_HTTP_NOCONTENT:
152       /* wget can't handle output of zero length */
153       abuf_puts(abuf, "\n");
154       break;
155
156     default:
157       abuf_appendf(abuf, "error: Unknown status %d for request '%s'\n", status, req);
158       return;
159   }
160 }
161
162 static void ipc_print_neighbors_internal(struct autobuf *abuf, bool list_2hop) {
163   struct ipaddr_str neighAddrBuf;
164   struct neighbor_entry *neigh;
165   struct neighbor_2_list_entry *list_2;
166   int thop_cnt;
167
168   const char * field;
169   if (list_2hop) {
170     field = "(2-hop address)+";
171   } else {
172     field = "2-hop count";
173   }
174
175   abuf_puts(abuf, "Table: Neighbors\n");
176   abuf_appendf(abuf, "IP address\tSYM\tMPR\tMPRS\tWill.\t%s\n", field);
177
178   /* Neighbors */
179   OLSR_FOR_ALL_NBR_ENTRIES(neigh) {
180     abuf_appendf(abuf, "%s\t%s\t%s\t%s\t%d",
181       olsr_ip_to_string(&neighAddrBuf, &neigh->neighbor_main_addr),
182       (neigh->status == SYM) ? "YES" : "NO",
183       neigh->is_mpr ? "YES" : "NO",
184       olsr_lookup_mprs_set(&neigh->neighbor_main_addr) ? "YES" : "NO",
185       neigh->willingness);
186     thop_cnt = 0;
187
188     for (list_2 = neigh->neighbor_2_list.next; list_2 != &neigh->neighbor_2_list; list_2 = list_2->next) {
189       if (list_2hop) {
190         if (list_2->neighbor_2) {
191           abuf_appendf(abuf, "\t%s", olsr_ip_to_string(&neighAddrBuf, &list_2->neighbor_2->neighbor_2_addr));
192         }
193       } else {
194         thop_cnt++;
195       }
196     }
197
198     if (!list_2hop) {
199       abuf_appendf(abuf, "\t%d", thop_cnt);
200     }
201     abuf_puts(abuf, "\n");
202   } OLSR_FOR_ALL_NBR_ENTRIES_END(neigh);
203   abuf_puts(abuf, "\n");
204 }
205
206 void ipc_print_neighbors(struct autobuf *abuf) {
207   ipc_print_neighbors_internal(abuf, false);
208 }
209
210 void ipc_print_links(struct autobuf *abuf) {
211   struct link_entry *my_link;
212
213   const char * field;
214   if (vtime) {
215     field = "VTime";
216   } else {
217     field = "Hyst.";
218   }
219
220   abuf_puts(abuf, "Table: Links\n");
221   abuf_appendf(abuf, "Local IP\tRemote IP\t%s\tLQ\tNLQ\tCost\n", field);
222
223   /* Link set */
224   OLSR_FOR_ALL_LINK_ENTRIES(my_link) {
225     struct ipaddr_str localAddr;
226     struct ipaddr_str remoteAddr;
227     struct lqtextbuffer lqbuffer;
228     struct lqtextbuffer costbuffer;
229     unsigned int diffI = 0;
230     unsigned int diffF = 0;
231
232     if (vtime) {
233       unsigned int diff = my_link->link_timer ? (unsigned int) (my_link->link_timer->timer_clock - now_times) : 0;
234       diffI = diff / 1000;
235       diffF = diff % 1000;
236     }
237
238     abuf_appendf(abuf, "%s\t%s\t%u.%03u\t%s\t%s\n",
239       olsr_ip_to_string(&localAddr, &my_link->local_iface_addr),
240       olsr_ip_to_string(&remoteAddr, &my_link->neighbor_iface_addr),
241       diffI,
242       diffF,
243       get_link_entry_text(my_link, '\t', &lqbuffer),
244       get_linkcost_text(my_link->linkcost, false, &costbuffer));
245   } OLSR_FOR_ALL_LINK_ENTRIES_END(my_link);
246   abuf_puts(abuf, "\n");
247 }
248
249 void ipc_print_routes(struct autobuf *abuf) {
250   struct rt_entry *rt;
251
252   abuf_puts(abuf, "Table: Routes\n");
253   abuf_puts(abuf, "Destination\tGateway IP\tMetric\tETX\tInterface\n");
254
255   /* Walk the route table */
256   OLSR_FOR_ALL_RT_ENTRIES(rt) {
257     struct ipaddr_str dstAddr;
258     struct ipaddr_str nexthopAddr;
259     struct lqtextbuffer costbuffer;
260
261     if (rt->rt_best) {
262       abuf_appendf(abuf, "%s/%d\t%s\t%d\t%s\t%s\t\n",
263         olsr_ip_to_string(&dstAddr, &rt->rt_dst.prefix),
264         rt->rt_dst.prefix_len,
265         olsr_ip_to_string(&nexthopAddr, &rt->rt_best->rtp_nexthop.gateway),
266         rt->rt_best->rtp_metric.hops,
267         get_linkcost_text(rt->rt_best->rtp_metric.cost, true, &costbuffer),
268         if_ifwithindex_name(rt->rt_best->rtp_nexthop.iif_index));
269     }
270   } OLSR_FOR_ALL_RT_ENTRIES_END(rt);
271   abuf_puts(abuf, "\n");
272 }
273
274 void ipc_print_topology(struct autobuf *abuf) {
275   struct tc_entry *tc;
276
277   const char * field;
278   if (vtime) {
279     field = "\tVTime";
280   } else {
281     field = "";
282   }
283
284   abuf_puts(abuf, "Table: Topology\n");
285   abuf_appendf(abuf, "Dest. IP\tLast hop IP\tLQ\tNLQ\tCost%s\n", field);
286
287   /* Topology */
288   OLSR_FOR_ALL_TC_ENTRIES(tc) {
289     struct tc_edge_entry *tc_edge;
290     OLSR_FOR_ALL_TC_EDGE_ENTRIES(tc, tc_edge) {
291       if (tc_edge->edge_inv) {
292         struct ipaddr_str dstAddr;
293         struct ipaddr_str lastHopAddr;
294         struct lqtextbuffer lqbuffer;
295         struct lqtextbuffer costbuffer;
296
297         abuf_appendf(abuf, "%s\t%s\t%s\t%s",
298           olsr_ip_to_string(&dstAddr, &tc_edge->T_dest_addr),
299           olsr_ip_to_string(&lastHopAddr, &tc->addr),
300           get_tc_edge_entry_text(tc_edge, '\t', &lqbuffer),
301           get_linkcost_text(tc_edge->cost, false, &costbuffer));
302
303         if (vtime) {
304           unsigned int diff = (unsigned int) (tc->validity_timer ? (tc->validity_timer->timer_clock - now_times) : 0);
305           abuf_appendf(abuf, "\t%u.%03u", diff / 1000, diff % 1000);
306         }
307
308         abuf_puts(abuf, "\n");
309       }
310     } OLSR_FOR_ALL_TC_EDGE_ENTRIES_END(tc, tc_edge);
311   } OLSR_FOR_ALL_TC_ENTRIES_END(tc);
312   abuf_puts(abuf, "\n");
313 }
314
315 void ipc_print_hna(struct autobuf *abuf) {
316   struct ip_prefix_list *hna;
317   struct hna_entry *tmp_hna;
318   struct ipaddr_str prefixbuf;
319   struct ipaddr_str gwaddrbuf;
320
321   const char * field;
322   if (vtime) {
323     field = "\tVTime";
324   } else {
325     field = "";
326   }
327
328   abuf_puts(abuf, "Table: HNA\n");
329   abuf_appendf(abuf, "Destination\tGateway%s\n", field);
330
331   /* Announced HNA entries */
332   for (hna = olsr_cnf->hna_entries; hna != NULL ; hna = hna->next) {
333     abuf_appendf(abuf, "%s/%d\t%s",
334       olsr_ip_to_string(&prefixbuf, &hna->net.prefix),
335       hna->net.prefix_len,
336       olsr_ip_to_string(&gwaddrbuf, &olsr_cnf->main_addr));
337
338       if (vtime) {
339         abuf_appendf(abuf, "\t%u.%03u", 0, 0);
340       }
341       abuf_puts(abuf, "\n");
342   }
343
344   /* HNA entries */
345   OLSR_FOR_ALL_HNA_ENTRIES(tmp_hna) {
346     struct hna_net *tmp_net;
347
348     /* Check all networks */
349     for (tmp_net = tmp_hna->networks.next; tmp_net != &tmp_hna->networks; tmp_net = tmp_net->next) {
350       abuf_appendf(abuf, "%s/%d\t%s",
351         olsr_ip_to_string(&prefixbuf, &tmp_net->hna_prefix.prefix),
352         tmp_net->hna_prefix.prefix_len,
353         olsr_ip_to_string(&gwaddrbuf, &tmp_hna->A_gateway_addr));
354
355       if (vtime) {
356         unsigned int diff = tmp_net->hna_net_timer ? (unsigned int) (tmp_net->hna_net_timer->timer_clock - now_times) : 0;
357         abuf_appendf(abuf, "\t%u.%03u", diff / 1000, diff % 1000);
358       }
359       abuf_puts(abuf, "\n");
360     }
361   } OLSR_FOR_ALL_HNA_ENTRIES_END(tmp_hna);
362   abuf_puts(abuf, "\n");
363 }
364
365 void ipc_print_mid(struct autobuf *abuf) {
366   int idx;
367
368   const char * field;
369   if (vtime) {
370     field = ":VTime";
371   } else {
372     field = "";
373   }
374
375   abuf_puts(abuf, "Table: MID\n");
376   abuf_appendf(abuf, "IP address\t(Alias%s)+\n", field);
377
378   /* MID */
379   for (idx = 0; idx < HASHSIZE; idx++) {
380     struct mid_entry *entry = mid_set[idx].next;
381
382     while (entry && (entry != &mid_set[idx])) {
383       struct mid_address *alias = entry->aliases;
384       struct ipaddr_str ipAddr;
385
386       abuf_puts(abuf, olsr_ip_to_string(&ipAddr, &entry->main_addr));
387       abuf_puts(abuf, "\t");
388
389       while (alias) {
390         struct ipaddr_str buf2;
391
392         abuf_appendf(abuf, "\t%s", olsr_ip_to_string(&buf2, &alias->alias));
393
394         if (vtime) {
395           unsigned int diff = (unsigned int) (alias->vtime - now_times);
396           abuf_appendf(abuf, ":%u.%03u", diff / 1000, diff % 1000);
397         }
398
399         alias = alias->next_alias;
400       }
401       entry = entry->next;
402       abuf_puts(abuf, "\n");
403     }
404   }
405   abuf_puts(abuf, "\n");
406 }
407
408 void ipc_print_gateways(struct autobuf *abuf) {
409 #ifndef __linux__
410   abuf_puts(abuf, "error: Gateway mode is only supported on Linux\n");
411 #else /* __linux__ */
412   static const char *fmth = "%-6s %-45s %-15s %-6s %-9s %-9s %-7s %-4s %s\n";
413   static const char *fmtv = "%c%c%-4s %-45s %-15s %-6u %-9u %-9u %-7s %-4s %s\n";
414
415   static const char IPV4[] = "ipv4";
416   static const char IPV4_NAT[] = "ipv4(n)";
417   static const char IPV6[] = "ipv6";
418   static const char NONE[] = "-";
419
420   struct gateway_entry *gw;
421   struct gateway_entry *current_gw_4 = olsr_get_inet_gateway(false);
422   struct gateway_entry *current_gw_6 = olsr_get_inet_gateway(true);
423
424   abuf_puts(abuf, "Table: Gateways\n");
425   abuf_appendf(abuf, fmth, "Status", "Gateway IP", "ETX", "Hopcnt", "Uplink", "Downlnk", "IPv4", "IPv6", "Prefix");
426
427   OLSR_FOR_ALL_GATEWAY_ENTRIES(gw) {
428     char v4, v6;
429     const char *v4type, *v6type;
430     struct ipaddr_str originatorbuf;
431     struct lqtextbuffer lqbuf;
432     struct tc_entry *tc = olsr_lookup_tc_entry(&gw->originator);
433
434     if (current_gw_4 && (gw == current_gw_4)) {
435       v4 = 's';
436     } else if (isGwSelectable(gw, false)) {
437       v4 = 'u';
438     } else {
439       v4 = '-';
440     }
441
442     if (current_gw_6 && (gw == current_gw_6)) {
443       v6 = 's';
444     } else if (isGwSelectable(gw, true)) {
445       v6 = 'u';
446     } else {
447       v6 = '-';
448     }
449
450     if (gw->ipv4) {
451       v4type = gw->ipv4nat ? IPV4_NAT : IPV4;
452     } else {
453       v4type = NONE;
454     }
455     if (gw->ipv6) {
456       v6type = IPV6;
457     } else {
458       v6type = NONE;
459     }
460
461     abuf_appendf(abuf, fmtv, //
462       v4, //
463       v6, //
464       "", //
465       olsr_ip_to_string(&originatorbuf, &gw->originator), //
466       get_linkcost_text(!tc ? ROUTE_COST_BROKEN : tc->path_cost, true, &lqbuf), //
467       !tc ? 0 : tc->hops, //
468       gw->uplink, //
469       gw->downlink, //
470       v4type, //
471       v6type, //
472       !gw->external_prefix.prefix_len ? NONE : olsr_ip_prefix_to_string(&gw->external_prefix));
473   } OLSR_FOR_ALL_GATEWAY_ENTRIES_END (gw)
474 #endif /* __linux__ */
475   abuf_puts(abuf, "\n");
476 }
477
478 #ifdef __linux__
479
480 /** interface names for smart gateway tunnel interfaces, IPv4 */
481 extern struct interfaceName * sgwTunnel4InterfaceNames;
482
483 /** interface names for smart gateway tunnel interfaces, IPv6 */
484 extern struct interfaceName * sgwTunnel6InterfaceNames;
485
486 /**
487  * Construct the sgw table for a given ip version
488  *
489  * @param abuf the string buffer
490  * @param ipv6 true for IPv6, false for IPv4
491  * @param fmtv the format for printing
492  */
493 static void sgw_ipvx(struct autobuf *abuf, bool ipv6, const char * fmth, const char * fmtv) {
494   struct interfaceName * sgwTunnelInterfaceNames = !ipv6 ? sgwTunnel4InterfaceNames : sgwTunnel6InterfaceNames;
495
496   abuf_appendf(abuf, "Table: Smart Gateway IPv%d\n", ipv6 ? 6 : 4);
497   abuf_appendf(abuf, fmth, " ", "Originator", "Prefix", "Uplink", "Downlink", "PathCost", "IPv4", "IPv4-NAT", "IPv6", "Tunnel-Name", "Destination", "Cost");
498
499   if (olsr_cnf->smart_gw_active && sgwTunnelInterfaceNames) {
500     struct gateway_entry * current_gw = olsr_get_inet_gateway(ipv6);
501
502     int i;
503     for (i = 0; i < olsr_cnf->smart_gw_use_count; i++) {
504       struct interfaceName * node = &sgwTunnelInterfaceNames[i];
505       struct gateway_entry * gw = node->gw;
506
507       if (gw) {
508         struct lqtextbuffer lqbuf;
509         struct tc_entry* tc = olsr_lookup_tc_entry(&gw->originator);
510
511         struct ipaddr_str originator;
512         struct ipaddr_str prefix;
513         char prefixAndMask[INET6_ADDRSTRLEN * 2];
514
515         olsr_ip_to_string(&originator, &gw->originator);
516         olsr_ip_to_string(&prefix, &gw->external_prefix.prefix);
517
518         if (!ipv6) {
519           union olsr_ip_addr netmask = { { 0 } };
520           struct ipaddr_str prefixMask;
521           prefix_to_netmask((uint8_t *) &netmask, sizeof(netmask.v4), gw->external_prefix.prefix_len);
522           olsr_ip_to_string(&prefixMask, &netmask);
523           snprintf(prefixAndMask, sizeof(prefixAndMask), "%s/%s", prefix.buf, prefixMask.buf);
524         } else {
525           snprintf(prefixAndMask, sizeof(prefixAndMask), "%s/%d", prefix.buf, gw->external_prefix.prefix_len);
526         }
527
528         abuf_appendf(abuf, fmtv, //
529           (current_gw && (current_gw == gw)) ? "*" : " ", // selected
530           originator.buf, // Originator
531           prefixAndMask, // 4: IP/Mask, 6: IP/Length
532           gw->uplink, // Uplink
533           gw->downlink, // Downlink
534           get_linkcost_text(!tc ? ROUTE_COST_BROKEN : tc->path_cost, true, &lqbuf), // PathCost
535           gw->ipv4 ? "Y" : "N", // IPv4
536           gw->ipv4nat ? "Y" : "N", // IPv4-NAT
537           gw->ipv6 ? "Y" : "N", // IPv6
538           node->name, // Tunnel-Name
539           originator.buf, // Destination
540           gw->path_cost // Cost
541           );
542       }
543     }
544   }
545   abuf_puts(abuf, "\n");
546 }
547 #endif /* __linux__ */
548
549 void ipc_print_sgw(struct autobuf *abuf) {
550 #ifndef __linux__
551   abuf_puts(abuf, "error: Gateway mode is only supported on Linux\n");
552 #else
553
554   static const char * fmth4 = "%s%-15s %-31s %-9s %-9s %-10s %-4s %-8s %-4s %-15s %-15s %s\n";
555   static const char * fmtv4 = "%s%-15s %-31s %-9u %-9u %-10s %-4s %-8s %-4s %-15s %-15s %lld\n";
556 #if 0
557   static const char * fmth6 = "%s%-45s %-49s %-9s %-9s %-10s %-4s %-8s %-4s %-15s %-45s %s\n";
558   static const char * fmtv6 = "%s%-45s %-49s %-9u %-9u %-10s %-4s %-8s %-4s %-15s %-45s %lld\n";
559 #endif
560
561   sgw_ipvx(abuf, false, fmth4, fmtv4);
562 #if 0
563   sgw_ipvx(abuf, true, fmth6, fmtv6);
564 #endif
565 #endif /* __linux__ */
566 }
567
568 void ipc_print_version(struct autobuf *abuf) {
569   abuf_appendf(abuf, "Version: %s (built on %s on %s)\n", olsrd_version, build_date, build_host);
570   abuf_puts(abuf, "\n");
571 }
572
573 void ipc_print_olsrd_conf(struct autobuf *abuf) {
574   olsrd_write_cnf_autobuf(abuf, olsr_cnf);
575 }
576
577 void ipc_print_interfaces(struct autobuf *abuf) {
578   const struct olsr_if *ifs;
579
580   abuf_puts(abuf, "Table: Interfaces\n");
581   abuf_puts(abuf, "Name\tState\tMTU\tWLAN\tSrc-Adress\tMask\tDst-Adress\n");
582
583   for (ifs = olsr_cnf->interfaces; ifs != NULL ; ifs = ifs->next) {
584     const struct interface_olsr * const rifs = ifs->interf;
585
586     abuf_appendf(abuf, "%s", ifs->name);
587
588     if (!rifs) {
589       abuf_puts(abuf, "\tDOWN\n");
590       continue;
591     }
592     abuf_appendf(abuf, "\tUP\t%d\t%s",
593         rifs->int_mtu,
594         rifs->is_wireless ? "Yes" : "No");
595
596     {
597       struct ipaddr_str addrbuf;
598       struct ipaddr_str maskbuf;
599       struct ipaddr_str bcastbuf;
600
601       if (olsr_cnf->ip_version == AF_INET) {
602         abuf_appendf(abuf, "\t%s\t%s\t%s\n",
603             ip4_to_string(&addrbuf, rifs->int_addr.sin_addr),
604             ip4_to_string(&maskbuf, rifs->int_netmask.sin_addr),
605             ip4_to_string(&bcastbuf, rifs->int_broadaddr.sin_addr));
606       } else {
607         abuf_appendf(abuf, "\t%s\t\t%s\n",
608             ip6_to_string(&addrbuf, &rifs->int6_addr.sin6_addr),
609             ip6_to_string(&bcastbuf, &rifs->int6_multaddr.sin6_addr));
610       }
611     }
612   }
613   abuf_puts(abuf, "\n");
614 }
615
616 void ipc_print_twohop(struct autobuf *abuf) {
617   ipc_print_neighbors_internal(abuf, true);
618 }