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
22 #include "pim_igmp_mtrace.h"
26 #include "mtracebis_routeget.h"
28 #include <sys/select.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
32 #include <sys/types.h>
43 #define MTRACEBIS_VERSION "0.1"
44 #define MTRACE_TIMEOUT (5)
46 #define IP_HDR_LEN (sizeof(struct ip))
48 #define MTRACE_BUF_LEN (MTRACE_HDR_SIZE + (MTRACE_MAX_HOPS * MTRACE_RSP_SIZE))
49 #define IP_AND_MTRACE_BUF_LEN (IP_HDR_LEN + IP_RA_LEN + MTRACE_BUF_LEN)
51 static const char *progname
;
52 static void usage(void)
54 fprintf(stderr
, "Usage : %s <multicast source> [<multicast group>]\n",
57 static void version(void)
59 fprintf(stderr
, "%s %s\n", progname
, MTRACEBIS_VERSION
);
62 static void print_host(struct in_addr addr
)
66 h
= gethostbyaddr(&addr
, sizeof(addr
), AF_INET
);
70 printf("%s", h
->h_name
);
71 printf(" (%s) ", inet_ntoa(addr
));
74 static void print_line_no(int i
)
79 static const char *rtg_proto_str(enum mtrace_rtg_proto proto
)
86 case MTRACE_RTG_PROTO_DVMRP
:
88 case MTRACE_RTG_PROTO_MOSPF
:
90 case MTRACE_RTG_PROTO_PIM
:
92 case MTRACE_RTG_PROTO_CBT
:
94 case MTRACE_RTG_PROTO_PIM_SPECIAL
:
96 case MTRACE_RTG_PROTO_PIM_STATIC
:
98 case MTRACE_RTG_PROTO_DVMRP_STATIC
:
99 return "DVMRP static";
100 case MTRACE_RTG_PROTO_PIM_MBGP
:
102 case MTRACE_RTG_PROTO_CBT_SPECIAL
:
103 return "CBT special";
104 case MTRACE_RTG_PROTO_CBT_STATIC
:
106 case MTRACE_RTG_PROTO_PIM_ASSERT
:
109 sprintf(buf
, "unknown protocol (%d)", proto
);
114 static void print_rtg_proto(uint32_t rtg_proto
)
116 printf("%s", rtg_proto_str(rtg_proto
));
119 static void print_fwd_ttl(uint32_t fwd_ttl
)
121 printf("thresh^ %d", fwd_ttl
);
124 static const char *fwd_code_str(enum mtrace_fwd_code code
)
131 case MTRACE_FWD_CODE_NO_ERROR
:
133 case MTRACE_FWD_CODE_WRONG_IF
:
134 return "wrong interface";
135 case MTRACE_FWD_CODE_PRUNE_SENT
:
137 case MTRACE_FWD_CODE_PRUNE_RCVD
:
138 return "prune received";
139 case MTRACE_FWD_CODE_SCOPED
:
141 case MTRACE_FWD_CODE_NO_ROUTE
:
143 case MTRACE_FWD_CODE_WRONG_LAST_HOP
:
144 return "wrong last hop";
145 case MTRACE_FWD_CODE_NOT_FORWARDING
:
146 return "not forwarding";
147 case MTRACE_FWD_CODE_REACHED_RP
:
149 case MTRACE_FWD_CODE_RPF_IF
:
150 return "RPF interface";
151 case MTRACE_FWD_CODE_NO_MULTICAST
:
152 return "no multicast";
153 case MTRACE_FWD_CODE_INFO_HIDDEN
:
154 return "info hidden";
155 case MTRACE_FWD_CODE_NO_SPACE
:
157 case MTRACE_FWD_CODE_OLD_ROUTER
:
159 case MTRACE_FWD_CODE_ADMIN_PROHIB
:
160 return "admin. prohib.";
162 sprintf(buf
, "unknown fwd. code (%d)", code
);
167 static void print_fwd_code(uint32_t fwd_code
)
169 printf("%s", fwd_code_str(fwd_code
));
172 static void print_rsp(struct igmp_mtrace_rsp
*rsp
)
174 print_host(rsp
->outgoing
);
175 if (rsp
->fwd_code
== 0 || rsp
->fwd_code
== MTRACE_FWD_CODE_REACHED_RP
) {
176 print_rtg_proto(rsp
->rtg_proto
);
178 if (rsp
->fwd_code
== MTRACE_FWD_CODE_REACHED_RP
)
180 if (rsp
->rtg_proto
== MTRACE_RTG_PROTO_PIM
) {
181 switch (rsp
->src_mask
) {
182 case MTRACE_SRC_MASK_GROUP
:
185 case MTRACE_SRC_MASK_SOURCE
:
190 print_fwd_ttl(rsp
->fwd_ttl
);
192 print_fwd_code(rsp
->fwd_code
);
197 static void print_dest(struct igmp_mtrace
*mtrace
)
200 print_host(mtrace
->dst_addr
);
204 static void print_summary(struct igmp_mtrace
*mtrace
, int hops
, long msec
)
209 for (i
= 0; i
< hops
; i
++)
210 t
+= mtrace
->rsp
[i
].fwd_ttl
;
212 printf("Round trip time %ld ms; total ttl of %d required.\n", msec
, t
);
215 static void print_responses(struct igmp_mtrace
*mtrace
, int hops
, long msec
)
221 for (i
= 0; i
< hops
; i
++) {
222 print_line_no(i
+ 1);
223 print_rsp(&mtrace
->rsp
[i
]);
225 print_summary(mtrace
, hops
, msec
);
228 static int send_query(int fd
, struct in_addr to_addr
,
229 struct igmp_mtrace
*mtrace
)
231 struct sockaddr_in to
;
235 memset(&to
, 0, sizeof(to
));
236 to
.sin_family
= AF_INET
;
237 to
.sin_addr
= to_addr
;
240 sent
= sendto(fd
, (char *)mtrace
, sizeof(*mtrace
), MSG_DONTWAIT
,
241 (struct sockaddr
*)&to
, tolen
);
248 static void print_query(struct igmp_mtrace
*mtrace
)
250 char src_str
[INET_ADDRSTRLEN
];
251 char dst_str
[INET_ADDRSTRLEN
];
252 char grp_str
[INET_ADDRSTRLEN
];
254 printf("* Mtrace from %s to %s via group %s\n",
255 inet_ntop(AF_INET
, &mtrace
->src_addr
, src_str
, sizeof(src_str
)),
256 inet_ntop(AF_INET
, &mtrace
->dst_addr
, dst_str
, sizeof(dst_str
)),
257 inet_ntop(AF_INET
, &mtrace
->grp_addr
, grp_str
, sizeof(grp_str
)));
260 static int recv_response(int fd
, int *hops
, struct igmp_mtrace
*mtracer
)
263 char mtrace_buf
[IP_AND_MTRACE_BUF_LEN
];
265 struct igmp_mtrace
*mtrace
;
272 recvd
= recvfrom(fd
, mtrace_buf
, IP_AND_MTRACE_BUF_LEN
, 0, NULL
, 0);
275 fprintf(stderr
, "recvfrom error: %s\n", strerror(errno
));
279 if (recvd
< (int)sizeof(struct ip
)) {
280 fprintf(stderr
, "no ip header\n");
284 ip
= (struct ip
*)mtrace_buf
;
287 fprintf(stderr
, "IP not version 4\n");
294 if (sum
!= in_cksum(ip
, ip
->ip_hl
* 4))
297 /* Header overflow check */
298 mtrace_off
= 4 * ip
->ip_hl
;
299 if (mtrace_off
> MTRACE_BUF_LEN
)
302 /* Underflow/overflow check */
303 ip_len
= ntohs(ip
->ip_len
);
304 if (ip_len
< mtrace_off
|| ip_len
< MTRACE_HDR_SIZE
305 || ip_len
> MTRACE_BUF_LEN
)
308 mtrace_len
= ip_len
- mtrace_off
;
309 mtrace
= (struct igmp_mtrace
*)(mtrace_buf
+ mtrace_off
);
311 sum
= mtrace
->checksum
;
312 mtrace
->checksum
= 0;
313 if (sum
!= in_cksum(mtrace
, mtrace_len
)) {
314 fprintf(stderr
, "mtrace checksum wrong\n");
318 if (mtrace
->type
!= PIM_IGMP_MTRACE_RESPONSE
)
322 responses
= mtrace_len
- sizeof(struct igmp_mtrace
);
323 responses
/= sizeof(struct igmp_mtrace_rsp
);
325 if (responses
> MTRACE_MAX_HOPS
) {
326 fprintf(stderr
, "mtrace too large\n");
334 memcpy(mtracer
, mtrace
, mtrace_len
);
339 static int wait_for_response(int fd
, int *hops
, struct igmp_mtrace
*mtrace
,
343 struct timeval timeout
;
345 long msec
, rmsec
, tmsec
;
348 FD_SET(fd
, &readfds
);
350 memset(&timeout
, 0, sizeof(timeout
));
352 timeout
.tv_sec
= MTRACE_TIMEOUT
;
354 tmsec
= timeout
.tv_sec
* 1000 + timeout
.tv_usec
/ 1000;
356 ret
= select(fd
+ 1, &readfds
, NULL
, NULL
, &timeout
);
359 rmsec
= timeout
.tv_sec
* 1000 + timeout
.tv_usec
/ 1000;
360 msec
= tmsec
- rmsec
;
361 } while (recv_response(fd
, hops
, mtrace
) != 0);
369 static bool check_end(struct igmp_mtrace
*mtrace
, int hops
)
371 return mtrace
->src_addr
.s_addr
== mtrace
->rsp
[hops
- 1].prev_hop
.s_addr
;
374 int main(int argc
, char *const argv
[])
376 struct in_addr mc_source
;
377 struct in_addr mc_group
;
378 struct in_addr iface_addr
;
379 struct in_addr gw_addr
;
380 struct in_addr mtrace_addr
;
381 struct igmp_mtrace mtrace
;
382 struct igmp_mtrace
*mtracep
;
395 char ifname
[IF_NAMESIZE
];
396 char mbuf
[MTRACE_BUF_LEN
];
399 mtrace_addr
.s_addr
= inet_addr("224.0.1.32");
401 uid_t uid
= getuid();
404 printf("must run as root\n");
409 progname
= "mtracebis";
413 if (argc
!= 2 && argc
!= 3) {
419 static struct option long_options
[] = {
420 {"help", no_argument
, 0, 'h'},
421 {"version", no_argument
, 0, 'v'},
423 int option_index
= 0;
425 c
= getopt_long(argc
, argv
, "vh", long_options
, &option_index
);
442 if (inet_pton(AF_INET
, argv
[1], &mc_source
) != 1) {
444 fprintf(stderr
, "%s: %s is not a valid IPv4 address\n", argv
[0],
453 if (inet_pton(AF_INET
, argv
[2], &mc_group
) != 1)
455 if (!not_group
&& !IPV4_CLASS_DE(ntohl(mc_group
.s_addr
)))
461 fprintf(stderr
, "%s: %s is not a valid IPv4 group address\n",
466 ifindex
= routeget(mc_source
, &iface_addr
, &gw_addr
);
468 fprintf(stderr
, "%s: failed to get route to source %s\n",
473 if (if_indextoname(ifindex
, ifname
) == NULL
) {
474 fprintf(stderr
, "%s: if_indextoname error: %s\n", argv
[0],
479 /* zero mtrace struct */
480 memset((char *)&mtrace
, 0, sizeof(mtrace
));
483 mtrace
.type
= PIM_IGMP_MTRACE_QUERY_REQUEST
;
486 mtrace
.grp_addr
= mc_group
;
487 mtrace
.src_addr
= mc_source
;
488 mtrace
.dst_addr
= iface_addr
;
489 mtrace
.rsp_addr
= unicast
? iface_addr
: mtrace_addr
;
490 mtrace
.rsp_ttl
= ttl
;
491 mtrace
.qry_id
= 0xffffff & time(NULL
);
493 mtrace
.checksum
= in_cksum(&mtrace
, sizeof(mtrace
));
495 fd
= socket(AF_INET
, SOCK_RAW
, IPPROTO_IGMP
);
498 fprintf(stderr
, "%s: socket error: %s\n", argv
[0],
503 ret
= setsockopt(fd
, SOL_SOCKET
, SO_BINDTODEVICE
, ifname
,
507 fprintf(stderr
, "%s: setsockopt error: %s\n", argv
[0],
513 print_query(&mtrace
);
514 if (send_query(fd
, gw_addr
, &mtrace
) < 0) {
515 fprintf(stderr
, "%s: sendto error: %s\n", argv
[0],
520 printf("Querying full reverse path...\n");
521 mtracep
= (struct igmp_mtrace
*)mbuf
;
522 ret
= wait_for_response(fd
, &rhops
, mtracep
, &msec
);
524 print_responses(mtracep
, rhops
, msec
);
529 fprintf(stderr
, "%s: select error: %s\n", argv
[0],
535 printf("switching to hop-by-hop:\n");
537 for (i
= 1; i
< maxhops
; i
++) {
540 for (j
= 0; j
< perhop
; j
++) {
543 mtrace
.checksum
= in_cksum(&mtrace
, sizeof(mtrace
));
544 if (send_query(fd
, gw_addr
, &mtrace
) < 0) {
545 fprintf(stderr
, "%s: sendto error: %s\n",
546 argv
[0], strerror(errno
));
550 ret
= wait_for_response(fd
, &rhops
, mtracep
, &msec
);
552 if (check_end(mtracep
, rhops
)) {
553 print_rsp(&mtracep
->rsp
[rhops
- 1]);
554 print_summary(mtracep
, rhops
, msec
);
559 printf(" * ...giving up.\n");
563 print_rsp(&mtracep
->rsp
[rhops
- 1]);
577 #else /* __linux__ */
582 int main(int argc
, char *argv
[])
584 printf("%s implemented only for GNU/Linux\n", argv
[0]);
588 #endif /* __linux__ */