Changed how conflicitng first/max TTL works.
[mtr.git] / packet / probe.c
blob4b265af47cc82918feab22a5720d0e2b4875bc65
1 /*
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.
19 #include "probe.h"
21 #include <arpa/inet.h>
22 #include <assert.h>
23 #include <errno.h>
24 #ifdef HAVE_ERROR_H
25 #include <error.h>
26 #else
27 #include "portability/error.h"
28 #endif
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/socket.h>
33 #include <unistd.h>
35 #include "command.h"
36 #include "platform.h"
37 #include "protocols.h"
38 #include "timeval.h"
39 #include "sockaddr.h"
41 char *probe_err;
43 /* Convert the destination address from text to sockaddr */
44 int decode_address_string(
45 int ip_version,
46 const char *address_string,
47 struct sockaddr_storage *address)
49 struct in_addr addr4;
50 struct in6_addr addr6;
51 struct sockaddr_in *sockaddr4;
52 struct sockaddr_in6 *sockaddr6;
54 if (address == NULL) {
55 errno = EINVAL;
56 return -1;
59 if (ip_version == 6) {
60 sockaddr6 = (struct sockaddr_in6 *) address;
62 if (inet_pton(AF_INET6, address_string, &addr6) != 1) {
63 errno = EINVAL;
64 return -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) {
76 errno = EINVAL;
77 return -1;
80 sockaddr4->sin_family = AF_INET;
81 sockaddr4->sin_port = 0;
82 sockaddr4->sin_addr = addr4;
83 } else {
84 errno = EINVAL;
85 return -1;
88 return 0;
92 Resolve the probe parameters into a remote and local address
93 for the probe.
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";
104 return -1;
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";
111 return -1;
113 } else {
114 probe_err = "find source address";
115 if (find_source_addr(src_sockaddr, dest_sockaddr)) {
116 //probe_err = "find source address";
117 return -1;
119 probe_err = "";
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());
128 return 0;
131 /* Allocate a structure for tracking a new probe */
132 struct probe_t *alloc_probe(
133 struct net_state_t *net_state,
134 int token)
136 struct probe_t *probe;
138 if (net_state->outstanding_probe_count >= MAX_PROBES) {
139 return NULL;
142 probe = malloc(sizeof(struct probe_t));
143 if (probe == NULL) {
144 return NULL;
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,
154 probe_list_entry);
156 return probe;
159 /* Mark a probe tracking structure as unused */
160 void free_probe(
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);
169 free(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,
178 int protocol,
179 int id,
180 int sequence)
182 struct probe_t *probe;
185 ICMP has room for an id to check against our process, but
186 UDP doesn't.
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())) {
194 return NULL;
198 LIST_FOREACH(probe, &net_state->outstanding_probes, probe_list_entry) {
199 if (htons(probe->sequence) == sequence) {
200 return probe;
204 return NULL;
208 Format a list of MPLS labels into a string appropriate for including
209 as an argument to a probe reply.
211 static
212 void format_mpls_string(
213 char *str,
214 int buffer_size,
215 int mpls_count,
216 const struct mpls_label_t *mpls_list)
218 int i;
219 char *append_pos = str;
220 const struct mpls_label_t *mpls;
222 /* Start with an empty string */
223 str[0] = 0;
225 for (i = 0; i < mpls_count; i++) {
226 mpls = &mpls_list[i];
228 if (i > 0) {
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
246 sent the probe.
248 void respond_to_probe(
249 struct net_state_t *net_state,
250 struct probe_t *probe,
251 int icmp_type,
252 const struct sockaddr_storage *remote_addr,
253 unsigned int round_trip_us,
254 int mpls_count,
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];
260 int remaining_size;
261 const char *result;
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";
269 } else {
270 assert(icmp_type == ICMP_ECHOREPLY);
271 result = "reply";
274 if (remote_addr->ss_family == AF_INET6) {
275 ip_argument = "ip-6";
276 } else {
277 ip_argument = "ip-4";
280 if (inet_ntop(remote_addr->ss_family, sockaddr_addr_offset(remote_addr), ip_text, INET6_ADDRSTRLEN) ==
281 NULL) {
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);
289 if (mpls_count) {
290 format_mpls_string(mpls_str, COMMAND_BUFFER_SIZE, mpls_count,
291 mpls);
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);
300 puts(response);
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)
319 int sock;
320 int len;
321 struct sockaddr_storage dest_with_port;
322 #ifdef __linux__
323 // The Linux code needs these.
324 struct sockaddr_in *srcaddr4;
325 struct sockaddr_in6 *srcaddr6;
326 #endif
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);
341 if (sock == -1) {
342 probe_err = "open socket";
343 return -1;
346 if (connect(sock, (struct sockaddr *) &dest_with_port, len) == 0) {
347 if (getsockname(sock, (struct sockaddr *) srcaddr, &len)) {
348 close(sock);
349 probe_err = "getsockname";
350 return -1;
352 } else {
353 #ifdef __linux__
354 /* Linux doesn't require source address, so we can support
355 * a case when mtr is run against unreachable host (that can become
356 * reachable) */
357 if (errno != EHOSTUNREACH) {
358 probe_err = "not hostunreach";
359 close(sock);
360 return -1;
363 if (destaddr->ss_family == AF_INET6) {
364 srcaddr6 = (struct sockaddr_in6 *) srcaddr;
365 srcaddr6->sin6_addr = in6addr_any;
366 } else {
367 srcaddr4 = (struct sockaddr_in *) srcaddr;
368 srcaddr4->sin_addr.s_addr = INADDR_ANY;
370 #else
371 close(sock);
372 probe_err = "connect failed";
373 return -1;
374 #endif
377 close(sock);
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;
385 return 0;