From 8cfd8c36b50e1d68fd2634501e46344724a7e292 Mon Sep 17 00:00:00 2001 From: Matt Kimball Date: Sun, 16 Jul 2017 10:19:32 -0700 Subject: [PATCH] commandline: Added --interface for using a named network interface Using '--interface' on the commandline (or '-I') will specify a network interface by name. This is sometimes a more convenient alternative to using '--address' for specifying a source address from which to send probes. This can be useful when you have both a wired ethernet connection and WiFi connection, and wish to use a specific connection for the purposes of tracing. This feature was requested in issue #207. This change also cleans up main() slightly by factoring out the hostent structure generation. --- man/mtr.8.in | 9 ++++ ui/mtr.c | 120 +++++++++++++++++++++++++++++++-------------------- ui/mtr.h | 1 + ui/net.c | 61 ++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 47 deletions(-) diff --git a/man/mtr.8.in b/man/mtr.8.in index 7b6709b..7dacd8e 100644 --- a/man/mtr.8.in +++ b/man/mtr.8.in @@ -77,6 +77,9 @@ mtr \- a network diagnostic tool .B \-\-mpls\c ] [\c +.BI \-I \ NAME\c +] +[\c .BI \-a \ ADDRESS\c ] [\c @@ -377,6 +380,12 @@ Use this option to tell to display information from ICMP extensions for MPLS (RFC 4950) that are encoded in the response packets. .TP +.B \-I \fINAME\fR, \fB\-\-interface \fINAME +Use the network interface with a specific name for sending network probes. +This can be useful when you have multiple network interfaces with routes +to your destination, for example both wired Ethernet and WiFi, and wish +to test a particular interface. +.TP .B \-a \fIADDRESS\fR, \fB\-\-address \fIADDRESS Use this option to bind the outgoing socket to .IR ADDRESS , diff --git a/ui/mtr.c b/ui/mtr.c index 1f4afd6..9999246 100644 --- a/ui/mtr.c +++ b/ui/mtr.c @@ -112,6 +112,8 @@ static void __attribute__ ((__noreturn__)) usage(FILE * out) out); fputs(" -T, --tcp use TCP instead of ICMP echo\n", out); + fputs(" -I, --interface NAME use named network interface\n", + out); fputs (" -a, --address ADDRESS bind the outgoing socket to ADDRESS\n", out); @@ -365,6 +367,7 @@ static void parse_arg( {"bitpattern", 1, NULL, 'B'}, /* overload B>255, ->rand(0,255) */ {"tos", 1, NULL, 'Q'}, /* typeof service (0,255) */ {"mpls", 0, NULL, 'e'}, + {"interface", 1, NULL, 'I'}, {"address", 1, NULL, 'a'}, {"first-ttl", 1, NULL, 'f'}, /* -f & -m are borrowed from traceroute */ {"max-ttl", 1, NULL, 'm'}, @@ -464,6 +467,9 @@ static void parse_arg( ctl->cpacketsize = strtonum_or_err(optarg, "invalid argument", STRTO_INT); break; + case 'I': + ctl->InterfaceName = optarg; + break; case 'a': ctl->InterfaceAddress = optarg; break; @@ -683,19 +689,79 @@ static void init_rand( srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec); } + +/* + For historical reasons, we need a hostent structure to represent + our remote target for probing. The obsolete way of doing this + would be to use gethostbyname(). We'll use getaddrinfo() instead + to generate the hostent. +*/ +static int get_hostent_from_name( + struct mtr_ctl *ctl, + struct hostent *host, + const char *name, + char **alptr) +{ + int gai_error; + struct addrinfo hints, *res; + struct sockaddr_in *sa4; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *sa6; +#endif + + /* gethostbyname2() is deprecated so we'll use getaddrinfo() instead. */ + memset(&hints, 0, sizeof hints); + hints.ai_family = ctl->af; + hints.ai_socktype = SOCK_DGRAM; + gai_error = getaddrinfo(name, NULL, &hints, &res); + if (gai_error) { + if (gai_error == EAI_SYSTEM) + error(0, 0, "Failed to resolve host: %s", name); + else + error(0, 0, "Failed to resolve host: %s: %s", name, + gai_strerror(gai_error)); + + return -1; + } + + /* Convert the first addrinfo into a hostent. */ + memset(host, 0, sizeof(struct hostent)); + host->h_name = res->ai_canonname; + host->h_aliases = NULL; + host->h_addrtype = res->ai_family; + ctl->af = res->ai_family; + host->h_length = res->ai_addrlen; + host->h_addr_list = alptr; + switch (ctl->af) { + case AF_INET: + sa4 = (struct sockaddr_in *) res->ai_addr; + alptr[0] = (void *) &(sa4->sin_addr); + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + sa6 = (struct sockaddr_in6 *) res->ai_addr; + alptr[0] = (void *) &(sa6->sin6_addr); + break; +#endif + default: + error(0, 0, "unknown address type"); + + errno = EINVAL; + return -1; + } + alptr[1] = NULL; + + return 0; +} + + int main( int argc, char **argv) { struct hostent *host = NULL; - struct addrinfo hints, *res; - int gai_error; struct hostent trhost; char *alptr[2]; - struct sockaddr_in *sa4; -#ifdef ENABLE_IPV6 - struct sockaddr_in6 *sa6; -#endif names_t *names_head = NULL; names_t *names_walk; @@ -764,47 +830,8 @@ int main( sizeof(ctl.LocalHostname)); } - /* gethostbyname2() is deprecated so we'll use getaddrinfo() instead. */ - memset(&hints, 0, sizeof hints); - hints.ai_family = ctl.af; - hints.ai_socktype = SOCK_DGRAM; - gai_error = getaddrinfo(ctl.Hostname, NULL, &hints, &res); - if (gai_error) { - if (gai_error == EAI_SYSTEM) - error(0, 0, "Failed to resolve host: %s", ctl.Hostname); - else - error(0, 0, "Failed to resolve host: %s: %s", ctl.Hostname, - gai_strerror(gai_error)); - - if (ctl.Interactive) - exit(EXIT_FAILURE); - else { - names_walk = names_walk->next; - continue; - } - } - /* Convert the first addrinfo into a hostent. */ host = &trhost; - memset(host, 0, sizeof trhost); - host->h_name = res->ai_canonname; - host->h_aliases = NULL; - host->h_addrtype = res->ai_family; - ctl.af = res->ai_family; - host->h_length = res->ai_addrlen; - host->h_addr_list = alptr; - switch (ctl.af) { - case AF_INET: - sa4 = (struct sockaddr_in *) res->ai_addr; - alptr[0] = (void *) &(sa4->sin_addr); - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - sa6 = (struct sockaddr_in6 *) res->ai_addr; - alptr[0] = (void *) &(sa6->sin6_addr); - break; -#endif - default: - error(0, 0, "unknown address type"); + if (get_hostent_from_name(&ctl, host, ctl.Hostname, alptr) != 0) { if (ctl.Interactive) exit(EXIT_FAILURE); else { @@ -812,7 +839,6 @@ int main( continue; } } - alptr[1] = NULL; if (net_open(&ctl, host) != 0) { error(0, 0, "Unable to start net module"); diff --git a/ui/mtr.h b/ui/mtr.h index 7ede21d..fdca96b 100644 --- a/ui/mtr.h +++ b/ui/mtr.h @@ -83,6 +83,7 @@ struct mtr_ctl { float WaitTime; float GraceTime; char *Hostname; + char *InterfaceName; char *InterfaceAddress; char LocalHostname[128]; int ipinfo_no; diff --git a/ui/net.c b/ui/net.c index 6396ab6..507cc4c 100644 --- a/ui/net.c +++ b/ui/net.c @@ -19,6 +19,7 @@ #include "config.h" #include +#include #include #include #include @@ -620,6 +621,61 @@ static void net_validate_interface_address( } +/* + Given the name of a network interface and a preferred address + family (IPv4 or IPv6), find the source IP address for sending + probes from that interface. +*/ +static void net_find_interface_address_from_name( + struct sockaddr_storage *addr, + int address_family, + const char *interface_name) +{ + struct ifaddrs *ifaddrs; + struct ifaddrs *interface; + int found_interface_name = 0; + + if (getifaddrs(&ifaddrs) != 0) { + error(EXIT_FAILURE, errno, "getifaddrs failure"); + } + + interface = ifaddrs; + while (interface != NULL) { + if (!strcmp(interface->ifa_name, interface_name)) { + found_interface_name = 1; + + if (interface->ifa_addr->sa_family == address_family) { + if (address_family == AF_INET) { + memcpy(addr, + interface->ifa_addr, sizeof(struct sockaddr_in)); + freeifaddrs(ifaddrs); + + return; + } else if (address_family == AF_INET6) { + memcpy(addr, + interface->ifa_addr, sizeof(struct sockaddr_in6)); + freeifaddrs(ifaddrs); + + return; + } + } + } + + interface = interface->ifa_next; + } + + if (!found_interface_name) { + error(EXIT_FAILURE, 0, "no such interface"); + } else if (address_family == AF_INET) { + error(EXIT_FAILURE, 0, "interface missing IPv4 address"); + } else if (address_family == AF_INET6) { + error(EXIT_FAILURE, 0, "interface missing IPv6 address"); + } else { + error(EXIT_FAILURE, 0, "interface missing address"); + } +} + + /* Find the local address we will use to sent to the remote host by connecting a UDP socket and checking the address @@ -711,6 +767,11 @@ int net_open( if (ctl->InterfaceAddress) { net_validate_interface_address(ctl->af, ctl->InterfaceAddress); + } else if (ctl->InterfaceName) { + net_find_interface_address_from_name( + &sourcesockaddr_struct, ctl->af, ctl->InterfaceName); + + sockaddrtop(sourcesockaddr, localaddr, sizeof(localaddr)); } else { net_find_local_address(); } -- 2.47.2