2 mtr -- a network diagnostic tool
3 Copyright (C) 2016 Matt Kimball
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 as
7 published by the Free Software Foundation.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include <arpa/inet.h>
27 #include "portability/error.h"
32 #include <sys/socket.h>
37 #include "protocols.h"
43 /* Convert the destination address from text to sockaddr */
44 int decode_address_string(
46 const char *address_string
,
47 struct sockaddr_storage
*address
)
50 struct in6_addr addr6
;
51 struct sockaddr_in
*sockaddr4
;
52 struct sockaddr_in6
*sockaddr6
;
54 if (address
== NULL
) {
59 if (ip_version
== 6) {
60 sockaddr6
= (struct sockaddr_in6
*) address
;
62 if (inet_pton(AF_INET6
, address_string
, &addr6
) != 1) {
67 sockaddr6
->sin6_family
= AF_INET6
;
68 sockaddr6
->sin6_port
= 0;
69 sockaddr6
->sin6_flowinfo
= 0;
70 sockaddr6
->sin6_addr
= addr6
;
71 sockaddr6
->sin6_scope_id
= 0;
72 } else if (ip_version
== 4) {
73 sockaddr4
= (struct sockaddr_in
*) address
;
75 if (inet_pton(AF_INET
, address_string
, &addr4
) != 1) {
80 sockaddr4
->sin_family
= AF_INET
;
81 sockaddr4
->sin_port
= 0;
82 sockaddr4
->sin_addr
= addr4
;
92 Resolve the probe parameters into a remote and local address
95 int resolve_probe_addresses(
96 struct net_state_t
*net_state
,
97 const struct probe_param_t
*param
,
98 struct sockaddr_storage
*dest_sockaddr
,
99 struct sockaddr_storage
*src_sockaddr
)
101 if (decode_address_string
102 (param
->ip_version
, param
->remote_address
, dest_sockaddr
)) {
103 probe_err
= "decode address string remote";
107 if (param
->local_address
) {
108 if (decode_address_string
109 (param
->ip_version
, param
->local_address
, src_sockaddr
)) {
110 probe_err
= "decode address string local";
114 probe_err
= "find source address";
115 if (find_source_addr(src_sockaddr
, dest_sockaddr
)) {
116 //probe_err = "find source address";
121 /* DGRAM ICMP id is taken from src_port not from ICMP header */
122 if (param
->protocol
== IPPROTO_ICMP
) {
123 if ( (src_sockaddr
->ss_family
== AF_INET
&& !net_state
->platform
.ip4_socket_raw
) ||
124 (src_sockaddr
->ss_family
== AF_INET6
&& !net_state
->platform
.ip6_socket_raw
) )
125 *sockaddr_port_offset(src_sockaddr
) = htons(getpid());
131 /* Allocate a structure for tracking a new probe */
132 struct probe_t
*alloc_probe(
133 struct net_state_t
*net_state
,
136 struct probe_t
*probe
;
138 if (net_state
->outstanding_probe_count
>= MAX_PROBES
) {
142 probe
= malloc(sizeof(struct probe_t
));
147 memset(probe
, 0, sizeof(struct probe_t
));
148 probe
->token
= token
;
150 platform_alloc_probe(net_state
, probe
);
152 net_state
->outstanding_probe_count
++;
153 LIST_INSERT_HEAD(&net_state
->outstanding_probes
, probe
,
159 /* Mark a probe tracking structure as unused */
161 struct net_state_t
*net_state
,
162 struct probe_t
*probe
)
164 LIST_REMOVE(probe
, probe_list_entry
);
165 net_state
->outstanding_probe_count
--;
167 platform_free_probe(probe
);
173 Find an existing probe structure by ICMP id and sequence number.
174 Returns NULL if non is found.
176 struct probe_t
*find_probe(
177 struct net_state_t
*net_state
,
182 struct probe_t
*probe
;
185 ICMP has room for an id to check against our process, but
188 if (protocol
== IPPROTO_ICMP
) {
190 If the ICMP id doesn't match our process ID, it wasn't a
191 probe generated by this process, so ignore it.
193 if (id
!= htons(getpid())) {
198 LIST_FOREACH(probe
, &net_state
->outstanding_probes
, probe_list_entry
) {
199 if (htons(probe
->sequence
) == sequence
) {
208 Format a list of MPLS labels into a string appropriate for including
209 as an argument to a probe reply.
212 void format_mpls_string(
216 const struct mpls_label_t
*mpls_list
)
219 char *append_pos
= str
;
220 const struct mpls_label_t
*mpls
;
222 /* Start with an empty string */
225 for (i
= 0; i
< mpls_count
; i
++) {
226 mpls
= &mpls_list
[i
];
229 strncat(append_pos
, ",", buffer_size
- 1);
231 buffer_size
-= strlen(append_pos
);
232 append_pos
+= strlen(append_pos
);
235 snprintf(append_pos
, buffer_size
, "%d,%d,%d,%d",
236 mpls
->label
, mpls
->traffic_class
,
237 mpls
->bottom_of_stack
, mpls
->ttl
);
239 buffer_size
-= strlen(append_pos
);
240 append_pos
+= strlen(append_pos
);
245 After a probe reply has arrived, respond to the command request which
248 void respond_to_probe(
249 struct net_state_t
*net_state
,
250 struct probe_t
*probe
,
252 const struct sockaddr_storage
*remote_addr
,
253 unsigned int round_trip_us
,
255 const struct mpls_label_t
*mpls
)
257 char ip_text
[INET6_ADDRSTRLEN
];
258 char response
[COMMAND_BUFFER_SIZE
];
259 char mpls_str
[COMMAND_BUFFER_SIZE
];
262 const char *ip_argument
;
264 if (icmp_type
== ICMP_TIME_EXCEEDED
) {
265 result
= "ttl-expired";
266 } else if (icmp_type
== ICMP_DEST_UNREACH
) {
267 /* XXX icmphdr->code is not known here, so assume that host is unreachable */
268 result
= "no-route-host";
270 assert(icmp_type
== ICMP_ECHOREPLY
);
274 if (remote_addr
->ss_family
== AF_INET6
) {
275 ip_argument
= "ip-6";
277 ip_argument
= "ip-4";
280 if (inet_ntop(remote_addr
->ss_family
, sockaddr_addr_offset(remote_addr
), ip_text
, INET6_ADDRSTRLEN
) ==
282 error(EXIT_FAILURE
, errno
, "inet_ntop failure");
285 snprintf(response
, COMMAND_BUFFER_SIZE
,
286 "%d %s %s %s round-trip-time %d",
287 probe
->token
, result
, ip_argument
, ip_text
, round_trip_us
);
290 format_mpls_string(mpls_str
, COMMAND_BUFFER_SIZE
, mpls_count
,
293 remaining_size
= COMMAND_BUFFER_SIZE
- strlen(response
) - 1;
294 strncat(response
, " mpls ", remaining_size
);
296 remaining_size
= COMMAND_BUFFER_SIZE
- strlen(response
) - 1;
297 strncat(response
, mpls_str
, remaining_size
);
301 free_probe(net_state
, probe
);
305 Find the source address for transmitting to a particular destination
306 address. Remember that hosts can have multiple addresses, for example
307 a unique address for each network interface. So we will bind a UDP
308 socket to our destination and check the socket address after binding
309 to get the source for that destination, which will allow the kernel
310 to do the routing table work for us.
312 (connecting UDP sockets, unlike TCP sockets, doesn't transmit any packets.
313 It's just an association.)
315 int find_source_addr(
316 struct sockaddr_storage
*srcaddr
,
317 const struct sockaddr_storage
*destaddr
)
321 struct sockaddr_storage dest_with_port
;
323 // The Linux code needs these.
324 struct sockaddr_in
*srcaddr4
;
325 struct sockaddr_in6
*srcaddr6
;
329 dest_with_port
= *destaddr
;
332 MacOS requires a non-zero sin_port when used as an
333 address for a UDP connect. If we provide a zero port,
334 the connect will fail. We aren't actually sending
335 anything to the port.
337 *sockaddr_port_offset(&dest_with_port
) = htons(1);
338 len
= sockaddr_size(&dest_with_port
);
340 sock
= socket(destaddr
->ss_family
, SOCK_DGRAM
, IPPROTO_UDP
);
342 probe_err
= "open socket";
346 if (connect(sock
, (struct sockaddr
*) &dest_with_port
, len
) == 0) {
347 if (getsockname(sock
, (struct sockaddr
*) srcaddr
, &len
)) {
349 probe_err
= "getsockname";
354 /* Linux doesn't require source address, so we can support
355 * a case when mtr is run against unreachable host (that can become
357 if (errno
!= EHOSTUNREACH
) {
358 probe_err
= "not hostunreach";
363 if (destaddr
->ss_family
== AF_INET6
) {
364 srcaddr6
= (struct sockaddr_in6
*) srcaddr
;
365 srcaddr6
->sin6_addr
= in6addr_any
;
367 srcaddr4
= (struct sockaddr_in
*) srcaddr
;
368 srcaddr4
->sin_addr
.s_addr
= INADDR_ANY
;
372 probe_err
= "connect failed";
380 Zero the port, as we may later use this address to finding, and
381 we don't want to use the port from the socket we just created.
383 *sockaddr_port_offset(srcaddr
) = 0;