]> git.ipfire.org Git - thirdparty/mtr.git/commitdiff
commandline: Added --interface for using a named network interface 211/head
authorMatt Kimball <matt.kimball@gmail.com>
Sun, 16 Jul 2017 17:19:32 +0000 (10:19 -0700)
committerMatt Kimball <matt.kimball@gmail.com>
Sun, 16 Jul 2017 17:19:32 +0000 (10:19 -0700)
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
ui/mtr.c
ui/mtr.h
ui/net.c

index 7b6709b07baca0fbfbbaf45c9d748b70219920f6..7dacd8eb9c7129b19cbe051bd588a718967ee227 100644 (file)
@@ -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 ,
index 1f4afd678c8463f09e0a43226ae7bbc77315e9e9..99992463dfd50e17f39a4eec76ad2bc26056e557 100644 (file)
--- 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");
index 7ede21d8a49f1e6f31b4b3d35c61028c047a9444..fdca96ba24d57c4d1b51dd7224ecd8ae5047166a 100644 (file)
--- 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;
index 6396ab6d34574323aa39daf15e32554f871f5504..507cc4c4142cc69f9bbe90a58585c44ec58ed394 100644 (file)
--- a/ui/net.c
+++ b/ui/net.c
@@ -19,6 +19,7 @@
 #include "config.h"
 
 #include <errno.h>
+#include <ifaddrs.h>
 #include <math.h>
 #include <stdlib.h>
 #include <string.h>
@@ -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();
     }