txtinfo: show the scaled gw 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 "gateway_costs.h"
55 #include "olsrd_plugin.h"
56 #include "../../info/info_types.h"
57 #include "../../info/http_headers.h"
58 #include "gateway_default_handler.h"
59
60 bool isCommand(const char *str, unsigned long long siw) {
61   const char * cmd;
62   switch (siw) {
63     case SIW_OLSRD_CONF:
64       cmd = "/con";
65       break;
66
67     case SIW_ALL:
68       cmd = "/all";
69       break;
70
71     case SIW_RUNTIME_ALL:
72       cmd = "/runtime";
73       break;
74
75     case SIW_STARTUP_ALL:
76       cmd = "/startup";
77       break;
78
79     case SIW_NEIGHBORS:
80       cmd = "/nei";
81       break;
82
83     case SIW_LINKS:
84       cmd = "/lin";
85       break;
86
87     case SIW_ROUTES:
88       cmd = "/rou";
89       break;
90
91     case SIW_HNA:
92       cmd = "/hna";
93       break;
94
95     case SIW_MID:
96       cmd = "/mid";
97       break;
98
99     case SIW_TOPOLOGY:
100       cmd = "/top";
101       break;
102
103     case SIW_GATEWAYS:
104       cmd = "/gat";
105       break;
106
107     case SIW_INTERFACES:
108       cmd = "/int";
109       break;
110
111     case SIW_2HOP:
112       cmd = "/2ho";
113       break;
114
115     case SIW_SGW:
116       cmd = "/sgw";
117       break;
118
119     case SIW_VERSION:
120       cmd = "/ver";
121       break;
122
123     case SIW_CONFIG:
124       cmd = "/config";
125       return false; /* not implemented */
126
127     case SIW_PLUGINS:
128       cmd = "/plugins";
129       return false; /* not implemented */
130
131     case SIW_NEIGHBORS_FREIFUNK:
132       cmd = "/neighbours";
133       break;
134
135     default:
136       return false;
137   }
138
139   return !strcmp(str, cmd);
140 }
141
142 void output_error(struct autobuf *abuf, unsigned int status, const char * req, bool http_headers) {
143   if (http_headers || (status == INFO_HTTP_OK)) {
144     return;
145   }
146
147   switch (status) {
148     case INFO_HTTP_NOTFOUND:
149       abuf_appendf(abuf, "error: Invalid request '%s'\n", req);
150       break;
151
152     case INFO_HTTP_NOCONTENT:
153       /* wget can't handle output of zero length */
154       abuf_puts(abuf, "\n");
155       break;
156
157     default:
158       abuf_appendf(abuf, "error: Unknown status %d for request '%s'\n", status, req);
159       return;
160   }
161 }
162
163 static void ipc_print_neighbors_internal(struct autobuf *abuf, bool list_2hop) {
164   struct ipaddr_str neighAddrBuf;
165   struct neighbor_entry *neigh;
166   struct neighbor_2_list_entry *list_2;
167   int thop_cnt;
168
169   const char * field;
170   if (list_2hop) {
171     field = "(2-hop address)+";
172   } else {
173     field = "2-hop count";
174   }
175
176   abuf_puts(abuf, "Table: Neighbors\n");
177   abuf_appendf(abuf, "IP address\tSYM\tMPR\tMPRS\tWill.\t%s\n", field);
178
179   /* Neighbors */
180   OLSR_FOR_ALL_NBR_ENTRIES(neigh) {
181     abuf_appendf(abuf, "%s\t%s\t%s\t%s\t%d",
182       olsr_ip_to_string(&neighAddrBuf, &neigh->neighbor_main_addr),
183       (neigh->status == SYM) ? "YES" : "NO",
184       neigh->is_mpr ? "YES" : "NO",
185       olsr_lookup_mprs_set(&neigh->neighbor_main_addr) ? "YES" : "NO",
186       neigh->willingness);
187     thop_cnt = 0;
188
189     for (list_2 = neigh->neighbor_2_list.next; list_2 != &neigh->neighbor_2_list; list_2 = list_2->next) {
190       if (list_2hop) {
191         if (list_2->neighbor_2) {
192           abuf_appendf(abuf, "\t%s", olsr_ip_to_string(&neighAddrBuf, &list_2->neighbor_2->neighbor_2_addr));
193         }
194       } else {
195         thop_cnt++;
196       }
197     }
198
199     if (!list_2hop) {
200       abuf_appendf(abuf, "\t%d", thop_cnt);
201     }
202     abuf_puts(abuf, "\n");
203   } OLSR_FOR_ALL_NBR_ENTRIES_END(neigh);
204   abuf_puts(abuf, "\n");
205 }
206
207 void ipc_print_neighbors(struct autobuf *abuf) {
208   ipc_print_neighbors_internal(abuf, false);
209 }
210
211 void ipc_print_links(struct autobuf *abuf) {
212   struct link_entry *my_link;
213
214   const char * field;
215   if (vtime) {
216     field = "VTime";
217   } else {
218     field = "Hyst.";
219   }
220
221   abuf_puts(abuf, "Table: Links\n");
222   abuf_appendf(abuf, "Local IP\tRemote IP\t%s\tLQ\tNLQ\tCost\n", field);
223
224   /* Link set */
225   OLSR_FOR_ALL_LINK_ENTRIES(my_link) {
226     struct ipaddr_str localAddr;
227     struct ipaddr_str remoteAddr;
228     struct lqtextbuffer lqbuffer;
229     struct lqtextbuffer costbuffer;
230     unsigned int diffI = 0;
231     unsigned int diffF = 0;
232
233     if (vtime) {
234       unsigned int diff = my_link->link_timer ? (unsigned int) (my_link->link_timer->timer_clock - now_times) : 0;
235       diffI = diff / 1000;
236       diffF = diff % 1000;
237     }
238
239     abuf_appendf(abuf, "%s\t%s\t%u.%03u\t%s\t%s\n",
240       olsr_ip_to_string(&localAddr, &my_link->local_iface_addr),
241       olsr_ip_to_string(&remoteAddr, &my_link->neighbor_iface_addr),
242       diffI,
243       diffF,
244       get_link_entry_text(my_link, '\t', &lqbuffer),
245       get_linkcost_text(my_link->linkcost, false, &costbuffer));
246   } OLSR_FOR_ALL_LINK_ENTRIES_END(my_link);
247   abuf_puts(abuf, "\n");
248 }
249
250 void ipc_print_routes(struct autobuf *abuf) {
251   struct rt_entry *rt;
252
253   abuf_puts(abuf, "Table: Routes\n");
254   abuf_puts(abuf, "Destination\tGateway IP\tMetric\tETX\tInterface\n");
255
256   /* Walk the route table */
257   OLSR_FOR_ALL_RT_ENTRIES(rt) {
258     struct ipaddr_str dstAddr;
259     struct ipaddr_str nexthopAddr;
260     struct lqtextbuffer costbuffer;
261
262     if (rt->rt_best) {
263       abuf_appendf(abuf, "%s/%d\t%s\t%d\t%s\t%s\t\n",
264         olsr_ip_to_string(&dstAddr, &rt->rt_dst.prefix),
265         rt->rt_dst.prefix_len,
266         olsr_ip_to_string(&nexthopAddr, &rt->rt_best->rtp_nexthop.gateway),
267         rt->rt_best->rtp_metric.hops,
268         get_linkcost_text(rt->rt_best->rtp_metric.cost, true, &costbuffer),
269         if_ifwithindex_name(rt->rt_best->rtp_nexthop.iif_index));
270     }
271   } OLSR_FOR_ALL_RT_ENTRIES_END(rt);
272   abuf_puts(abuf, "\n");
273 }
274
275 void ipc_print_topology(struct autobuf *abuf) {
276   struct tc_entry *tc;
277
278   const char * field;
279   if (vtime) {
280     field = "\tVTime";
281   } else {
282     field = "";
283   }
284
285   abuf_puts(abuf, "Table: Topology\n");
286   abuf_appendf(abuf, "Dest. IP\tLast hop IP\tLQ\tNLQ\tCost%s\n", field);
287
288   /* Topology */
289   OLSR_FOR_ALL_TC_ENTRIES(tc) {
290     struct tc_edge_entry *tc_edge;
291     OLSR_FOR_ALL_TC_EDGE_ENTRIES(tc, tc_edge) {
292       if (tc_edge->edge_inv) {
293         struct ipaddr_str dstAddr;
294         struct ipaddr_str lastHopAddr;
295         struct lqtextbuffer lqbuffer;
296         struct lqtextbuffer costbuffer;
297
298         abuf_appendf(abuf, "%s\t%s\t%s\t%s",
299           olsr_ip_to_string(&dstAddr, &tc_edge->T_dest_addr),
300           olsr_ip_to_string(&lastHopAddr, &tc->addr),
301           get_tc_edge_entry_text(tc_edge, '\t', &lqbuffer),
302           get_linkcost_text(tc_edge->cost, false, &costbuffer));
303
304         if (vtime) {
305           unsigned int diff = (unsigned int) (tc->validity_timer ? (tc->validity_timer->timer_clock - now_times) : 0);
306           abuf_appendf(abuf, "\t%u.%03u", diff / 1000, diff % 1000);
307         }
308
309         abuf_puts(abuf, "\n");
310       }
311     } OLSR_FOR_ALL_TC_EDGE_ENTRIES_END(tc, tc_edge);
312   } OLSR_FOR_ALL_TC_ENTRIES_END(tc);
313   abuf_puts(abuf, "\n");
314 }
315
316 void ipc_print_hna(struct autobuf *abuf) {
317   struct ip_prefix_list *hna;
318   struct hna_entry *tmp_hna;
319   struct ipaddr_str prefixbuf;
320   struct ipaddr_str gwaddrbuf;
321
322   const char * field;
323   if (vtime) {
324     field = "\tVTime";
325   } else {
326     field = "";
327   }
328
329   abuf_puts(abuf, "Table: HNA\n");
330   abuf_appendf(abuf, "Destination\tGateway%s\n", field);
331
332   /* Announced HNA entries */
333   for (hna = olsr_cnf->hna_entries; hna != NULL ; hna = hna->next) {
334     abuf_appendf(abuf, "%s/%d\t%s",
335       olsr_ip_to_string(&prefixbuf, &hna->net.prefix),
336       hna->net.prefix_len,
337       olsr_ip_to_string(&gwaddrbuf, &olsr_cnf->main_addr));
338
339       if (vtime) {
340         abuf_appendf(abuf, "\t%u.%03u", 0, 0);
341       }
342       abuf_puts(abuf, "\n");
343   }
344
345   /* HNA entries */
346   OLSR_FOR_ALL_HNA_ENTRIES(tmp_hna) {
347     struct hna_net *tmp_net;
348
349     /* Check all networks */
350     for (tmp_net = tmp_hna->networks.next; tmp_net != &tmp_hna->networks; tmp_net = tmp_net->next) {
351       abuf_appendf(abuf, "%s/%d\t%s",
352         olsr_ip_to_string(&prefixbuf, &tmp_net->hna_prefix.prefix),
353         tmp_net->hna_prefix.prefix_len,
354         olsr_ip_to_string(&gwaddrbuf, &tmp_hna->A_gateway_addr));
355
356       if (vtime) {
357         unsigned int diff = tmp_net->hna_net_timer ? (unsigned int) (tmp_net->hna_net_timer->timer_clock - now_times) : 0;
358         abuf_appendf(abuf, "\t%u.%03u", diff / 1000, diff % 1000);
359       }
360       abuf_puts(abuf, "\n");
361     }
362   } OLSR_FOR_ALL_HNA_ENTRIES_END(tmp_hna);
363   abuf_puts(abuf, "\n");
364 }
365
366 void ipc_print_mid(struct autobuf *abuf) {
367   int idx;
368
369   const char * field;
370   if (vtime) {
371     field = ":VTime";
372   } else {
373     field = "";
374   }
375
376   abuf_puts(abuf, "Table: MID\n");
377   abuf_appendf(abuf, "IP address\t(Alias%s)+\n", field);
378
379   /* MID */
380   for (idx = 0; idx < HASHSIZE; idx++) {
381     struct mid_entry *entry = mid_set[idx].next;
382
383     while (entry && (entry != &mid_set[idx])) {
384       struct mid_address *alias = entry->aliases;
385       struct ipaddr_str ipAddr;
386
387       abuf_puts(abuf, olsr_ip_to_string(&ipAddr, &entry->main_addr));
388       abuf_puts(abuf, "\t");
389
390       while (alias) {
391         struct ipaddr_str buf2;
392
393         abuf_appendf(abuf, "\t%s", olsr_ip_to_string(&buf2, &alias->alias));
394
395         if (vtime) {
396           unsigned int diff = (unsigned int) (alias->vtime - now_times);
397           abuf_appendf(abuf, ":%u.%03u", diff / 1000, diff % 1000);
398         }
399
400         alias = alias->next_alias;
401       }
402       entry = entry->next;
403       abuf_puts(abuf, "\n");
404     }
405   }
406   abuf_puts(abuf, "\n");
407 }
408
409 void ipc_print_gateways(struct autobuf *abuf) {
410 #ifndef __linux__
411   abuf_puts(abuf, "error: Gateway mode is only supported on Linux\n");
412 #else /* __linux__ */
413   static const char *fmth = "%-6s %-45s %-15s %-6s %-9s %-9s %-7s %-4s %s\n";
414   static const char *fmtv = "%c%c%-4s %-45s %-15s %-6u %-9u %-9u %-7s %-4s %s\n";
415
416   static const char IPV4[] = "ipv4";
417   static const char IPV4_NAT[] = "ipv4(n)";
418   static const char IPV6[] = "ipv6";
419   static const char NONE[] = "-";
420
421   struct gateway_entry *gw;
422   struct gateway_entry *current_gw_4 = olsr_get_inet_gateway(false);
423   struct gateway_entry *current_gw_6 = olsr_get_inet_gateway(true);
424
425   abuf_puts(abuf, "Table: Gateways\n");
426   abuf_appendf(abuf, fmth, "Status", "Gateway IP", "ETX", "Hopcnt", "Uplink", "Downlnk", "IPv4", "IPv6", "Prefix");
427
428   OLSR_FOR_ALL_GATEWAY_ENTRIES(gw) {
429     char v4, v6;
430     const char *v4type, *v6type;
431     struct ipaddr_str originatorbuf;
432     struct lqtextbuffer lqbuf;
433     struct tc_entry *tc = olsr_lookup_tc_entry(&gw->originator);
434
435     if (current_gw_4 && (gw == current_gw_4)) {
436       v4 = 's';
437     } else if (isGwSelectable(gw, false)) {
438       v4 = 'u';
439     } else {
440       v4 = '-';
441     }
442
443     if (current_gw_6 && (gw == current_gw_6)) {
444       v6 = 's';
445     } else if (isGwSelectable(gw, true)) {
446       v6 = 'u';
447     } else {
448       v6 = '-';
449     }
450
451     if (gw->ipv4) {
452       v4type = gw->ipv4nat ? IPV4_NAT : IPV4;
453     } else {
454       v4type = NONE;
455     }
456     if (gw->ipv6) {
457       v6type = IPV6;
458     } else {
459       v6type = NONE;
460     }
461
462     abuf_appendf(abuf, fmtv, //
463       v4, //
464       v6, //
465       "", //
466       olsr_ip_to_string(&originatorbuf, &gw->originator), //
467       get_linkcost_text(!tc ? ROUTE_COST_BROKEN : tc->path_cost, true, &lqbuf), //
468       !tc ? 0 : tc->hops, //
469       gw->uplink, //
470       gw->downlink, //
471       v4type, //
472       v6type, //
473       !gw->external_prefix.prefix_len ? NONE : olsr_ip_prefix_to_string(&gw->external_prefix));
474   } OLSR_FOR_ALL_GATEWAY_ENTRIES_END (gw)
475 #endif /* __linux__ */
476   abuf_puts(abuf, "\n");
477 }
478
479 #ifdef __linux__
480
481 /** interface names for smart gateway tunnel interfaces, IPv4 */
482 extern struct interfaceName * sgwTunnel4InterfaceNames;
483
484 /** interface names for smart gateway tunnel interfaces, IPv6 */
485 extern struct interfaceName * sgwTunnel6InterfaceNames;
486
487 /**
488  * Construct the sgw table for a given ip version
489  *
490  * @param abuf the string buffer
491  * @param ipv6 true for IPv6, false for IPv4
492  * @param fmtv the format for printing
493  */
494 static void sgw_ipvx(struct autobuf *abuf, bool ipv6, const char * fmth, const char * fmtv) {
495   struct interfaceName * sgwTunnelInterfaceNames = !ipv6 ? sgwTunnel4InterfaceNames : sgwTunnel6InterfaceNames;
496   struct gwtextbuffer gwbuf;
497
498   abuf_appendf(abuf, "Table: Smart Gateway IPv%d\n", ipv6 ? 6 : 4);
499   abuf_appendf(abuf, fmth, " ", "Originator", "Prefix", "Uplink", "Downlink", "PathCost", "IPv4", "IPv4-NAT", "IPv6", "Tunnel-Name", "Destination", "Cost");
500
501   if (olsr_cnf->smart_gw_active && sgwTunnelInterfaceNames) {
502     struct gateway_entry * current_gw = olsr_get_inet_gateway(ipv6);
503
504     int i;
505     for (i = 0; i < olsr_cnf->smart_gw_use_count; i++) {
506       struct interfaceName * node = &sgwTunnelInterfaceNames[i];
507       struct gateway_entry * gw = node->gw;
508
509       if (gw) {
510         struct lqtextbuffer lqbuf;
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           get_linkcost_text(!tc ? ROUTE_COST_BROKEN : tc->path_cost, true, &lqbuf), // 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           get_gwcost_text(gw->path_cost, &gwbuf) // 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 %-10s %-4s %-8s %-4s %-15s %-15s %s\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 %-10s %-4s %-8s %-4s %-15s %-45s %s\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 }