ae68d9212256e53bf2c8d9f00e8c92e6ebe3cb93
[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 int 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 static void ipc_print_neighbors_internal(struct autobuf *abuf, bool list_2hop) {
142   struct ipaddr_str neighAddrBuf;
143   struct neighbor_entry *neigh;
144   struct neighbor_2_list_entry *list_2;
145   int thop_cnt;
146
147   const char * field;
148   if (list_2hop) {
149     field = "(2-hop address)+";
150   } else {
151     field = "2-hop count";
152   }
153
154   abuf_puts(abuf, "Table: Neighbors\n");
155   abuf_appendf(abuf, "IP address\tSYM\tMPR\tMPRS\tWill.\t%s\n", field);
156
157   /* Neighbors */
158   OLSR_FOR_ALL_NBR_ENTRIES(neigh) {
159     abuf_appendf(abuf, "%s\t%s\t%s\t%s\t%d",
160       olsr_ip_to_string(&neighAddrBuf, &neigh->neighbor_main_addr),
161       (neigh->status == SYM) ? "YES" : "NO",
162       neigh->is_mpr ? "YES" : "NO",
163       olsr_lookup_mprs_set(&neigh->neighbor_main_addr) ? "YES" : "NO",
164       neigh->willingness);
165     thop_cnt = 0;
166
167     for (list_2 = neigh->neighbor_2_list.next; list_2 != &neigh->neighbor_2_list; list_2 = list_2->next) {
168       if (list_2hop) {
169         if (list_2->neighbor_2) {
170           abuf_appendf(abuf, "\t%s", olsr_ip_to_string(&neighAddrBuf, &list_2->neighbor_2->neighbor_2_addr));
171         }
172       } else {
173         thop_cnt++;
174       }
175     }
176
177     if (!list_2hop) {
178       abuf_appendf(abuf, "\t%d", thop_cnt);
179     }
180     abuf_puts(abuf, "\n");
181   } OLSR_FOR_ALL_NBR_ENTRIES_END(neigh);
182   abuf_puts(abuf, "\n");
183 }
184
185 void output_error(struct autobuf *abuf, unsigned int status, const char * req, bool http_headers) {
186   if (http_headers) {
187     return;
188   }
189
190   switch (status) {
191     case INFO_HTTP_OK:
192       break;
193
194     case INFO_HTTP_NOTFOUND:
195       abuf_appendf(abuf, "error: Invalid request '%s'\n", req);
196       break;
197
198     case INFO_HTTP_NOCONTENT:
199       /* wget can't handle output of zero length */
200       abuf_puts(abuf, "\n");
201       break;
202
203     default:
204       abuf_appendf(abuf, "error: Unknown status %d for request '%s'\n", status, req);
205       return;
206   }
207 }
208
209 void ipc_print_neighbors(struct autobuf *abuf) {
210   ipc_print_neighbors_internal(abuf, false);
211 }
212
213 void ipc_print_links(struct autobuf *abuf) {
214   struct link_entry *my_link;
215
216   const char * field;
217   if (vtime) {
218     field = "VTime";
219   } else {
220     field = "Hyst.";
221   }
222
223   abuf_puts(abuf, "Table: Links\n");
224   abuf_appendf(abuf, "Local IP\tRemote IP\t%s\tLQ\tNLQ\tCost\n", field);
225
226   /* Link set */
227   OLSR_FOR_ALL_LINK_ENTRIES(my_link) {
228     struct ipaddr_str localAddr;
229     struct ipaddr_str remoteAddr;
230     struct lqtextbuffer lqbuffer;
231     struct lqtextbuffer costbuffer;
232     unsigned int diffI = 0;
233     unsigned int diffF = 0;
234
235     if (vtime) {
236       unsigned int diff = my_link->link_timer ? (unsigned int) (my_link->link_timer->timer_clock - now_times) : 0;
237       diffI = diff / 1000;
238       diffF = diff % 1000;
239     }
240
241     abuf_appendf(abuf, "%s\t%s\t%u.%03u\t%s\t%s\n",
242       olsr_ip_to_string(&localAddr, &my_link->local_iface_addr),
243       olsr_ip_to_string(&remoteAddr, &my_link->neighbor_iface_addr),
244       diffI,
245       diffF,
246       get_link_entry_text(my_link, '\t', &lqbuffer),
247       get_linkcost_text(my_link->linkcost, false, &costbuffer));
248   } OLSR_FOR_ALL_LINK_ENTRIES_END(my_link);
249   abuf_puts(abuf, "\n");
250 }
251
252 void ipc_print_routes(struct autobuf *abuf) {
253   struct rt_entry *rt;
254
255   abuf_puts(abuf, "Table: Routes\n");
256   abuf_puts(abuf, "Destination\tGateway IP\tMetric\tETX\tInterface\n");
257
258   /* Walk the route table */
259   OLSR_FOR_ALL_RT_ENTRIES(rt) {
260     struct ipaddr_str dstAddr;
261     struct ipaddr_str nexthopAddr;
262     struct lqtextbuffer costbuffer;
263
264     if (rt->rt_best) {
265       abuf_appendf(abuf, "%s/%d\t%s\t%d\t%s\t%s\t\n",
266         olsr_ip_to_string(&dstAddr, &rt->rt_dst.prefix),
267         rt->rt_dst.prefix_len,
268         olsr_ip_to_string(&nexthopAddr, &rt->rt_best->rtp_nexthop.gateway),
269         rt->rt_best->rtp_metric.hops,
270         get_linkcost_text(rt->rt_best->rtp_metric.cost, true, &costbuffer),
271         if_ifwithindex_name(rt->rt_best->rtp_nexthop.iif_index));
272     }
273   } OLSR_FOR_ALL_RT_ENTRIES_END(rt);
274   abuf_puts(abuf, "\n");
275 }
276
277 void ipc_print_topology(struct autobuf *abuf) {
278   struct tc_entry *tc;
279
280   const char * field;
281   if (vtime) {
282     field = "\tVTime";
283   } else {
284     field = "";
285   }
286
287   abuf_puts(abuf, "Table: Topology\n");
288   abuf_appendf(abuf, "Dest. IP\tLast hop IP\tLQ\tNLQ\tCost%s\n", field);
289
290   /* Topology */
291   OLSR_FOR_ALL_TC_ENTRIES(tc) {
292     struct tc_edge_entry *tc_edge;
293     OLSR_FOR_ALL_TC_EDGE_ENTRIES(tc, tc_edge) {
294       if (tc_edge->edge_inv) {
295         struct ipaddr_str dstAddr;
296         struct ipaddr_str lastHopAddr;
297         struct lqtextbuffer lqbuffer;
298         struct lqtextbuffer costbuffer;
299
300         abuf_appendf(abuf, "%s\t%s\t%s\t%s",
301           olsr_ip_to_string(&dstAddr, &tc_edge->T_dest_addr),
302           olsr_ip_to_string(&lastHopAddr, &tc->addr),
303           get_tc_edge_entry_text(tc_edge, '\t', &lqbuffer),
304           get_linkcost_text(tc_edge->cost, false, &costbuffer));
305
306         if (vtime) {
307           unsigned int diff = (unsigned int) (tc->validity_timer ? (tc->validity_timer->timer_clock - now_times) : 0);
308           abuf_appendf(abuf, "\t%u.%03u", diff / 1000, diff % 1000);
309         }
310
311         abuf_puts(abuf, "\n");
312       }
313     } OLSR_FOR_ALL_TC_EDGE_ENTRIES_END(tc, tc_edge);
314   } OLSR_FOR_ALL_TC_ENTRIES_END(tc);
315   abuf_puts(abuf, "\n");
316 }
317
318 void ipc_print_hna(struct autobuf *abuf) {
319   struct ip_prefix_list *hna;
320   struct hna_entry *tmp_hna;
321   struct ipaddr_str prefixbuf;
322   struct ipaddr_str gwaddrbuf;
323
324   const char * field;
325   if (vtime) {
326     field = "\tVTime";
327   } else {
328     field = "";
329   }
330
331   abuf_puts(abuf, "Table: HNA\n");
332   abuf_appendf(abuf, "Destination\tGateway%s\n", field);
333
334   /* Announced HNA entries */
335   for (hna = olsr_cnf->hna_entries; hna != NULL ; hna = hna->next) {
336     abuf_appendf(abuf, "%s/%d\t%s",
337       olsr_ip_to_string(&prefixbuf, &hna->net.prefix),
338       hna->net.prefix_len,
339       olsr_ip_to_string(&gwaddrbuf, &olsr_cnf->main_addr));
340
341       if (vtime) {
342         abuf_appendf(abuf, "\t%u.%03u", 0, 0);
343       }
344       abuf_puts(abuf, "\n");
345   }
346
347   /* HNA entries */
348   OLSR_FOR_ALL_HNA_ENTRIES(tmp_hna) {
349     struct hna_net *tmp_net;
350
351     /* Check all networks */
352     for (tmp_net = tmp_hna->networks.next; tmp_net != &tmp_hna->networks; tmp_net = tmp_net->next) {
353       abuf_appendf(abuf, "%s/%d\t%s",
354         olsr_ip_to_string(&prefixbuf, &tmp_net->hna_prefix.prefix),
355         tmp_net->hna_prefix.prefix_len,
356         olsr_ip_to_string(&gwaddrbuf, &tmp_hna->A_gateway_addr));
357
358       if (vtime) {
359         unsigned int diff = tmp_net->hna_net_timer ? (unsigned int) (tmp_net->hna_net_timer->timer_clock - now_times) : 0;
360         abuf_appendf(abuf, "\t%u.%03u", diff / 1000, diff % 1000);
361       }
362       abuf_puts(abuf, "\n");
363     }
364   } OLSR_FOR_ALL_HNA_ENTRIES_END(tmp_hna);
365   abuf_puts(abuf, "\n");
366 }
367
368 void ipc_print_mid(struct autobuf *abuf) {
369   int idx;
370
371   const char * field;
372   if (vtime) {
373     field = ":VTime";
374   } else {
375     field = "";
376   }
377
378   abuf_puts(abuf, "Table: MID\n");
379   abuf_appendf(abuf, "IP address\t(Alias%s)+\n", field);
380
381   /* MID */
382   for (idx = 0; idx < HASHSIZE; idx++) {
383     struct mid_entry *entry = mid_set[idx].next;
384
385     while (entry && (entry != &mid_set[idx])) {
386       struct mid_address *alias = entry->aliases;
387       struct ipaddr_str ipAddr;
388
389       abuf_puts(abuf, olsr_ip_to_string(&ipAddr, &entry->main_addr));
390       abuf_puts(abuf, "\t");
391
392       while (alias) {
393         struct ipaddr_str buf2;
394
395         abuf_appendf(abuf, "\t%s", olsr_ip_to_string(&buf2, &alias->alias));
396
397         if (vtime) {
398           unsigned int diff = (unsigned int) (alias->vtime - now_times);
399           abuf_appendf(abuf, ":%u.%03u", diff / 1000, diff % 1000);
400         }
401
402         alias = alias->next_alias;
403       }
404       entry = entry->next;
405       abuf_puts(abuf, "\n");
406     }
407   }
408   abuf_puts(abuf, "\n");
409 }
410
411 void ipc_print_gateways(struct autobuf *abuf) {
412 #ifndef __linux__
413   abuf_puts(abuf, "error: Gateway mode is only supported on Linux\n");
414 #else /* __linux__ */
415   static const char *fmth = "%-6s %-45s %-15s %-6s %-9s %-9s %-7s %-4s %s\n";
416   static const char *fmtv = "%c%c%-4s %-45s %-15s %-6u %-9u %-9u %-7s %-4s %s\n";
417
418   static const char IPV4[] = "ipv4";
419   static const char IPV4_NAT[] = "ipv4(n)";
420   static const char IPV6[] = "ipv6";
421   static const char NONE[] = "-";
422
423   struct gateway_entry *gw;
424   struct gateway_entry *current_gw_4 = olsr_get_inet_gateway(false);
425   struct gateway_entry *current_gw_6 = olsr_get_inet_gateway(true);
426
427   abuf_puts(abuf, "Table: Gateways\n");
428   abuf_appendf(abuf, fmth, "Status", "Gateway IP", "ETX", "Hopcnt", "Uplink", "Downlnk", "IPv4", "IPv6", "Prefix");
429
430   OLSR_FOR_ALL_GATEWAY_ENTRIES(gw) {
431     char v4, v6;
432     const char *v4type, *v6type;
433     struct ipaddr_str originatorbuf;
434     struct lqtextbuffer lqbuf;
435     struct tc_entry *tc = olsr_lookup_tc_entry(&gw->originator);
436
437     if (current_gw_4 && (gw == current_gw_4)) {
438       v4 = 's';
439     } else if (isGwSelectable(gw, false)) {
440       v4 = 'u';
441     } else {
442       v4 = '-';
443     }
444
445     if (current_gw_6 && (gw == current_gw_6)) {
446       v6 = 's';
447     } else if (isGwSelectable(gw, true)) {
448       v6 = 'u';
449     } else {
450       v6 = '-';
451     }
452
453     if (gw->ipv4) {
454       v4type = gw->ipv4nat ? IPV4_NAT : IPV4;
455     } else {
456       v4type = NONE;
457     }
458     if (gw->ipv6) {
459       v6type = IPV6;
460     } else {
461       v6type = NONE;
462     }
463
464     abuf_appendf(abuf, fmtv, //
465       v4, //
466       v6, //
467       "", //
468       olsr_ip_to_string(&originatorbuf, &gw->originator), //
469       get_linkcost_text(!tc ? ROUTE_COST_BROKEN : tc->path_cost, true, &lqbuf), //
470       !tc ? 0 : tc->hops, //
471       gw->uplink, //
472       gw->downlink, //
473       v4type, //
474       v6type, //
475       !gw->external_prefix.prefix_len ? NONE : olsr_ip_prefix_to_string(&gw->external_prefix));
476   } OLSR_FOR_ALL_GATEWAY_ENTRIES_END (gw)
477 #endif /* __linux__ */
478   abuf_puts(abuf, "\n");
479 }
480
481 #ifdef __linux__
482
483 /** interface names for smart gateway tunnel interfaces, IPv4 */
484 extern struct interfaceName * sgwTunnel4InterfaceNames;
485
486 /** interface names for smart gateway tunnel interfaces, IPv6 */
487 extern struct interfaceName * sgwTunnel6InterfaceNames;
488
489 /**
490  * Construct the sgw table for a given ip version
491  *
492  * @param abuf the string buffer
493  * @param ipv6 true for IPv6, false for IPv4
494  * @param fmtv the format for printing
495  */
496 static void sgw_ipvx(struct autobuf *abuf, bool ipv6, const char * fmth, const char * fmtv) {
497   struct interfaceName * sgwTunnelInterfaceNames = !ipv6 ? sgwTunnel4InterfaceNames : sgwTunnel6InterfaceNames;
498
499   abuf_appendf(abuf, "Table: Smart Gateway IPv%d\n", ipv6 ? 6 : 4);
500   abuf_appendf(abuf, fmth, " ", "Originator", "Prefix", "Uplink", "Downlink", "PathCost", "IPv4", "IPv4-NAT", "IPv6", "Tunnel-Name", "Destination", "Cost");
501
502   if (olsr_cnf->smart_gw_active && sgwTunnelInterfaceNames) {
503     struct gateway_entry * current_gw = olsr_get_inet_gateway(ipv6);
504
505     int i;
506     for (i = 0; i < olsr_cnf->smart_gw_use_count; i++) {
507       struct interfaceName * node = &sgwTunnelInterfaceNames[i];
508       struct gateway_entry * gw = node->gw;
509
510       if (gw) {
511         struct tc_entry* tc = olsr_lookup_tc_entry(&gw->originator);
512
513         struct ipaddr_str originator;
514         struct ipaddr_str prefix;
515         char prefixAndMask[INET6_ADDRSTRLEN * 2];
516
517         olsr_ip_to_string(&originator, &gw->originator);
518         olsr_ip_to_string(&prefix, &gw->external_prefix.prefix);
519
520         if (!ipv6) {
521           union olsr_ip_addr netmask = { { 0 } };
522           struct ipaddr_str prefixMask;
523           prefix_to_netmask((uint8_t *) &netmask, sizeof(netmask.v4), gw->external_prefix.prefix_len);
524           olsr_ip_to_string(&prefixMask, &netmask);
525           snprintf(prefixAndMask, sizeof(prefixAndMask), "%s/%s", prefix.buf, prefixMask.buf);
526         } else {
527           snprintf(prefixAndMask, sizeof(prefixAndMask), "%s/%d", prefix.buf, gw->external_prefix.prefix_len);
528         }
529
530         abuf_appendf(abuf, fmtv, //
531           (current_gw && (current_gw == gw)) ? "*" : " ", // selected
532           originator.buf, // Originator
533           prefixAndMask, // 4: IP/Mask, 6: IP/Length
534           gw->uplink, // Uplink
535           gw->downlink, // Downlink
536           !tc ? ROUTE_COST_BROKEN : tc->path_cost, // PathCost
537           gw->ipv4 ? "Y" : "N", // IPv4
538           gw->ipv4nat ? "Y" : "N", // IPv4-NAT
539           gw->ipv6 ? "Y" : "N", // IPv6
540           node->name, // Tunnel-Name
541           originator.buf, // Destination
542           gw->path_cost // Cost
543           );
544       }
545     }
546   }
547   abuf_puts(abuf, "\n");
548 }
549 #endif /* __linux__ */
550
551 void ipc_print_sgw(struct autobuf *abuf) {
552 #ifndef __linux__
553   abuf_puts(abuf, "error: Gateway mode is only supported on Linux\n");
554 #else
555
556   static const char * fmth4 = "%s%-15s %-31s %-9s %-9s %-10s %-4s %-8s %-4s %-15s %-15s %s\n";
557   static const char * fmtv4 = "%s%-15s %-31s %-9u %-9u %-10u %-4s %-8s %-4s %-15s %-15s %lld\n";
558 #if 0
559   static const char * fmth6 = "%s%-45s %-49s %-9s %-9s %-10s %-4s %-8s %-4s %-15s %-45s %s\n";
560   static const char * fmtv6 = "%s%-45s %-49s %-9u %-9u %-10u %-4s %-8s %-4s %-15s %-45s %lld\n";
561 #endif
562
563   sgw_ipvx(abuf, false, fmth4, fmtv4);
564 #if 0
565   sgw_ipvx(abuf, true, fmth6, fmtv6);
566 #endif
567 #endif /* __linux__ */
568 }
569
570 void ipc_print_version(struct autobuf *abuf) {
571   abuf_appendf(abuf, "Version: %s (built on %s on %s)\n", olsrd_version, build_date, build_host);
572   abuf_puts(abuf, "\n");
573 }
574
575 void ipc_print_olsrd_conf(struct autobuf *abuf) {
576   olsrd_write_cnf_autobuf(abuf, olsr_cnf);
577 }
578
579 void ipc_print_interfaces(struct autobuf *abuf) {
580   const struct olsr_if *ifs;
581
582   abuf_puts(abuf, "Table: Interfaces\n");
583   abuf_puts(abuf, "Name\tState\tMTU\tWLAN\tSrc-Adress\tMask\tDst-Adress\n");
584
585   for (ifs = olsr_cnf->interfaces; ifs != NULL ; ifs = ifs->next) {
586     const struct interface_olsr * const rifs = ifs->interf;
587
588     abuf_appendf(abuf, "%s", ifs->name);
589
590     if (!rifs) {
591       abuf_puts(abuf, "\tDOWN\n");
592       continue;
593     }
594     abuf_appendf(abuf, "\tUP\t%d\t%s",
595         rifs->int_mtu,
596         rifs->is_wireless ? "Yes" : "No");
597
598     {
599       struct ipaddr_str addrbuf;
600       struct ipaddr_str maskbuf;
601       struct ipaddr_str bcastbuf;
602
603       if (olsr_cnf->ip_version == AF_INET) {
604         abuf_appendf(abuf, "\t%s\t%s\t%s\n",
605             ip4_to_string(&addrbuf, rifs->int_addr.sin_addr),
606             ip4_to_string(&maskbuf, rifs->int_netmask.sin_addr),
607             ip4_to_string(&bcastbuf, rifs->int_broadaddr.sin_addr));
608       } else {
609         abuf_appendf(abuf, "\t%s\t\t%s\n",
610             ip6_to_string(&addrbuf, &rifs->int6_addr.sin6_addr),
611             ip6_to_string(&bcastbuf, &rifs->int6_multaddr.sin6_addr));
612       }
613     }
614   }
615   abuf_puts(abuf, "\n");
616 }
617
618 void ipc_print_twohop(struct autobuf *abuf) {
619   ipc_print_neighbors_internal(abuf, true);
620 }