From 10c71e72071e3cc5d8df05f9ae1a0ae44720568a Mon Sep 17 00:00:00 2001 From: Dmitrii Allyanov Date: Wed, 10 Sep 2025 13:49:47 +0000 Subject: [PATCH] Add -D (--due-ttl) option. Specifies the minimum TTL value that must be reached when sending network probes. Closes feature request #295 --- man/mtr.8.in | 6 ++++++ ui/mtr.c | 22 ++++++++++++++++++++++ ui/mtr.h | 1 + ui/net.c | 22 +++++++++++++++++----- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/man/mtr.8.in b/man/mtr.8.in index 47c0cfe..5101094 100644 --- a/man/mtr.8.in +++ b/man/mtr.8.in @@ -421,6 +421,12 @@ Specifies with what TTL to start. Defaults to 1. Specifies the maximum number of hops (max time-to-live value) traceroute will probe. Default is 30. .TP +.B \-D \fINUM\fR, \fB\-\-due-ttl \fINUM +Specifies the minimum TTL value to achieve when sending network probes. +Default is disabled. However, if responses from the target host were received +on hops less than the specified value, the host address will be displayed only +in the ECMP section of the paths. +.TP .B \-U \fINUM\fR, \fB\-\-max-unknown \fINUM Specifies the maximum unknown host. Default is 5. .TP diff --git a/ui/mtr.c b/ui/mtr.c index 4d5a343..6886bce 100644 --- a/ui/mtr.c +++ b/ui/mtr.c @@ -107,6 +107,7 @@ static void __attribute__ ((__noreturn__)) usage(FILE * out) fputs(" -a, --address ADDRESS bind the outgoing socket to ADDRESS\n", out); fputs(" -f, --first-ttl NUMBER set what TTL to start\n", out); fputs(" -m, --max-ttl NUMBER maximum number of hops\n", out); + fputs(" -D, --due-ttl NUMBER set what TTL must be reached\n", out); fputs(" -U, --max-unknown NUMBER maximum unknown host\n", out); fputs(" -E, --max-display-path NUMBER maximum number of ECMP paths to display\n", out); fputs(" -P, --port PORT target port number for TCP, SCTP, or UDP\n", out); @@ -356,6 +357,7 @@ static void parse_arg( {"address", 1, NULL, 'a'}, {"first-ttl", 1, NULL, 'f'}, /* -f & -m are borrowed from traceroute */ {"max-ttl", 1, NULL, 'm'}, + {"due-ttl", 1, NULL, 'D'}, {"max-unknown", 1, NULL, 'U'}, {"max-display-path", 1, NULL, 'E'}, {"udp", 0, NULL, 'u'}, /* UDP (default is ICMP) */ @@ -497,6 +499,15 @@ static void parse_arg( ctl->maxTTL = 1; } break; + case 'D': + ctl->dueTTL = strtoint_or_err(optarg, "invalid argument"); + if (ctl->dueTTL > (MaxHost - 1)) { + ctl->dueTTL = MaxHost - 1; + } + if (ctl->dueTTL <= 0) { + error(EXIT_FAILURE, 0, "due TTL must be greater than 0"); + } + break; case 'U': ctl->maxUnknown = strtoint_or_err(optarg, "invalid argument"); @@ -652,6 +663,16 @@ static void parse_arg( exit (1); //ctl->fstTTL = ctl->maxTTL; } + if (ctl->dueTTL > 0 && ctl->dueTTL < ctl->fstTTL) { + fprintf (stderr, "%s: dueTTL(%d) cannot be less than firstTTL(%d). \n", + argv[0], ctl->dueTTL, ctl->fstTTL); + exit (1); + } + if (ctl->dueTTL > ctl->maxTTL) { + fprintf (stderr, "%s: dueTTL(%d) cannot be larger than maxTTL(%d). \n", + argv[0], ctl->dueTTL, ctl->maxTTL); + exit (1); + } if (optind > argc - 1) return; @@ -751,6 +772,7 @@ int main( ctl.mtrtype = IPPROTO_ICMP; ctl.fstTTL = 1; ctl.maxTTL = 30; + ctl.dueTTL = 0; ctl.maxUnknown = 12; ctl.maxDisplayPath = 8; ctl.probe_timeout = 10 * 1000000; diff --git a/ui/mtr.h b/ui/mtr.h index dcf02d7..038b7d2 100644 --- a/ui/mtr.h +++ b/ui/mtr.h @@ -102,6 +102,7 @@ struct mtr_ctl { int mtrtype; /* type of query packet used */ int fstTTL; /* initial hub(ttl) to ping byMin */ int maxTTL; /* last hub to ping byMin */ + int dueTTL; /* don't stop until reach dueTTL */ int maxUnknown; /* stop ping threshold */ int maxDisplayPath; /* maximum number of ECMP paths to display */ int remoteport; /* target port for TCP tracing */ diff --git a/ui/net.c b/ui/net.c index ce2c2c9..fe86c42 100644 --- a/ui/net.c +++ b/ui/net.c @@ -258,12 +258,23 @@ static void net_process_ping( display_rawhost(ctl, index, &nh->addrs[i], mpls); } - /* Always save the latest host in nh->addr. This + /* Save the latest host in nh->addr only, if the answer was from remotehost and option -D enabled. + This allows to see responses coming through routes with different TTLs and from the target host. + */ + if ((ctl->dueTTL > 0) && (addrcmp(&addrcopy, remoteaddress, ctl->af) == 0)) { + if (ctl->dueTTL <= (index + 1)) { + memcpy(&nh->addr, addrcopy, sockaddr_addr_size(sourcesockaddr)); + nh->mpls = *mpls; + display_rawhost(ctl, index, &nh->addr, mpls); + } + } else { + /* Save the latest host in nh->addr. This * allows maxTTL to change whenever path changes. */ memcpy(&nh->addr, addrcopy, sockaddr_addr_size(sourcesockaddr)); nh->mpls = *mpls; display_rawhost(ctl, index, &nh->addr, mpls); + } } nh->jitter = totusec - nh->last; @@ -587,8 +598,9 @@ int net_send_batch( but I don't remember why. It makes mtr stop skipping sections of unknown hosts. Removed in 0.65. If the line proves necessary, it should at least NOT trigger that line - when host[i].addr == 0 */ - if (host_addr_cmp(i, remoteaddress, ctl->af) == 0) { + when host[i].addr == 0 + Keep this behavior if the newly added -D (dueTTL) option is not enabled */ + if ((host_addr_cmp(i, remoteaddress, ctl->af) == 0) && (ctl->dueTTL == 0)) { restart = 1; numhosts = i + 1; /* Saves batch_at - index number of probes in the next round!*/ break; @@ -596,9 +608,9 @@ int net_send_batch( } if ( /* success in reaching target */ - (host_addr_cmp(batch_at, remoteaddress, ctl->af) == 0) || + ((host_addr_cmp(batch_at, remoteaddress, ctl->af) == 0) && (ctl->dueTTL == 0)) || /* fail in consecutive maxUnknown (firewall?) */ - (n_unknown > ctl->maxUnknown) || + ((n_unknown > ctl->maxUnknown) && (ctl->dueTTL == 0)) || /* or reach limit */ (batch_at >= ctl->maxTTL - 1)) { restart = 1; -- 2.47.3