2 * Multicast Traceroute for FRRouting
3 * Copyright (C) 2018 Mladen Sablic
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; see the file COPYING; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26 #include "pim_igmp_mtrace.h"
30 #include "mtracebis_routeget.h"
32 #include <sys/select.h>
33 #include <netinet/in.h>
34 #include <arpa/inet.h>
36 #include <sys/types.h>
47 #define MTRACEBIS_VERSION "0.1"
48 #define MTRACE_TIMEOUT (5)
50 #define IP_HDR_LEN (sizeof(struct ip))
52 #define MTRACE_BUF_LEN (MTRACE_HDR_SIZE + (MTRACE_MAX_HOPS * MTRACE_RSP_SIZE))
53 #define IP_AND_MTRACE_BUF_LEN (IP_HDR_LEN + IP_RA_LEN + MTRACE_BUF_LEN)
55 static const char *progname
;
56 static void usage(void)
58 fprintf(stderr
, "Usage : %s <multicast source> [<multicast group>]\n",
61 static void version(void)
63 fprintf(stderr
, "%s %s\n", progname
, MTRACEBIS_VERSION
);
66 static void print_host(struct in_addr addr
)
70 h
= gethostbyaddr(&addr
, sizeof(addr
), AF_INET
);
74 printf("%s", h
->h_name
);
75 printf(" (%s) ", inet_ntoa(addr
));
78 static void print_line_no(int i
)
83 static const char *rtg_proto_str(enum mtrace_rtg_proto proto
)
90 case MTRACE_RTG_PROTO_DVMRP
:
92 case MTRACE_RTG_PROTO_MOSPF
:
94 case MTRACE_RTG_PROTO_PIM
:
96 case MTRACE_RTG_PROTO_CBT
:
98 case MTRACE_RTG_PROTO_PIM_SPECIAL
:
100 case MTRACE_RTG_PROTO_PIM_STATIC
:
102 case MTRACE_RTG_PROTO_DVMRP_STATIC
:
103 return "DVMRP static";
104 case MTRACE_RTG_PROTO_PIM_MBGP
:
106 case MTRACE_RTG_PROTO_CBT_SPECIAL
:
107 return "CBT special";
108 case MTRACE_RTG_PROTO_CBT_STATIC
:
110 case MTRACE_RTG_PROTO_PIM_ASSERT
:
113 sprintf(buf
, "unknown protocol (%d)", proto
);
118 static void print_rtg_proto(uint32_t rtg_proto
)
120 printf("%s", rtg_proto_str(rtg_proto
));
123 static void print_fwd_ttl(uint32_t fwd_ttl
)
125 printf("thresh^ %d", fwd_ttl
);
128 static const char *fwd_code_str(enum mtrace_fwd_code code
)
135 case MTRACE_FWD_CODE_NO_ERROR
:
137 case MTRACE_FWD_CODE_WRONG_IF
:
138 return "wrong interface";
139 case MTRACE_FWD_CODE_PRUNE_SENT
:
141 case MTRACE_FWD_CODE_PRUNE_RCVD
:
142 return "prune received";
143 case MTRACE_FWD_CODE_SCOPED
:
145 case MTRACE_FWD_CODE_NO_ROUTE
:
147 case MTRACE_FWD_CODE_WRONG_LAST_HOP
:
148 return "wrong last hop";
149 case MTRACE_FWD_CODE_NOT_FORWARDING
:
150 return "not forwarding";
151 case MTRACE_FWD_CODE_REACHED_RP
:
153 case MTRACE_FWD_CODE_RPF_IF
:
154 return "RPF interface";
155 case MTRACE_FWD_CODE_NO_MULTICAST
:
156 return "no multicast";
157 case MTRACE_FWD_CODE_INFO_HIDDEN
:
158 return "info hidden";
159 case MTRACE_FWD_CODE_NO_SPACE
:
161 case MTRACE_FWD_CODE_OLD_ROUTER
:
163 case MTRACE_FWD_CODE_ADMIN_PROHIB
:
164 return "admin. prohib.";
166 sprintf(buf
, "unknown fwd. code (%d)", code
);
171 static void print_fwd_code(uint32_t fwd_code
)
173 printf("%s", fwd_code_str(fwd_code
));
176 static void print_rsp(struct igmp_mtrace_rsp
*rsp
)
178 print_host(rsp
->outgoing
);
179 if (rsp
->fwd_code
== 0 || rsp
->fwd_code
== MTRACE_FWD_CODE_REACHED_RP
) {
180 print_rtg_proto(rsp
->rtg_proto
);
182 if (rsp
->fwd_code
== MTRACE_FWD_CODE_REACHED_RP
)
184 if (rsp
->rtg_proto
== MTRACE_RTG_PROTO_PIM
) {
185 switch (rsp
->src_mask
) {
186 case MTRACE_SRC_MASK_GROUP
:
189 case MTRACE_SRC_MASK_SOURCE
:
194 print_fwd_ttl(rsp
->fwd_ttl
);
196 print_fwd_code(rsp
->fwd_code
);
201 static void print_dest(struct igmp_mtrace
*mtrace
)
204 print_host(mtrace
->dst_addr
);
208 static void print_summary(struct igmp_mtrace
*mtrace
, int hops
, long msec
)
213 for (i
= 0; i
< hops
; i
++)
214 t
+= mtrace
->rsp
[i
].fwd_ttl
;
216 printf("Round trip time %ld ms; total ttl of %d required.\n", msec
, t
);
219 static void print_responses(struct igmp_mtrace
*mtrace
, int hops
, long msec
)
225 for (i
= 0; i
< hops
; i
++) {
226 print_line_no(i
+ 1);
227 print_rsp(&mtrace
->rsp
[i
]);
229 print_summary(mtrace
, hops
, msec
);
232 static int send_query(int fd
, struct in_addr to_addr
,
233 struct igmp_mtrace
*mtrace
)
235 struct sockaddr_in to
;
239 memset(&to
, 0, sizeof(to
));
240 to
.sin_family
= AF_INET
;
241 to
.sin_addr
= to_addr
;
244 sent
= sendto(fd
, (char *)mtrace
, sizeof(*mtrace
), MSG_DONTWAIT
,
245 (struct sockaddr
*)&to
, tolen
);
252 static void print_query(struct igmp_mtrace
*mtrace
)
254 char src_str
[INET_ADDRSTRLEN
];
255 char dst_str
[INET_ADDRSTRLEN
];
256 char grp_str
[INET_ADDRSTRLEN
];
258 printf("* Mtrace from %s to %s via group %s\n",
259 inet_ntop(AF_INET
, &mtrace
->src_addr
, src_str
, sizeof(src_str
)),
260 inet_ntop(AF_INET
, &mtrace
->dst_addr
, dst_str
, sizeof(dst_str
)),
261 inet_ntop(AF_INET
, &mtrace
->grp_addr
, grp_str
, sizeof(grp_str
)));
264 static int recv_response(int fd
, int *hops
, struct igmp_mtrace
*mtracer
)
267 char mtrace_buf
[IP_AND_MTRACE_BUF_LEN
];
269 struct igmp_mtrace
*mtrace
;
276 recvd
= recvfrom(fd
, mtrace_buf
, IP_AND_MTRACE_BUF_LEN
, 0, NULL
, 0);
279 fprintf(stderr
, "recvfrom error: %s\n", strerror(errno
));
283 if (recvd
< (int)sizeof(struct ip
)) {
284 fprintf(stderr
, "no ip header\n");
288 ip
= (struct ip
*)mtrace_buf
;
291 fprintf(stderr
, "IP not version 4\n");
298 if (sum
!= in_cksum(ip
, ip
->ip_hl
* 4))
301 /* Header overflow check */
302 mtrace_off
= 4 * ip
->ip_hl
;
303 if (mtrace_off
> MTRACE_BUF_LEN
)
306 /* Underflow/overflow check */
307 ip_len
= ntohs(ip
->ip_len
);
308 if (ip_len
< mtrace_off
|| ip_len
< MTRACE_HDR_SIZE
309 || ip_len
> MTRACE_BUF_LEN
)
312 mtrace_len
= ip_len
- mtrace_off
;
313 mtrace
= (struct igmp_mtrace
*)(mtrace_buf
+ mtrace_off
);
315 sum
= mtrace
->checksum
;
316 mtrace
->checksum
= 0;
317 if (sum
!= in_cksum(mtrace
, mtrace_len
)) {
318 fprintf(stderr
, "mtrace checksum wrong\n");
322 if (mtrace
->type
!= PIM_IGMP_MTRACE_RESPONSE
)
326 responses
= mtrace_len
- sizeof(struct igmp_mtrace
);
327 responses
/= sizeof(struct igmp_mtrace_rsp
);
329 if (responses
> MTRACE_MAX_HOPS
) {
330 fprintf(stderr
, "mtrace too large\n");
338 memcpy(mtracer
, mtrace
, mtrace_len
);
343 static int wait_for_response(int fd
, int *hops
, struct igmp_mtrace
*mtrace
,
347 struct timeval timeout
;
349 long msec
, rmsec
, tmsec
;
352 FD_SET(fd
, &readfds
);
354 memset(&timeout
, 0, sizeof(timeout
));
356 timeout
.tv_sec
= MTRACE_TIMEOUT
;
358 tmsec
= timeout
.tv_sec
* 1000 + timeout
.tv_usec
/ 1000;
360 ret
= select(fd
+ 1, &readfds
, NULL
, NULL
, &timeout
);
363 rmsec
= timeout
.tv_sec
* 1000 + timeout
.tv_usec
/ 1000;
364 msec
= tmsec
- rmsec
;
365 } while (recv_response(fd
, hops
, mtrace
) != 0);
373 static bool check_end(struct igmp_mtrace
*mtrace
, int hops
)
375 return mtrace
->src_addr
.s_addr
== mtrace
->rsp
[hops
- 1].prev_hop
.s_addr
;
378 int main(int argc
, char *const argv
[])
380 struct in_addr mc_source
;
381 struct in_addr mc_group
;
382 struct in_addr iface_addr
;
383 struct in_addr gw_addr
;
384 struct in_addr mtrace_addr
;
385 struct igmp_mtrace mtrace
;
386 struct igmp_mtrace
*mtracep
;
399 char ifname
[IF_NAMESIZE
];
400 char mbuf
[MTRACE_BUF_LEN
];
403 mtrace_addr
.s_addr
= inet_addr("224.0.1.32");
405 uid_t uid
= getuid();
408 printf("must run as root\n");
413 progname
= "mtracebis";
417 if (argc
!= 2 && argc
!= 3) {
423 static struct option long_options
[] = {
424 {"help", no_argument
, 0, 'h'},
425 {"version", no_argument
, 0, 'v'},
427 int option_index
= 0;
429 c
= getopt_long(argc
, argv
, "vh", long_options
, &option_index
);
446 if (inet_pton(AF_INET
, argv
[1], &mc_source
) != 1) {
448 fprintf(stderr
, "%s: %s is not a valid IPv4 address\n", argv
[0],
457 if (inet_pton(AF_INET
, argv
[2], &mc_group
) != 1)
459 if (!not_group
&& !IPV4_CLASS_DE(ntohl(mc_group
.s_addr
)))
465 fprintf(stderr
, "%s: %s is not a valid IPv4 group address\n",
470 ifindex
= routeget(mc_source
, &iface_addr
, &gw_addr
);
472 fprintf(stderr
, "%s: failed to get route to source %s\n",
477 if (if_indextoname(ifindex
, ifname
) == NULL
) {
478 fprintf(stderr
, "%s: if_indextoname error: %s\n", argv
[0],
483 /* zero mtrace struct */
484 memset((char *)&mtrace
, 0, sizeof(mtrace
));
487 mtrace
.type
= PIM_IGMP_MTRACE_QUERY_REQUEST
;
490 mtrace
.grp_addr
= mc_group
;
491 mtrace
.src_addr
= mc_source
;
492 mtrace
.dst_addr
= iface_addr
;
493 mtrace
.rsp_addr
= unicast
? iface_addr
: mtrace_addr
;
494 mtrace
.rsp_ttl
= ttl
;
495 mtrace
.qry_id
= 0xffffff & time(NULL
);
497 mtrace
.checksum
= in_cksum(&mtrace
, sizeof(mtrace
));
499 fd
= socket(AF_INET
, SOCK_RAW
, IPPROTO_IGMP
);
502 fprintf(stderr
, "%s: socket error: %s\n", argv
[0],
507 ret
= setsockopt(fd
, SOL_SOCKET
, SO_BINDTODEVICE
, ifname
,
511 fprintf(stderr
, "%s: setsockopt error: %s\n", argv
[0],
517 print_query(&mtrace
);
518 if (send_query(fd
, gw_addr
, &mtrace
) < 0) {
519 fprintf(stderr
, "%s: sendto error: %s\n", argv
[0],
524 printf("Querying full reverse path...\n");
525 mtracep
= (struct igmp_mtrace
*)mbuf
;
526 ret
= wait_for_response(fd
, &rhops
, mtracep
, &msec
);
528 print_responses(mtracep
, rhops
, msec
);
533 fprintf(stderr
, "%s: select error: %s\n", argv
[0],
539 printf("switching to hop-by-hop:\n");
541 for (i
= 1; i
< maxhops
; i
++) {
544 for (j
= 0; j
< perhop
; j
++) {
547 mtrace
.checksum
= in_cksum(&mtrace
, sizeof(mtrace
));
548 if (send_query(fd
, gw_addr
, &mtrace
) < 0) {
549 fprintf(stderr
, "%s: sendto error: %s\n",
550 argv
[0], strerror(errno
));
554 ret
= wait_for_response(fd
, &rhops
, mtracep
, &msec
);
556 if (check_end(mtracep
, rhops
)) {
557 print_rsp(&mtracep
->rsp
[rhops
- 1]);
558 print_summary(mtracep
, rhops
, msec
);
563 printf(" * ...giving up.\n");
567 print_rsp(&mtracep
->rsp
[rhops
- 1]);
581 #else /* __linux__ */
586 int main(int argc
, char *argv
[])
588 printf("%s implemented only for GNU/Linux\n", argv
[0]);
592 #endif /* __linux__ */