]> git.ipfire.org Git - thirdparty/mtr.git/commitdiff
options: accept target ports in host arguments 621/head
authorDarafei Praliaskouski <me@komzpa.net>
Fri, 8 May 2026 12:38:23 +0000 (16:38 +0400)
committerDarafei Praliaskouski <me@komzpa.net>
Fri, 8 May 2026 12:38:23 +0000 (16:38 +0400)
man/mtr.8.in
ui/mtr.c

index 7a70065e2770f05158d6d7ff8d169e44b6cdefd0..c5d6d8a10da7f4b3f9597ff56a93705b8911d250 100644 (file)
@@ -121,7 +121,7 @@ mtr \- a network diagnostic tool
 [\c
 .BI \-M \ MARK\c
 ]
-.I HOSTNAME
+.IR HOSTNAME [ :PORT ]
 .SH DESCRIPTION
 .B mtr
 combines the functionality of the
@@ -466,6 +466,11 @@ The target port number for TCP/SCTP/UDP traces.
 When this option is not given, TCP and SCTP traces use port 80.
 UDP traces encode the probe sequence number in the destination port
 instead, so the destination port changes for each probe.
+For TCP/SCTP/UDP traces, the target can also be written as
+.IR HOSTNAME : PORT .
+Use
+.IR [ ADDRESS ] : PORT
+when specifying a port for a literal IPv6 address.
 .TP
 .B \-L \fILOCALPORT\fR, \fB\-\-localport \fILOCALPORT
 The source port number for UDP traces.  When this option is not given,
index 29f0432c222f86c3f637194cba2e1ed39544bad9..33cdf7580681d6ed55c678e187b6bc39456ee55d 100644 (file)
--- a/ui/mtr.c
+++ b/ui/mtr.c
@@ -88,13 +88,14 @@ const struct fields data_fields[MAXFLD] = {
 
 typedef struct names {
     char *name;
+    int remoteport;
     struct names *next;
 } names_t;
 
 static void __attribute__ ((__noreturn__)) usage(FILE * out)
 {
     fputs("\nUsage:\n", out);
-    fputs(" mtr [options] hostname\n", out);
+    fputs(" mtr [options] hostname[:port]\n", out);
     fputs("\n", out);
     fputs(" -F, --filename FILE              read hostname(s) from a file\n",
           out);      
@@ -180,6 +181,54 @@ static void append_to_names(
     *name_tail = name;
 }
 
+static int parse_target_port(
+    const char *port)
+{
+    int remoteport = strtoint_or_err(port, "invalid argument");
+
+    if (remoteport < 1 || MaxPort < remoteport) {
+        error(EXIT_FAILURE, 0, "Illegal port number: %d", remoteport);
+    }
+
+    return remoteport;
+}
+
+static void split_target_port(
+    names_t *names,
+    int mtrtype)
+{
+    if (mtrtype == IPPROTO_ICMP) {
+        return;
+    }
+
+    while (names != NULL) {
+        char *port = NULL;
+
+        if (names->name[0] == '[') {
+            char *close = strchr(names->name, ']');
+
+            if (close && close[1] == ':' && close[2] != '\0') {
+                port = close + 2;
+                *close = '\0';
+                memmove(names->name, names->name + 1, close - names->name);
+            }
+        } else {
+            char *colon = strchr(names->name, ':');
+
+            if (colon && strchr(colon + 1, ':') == NULL && colon[1] != '\0') {
+                port = colon + 1;
+                *colon = '\0';
+            }
+        }
+
+        if (port) {
+            names->remoteport = parse_target_port(port);
+        }
+
+        names = names->next;
+    }
+}
+
 static void read_from_file(
     names_t ** names,
     const char *filename)
@@ -934,15 +983,20 @@ int main(
     if (!names_head)
         append_to_names(&names_head, "localhost");
 
+    split_target_port(names_head, ctl.mtrtype);
+
     if (!ctl.Interactive && count_names(names_head) > 1 &&
         validate_report_targets(&ctl, names_head) != 0) {
         return EXIT_FAILURE;
     }
 
+    int default_remoteport = ctl.remoteport;
     names_walk = names_head;
     while (names_walk != NULL) {
 
         ctl.Hostname = names_walk->name;
+        ctl.remoteport =
+            names_walk->remoteport ? names_walk->remoteport : default_remoteport;
         if (gethostname(ctl.LocalHostname, sizeof(ctl.LocalHostname))) {
             xstrncpy(ctl.LocalHostname, "UNKNOWNHOST",
                      sizeof(ctl.LocalHostname));