diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..ed16194
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,14 @@
+[flake8]
+
+select =
+    E902,
+    E999,
+    S,
+    F,
+
+ignore =
+    F821
+
+per-file-ignores =
+    test/*: S603,S404
+
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
index 0000000..c47f170
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,35 @@
+name: Test compilation
+on: [push, pull_request]
+jobs:
+  lint:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v4
+        with:
+          python-version: 3.9
+      - name: Install flake8
+        run: pip3 install flake8==3.9.2 flake8-bandit==2.1.2 bandit==1.7.2
+      - name: Run flake8
+        run: python3 -m flake8 .
+  compile-linux:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v4
+        with:
+          python-version: 3.9
+      - uses: egor-tensin/setup-gcc@v1.3
+        with:
+          version: latest
+          platform: x64
+      - name: Bootstrap
+        run: ./bootstrap.sh
+      - name: Configure
+        run: ./configure --without-gtk --without-jansson
+      - name: Make
+        run: make -j $(nproc)
+      - name: Run sample mtr against 1.1.1.1
+        run: ./mtr --report --report-cycles 1 -m 1 1.1.1.1
+      - name: Run test - cmdparse.py
+        run: python3 ./test/cmdparse.py
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index d3d1068..ba54f42 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,5 @@ stamp-h1*
 /test/*.py.trs
 
 /mtr-*.tar.gz
+*.swp
+*~
diff --git a/.tarball-version b/.tarball-version
index fd6d73e..10b1865 100644
--- a/.tarball-version
+++ b/.tarball-version
@@ -1 +1 @@
-0.95
+0.96
diff --git a/NEWS b/NEWS
index ddbac03..8e8eb8a 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,125 @@ The new release script will do a "git shortlog" to add
 the commit messages here. 
 
 #NEW_STUFF_HERE this is a tag my script looks for. 
+V0.96
+   23hiro (1):
+         Merge branch 'traviscross:master' into master
+   
+   Alarig Le Lay (1):
+         Change UDP and ICMP sockets binding to accept a source IP from the -a CLI option
+   
+   Andrew Marshall (1):
+         Adjust MIN_PORT to match other implementations
+   
+   Arkadiusz Miśkiewicz (1):
+         Handle EHOSTDOWN and refine error handling better granularity
+   
+   Bart Trojanowski (3):
+         add braille graph support with --displaymode 3
+         fix legend for braille display
+         fix documentation/comment for ENABLE_BRAILLE
+   
+   Brandon Ewing (2):
+         use addrs for static host ordering in curses
+         add --max-display-paths option
+   
+   Denis Afonin (1):
+         Add a compact mode in curses
+   
+   Denis Ovsienko (1):
+         mtr.8.in: spell --mark argument type properly
+   
+   Denys Fedoryshchenko (1):
+         Fix tiny typo in target
+   
+   Evan Pratten (1):
+         Implement ASN lookups in well-known nat64 prefix
+   
+   James Lu (2):
+         net: implement addrcmp for AF_UNSPEC
+         Initialize lines to empty string in split mode
+   
+   Jian Cheng (1):
+         Add error code ETIMEOUT(110) handle logic
+   
+   Marcus Meissner (1):
+         fixed the sizes passed into snprintf
+   
+   Marek Küthe (6):
+         Allow signed integers in the utils function
+         Split the strtonum function into two parts to create a better structure
+         Remove redundant code
+         Fix https://github.com/traviscross/mtr/issues/475
+         xml report: remove leading spaces
+         Set UTF-8 encoding for XML reports
+   
+   Matt Kimball (1):
+         Update Cygwin ICMP service thread for asynchronous pipes
+   
+   Michal Sekletar (1):
+         Prevent icmp_socket leak on error
+   
+   R.E. Wolff (9):
+         Markus pointed out useless statement.
+         merged
+         Merge branch 'master' of github.com:traviscross/mtr
+         Fixed typo noted by @szczot3k
+         Changed how conflicitng first/max TTL works.
+         Increased max probes
+         Added protection against use of MTR_PACKET under special circumstances
+         Merge branch 'master' of github.com:traviscross/mtr
+         Added Arad Cohen to NEWS
+   
+   Robert Vollmer (2):
+         Set SO_BINDTODEVICE for -I
+         Check if SO_BINDTODEVICE is defined
+   
+   Sami Kerola (1):
+         ui: make interactive and non-interactive exit code the same
+   
+   Tony Lewis Hiroaki URAHAMA (3):
+         Add WSL method to Windows Install
+         Add Ubuntu as specific distribution
+         Update section title
+   
+   darless (1):
+         Github actions added to perform lint and compile
+   
+   eater (1):
+         configure.ac: fix broken cap check
+   
+   famfo (1):
+         Add option to use custom ipinfo provider
+   
+   flu0r1ne (10):
+         Fix Capability Management, Retain CAP_NET_ADMIN
+         Fix interface binding by retaining CAP_NET_RAW
+         Linux-Only Interface, Marking, and IP Unit Tests
+         Annotate `set_privileged_socket_opt` with UNUSED
+         Drop capabilities when `setsockopt` errors
+         Fix flake8 linting
+         Change B101->S101 to reflect flake8
+         Use a uint32 for the type of a Linux mark
+         Use Packet Marking for IP Address Selection
+         Support Hexadecimal Arguments for Packet Marking
+   
+   hiro (1):
+         ipv6 udp checksums like ipv4 but with ipv6 pseudoheader
+   
+   lilinjie (1):
+         fix typo
+   
+   verrens (1):
+         Merge branch 'master' into compact-layout
+   
+   wenlxie (1):
+         Add help info for option -E
+   
+
+   Arad Cohen:
+         Brought an unlikely privilege escalation
+         scenario to my attention. 
+
 V0.95
    Aaron Lipinski (27):
          move net_send_batch call to its caller
@@ -354,9 +473,9 @@ V0.88
          build-sys: default to ,/configure --enable-silent-rules
          warnings: do not take abs() when data type is unsigned
          warnings: mark unused function input variables
-         warnings: fix couple unsigned vs signed variable comparisions
+         warnings: fix couple unsigned vs signed variable comparisons
          warnings: multiply timeval seconds only when the value is small
-         warnings: fix some missed unsigned vs signed variable comparisions
+         warnings: fix some missed unsigned vs signed variable comparisons
          comment: add value range note to initialization
          cast: do not downgrade to float when double should be used
          warnings: remove dead code
diff --git a/README.md b/README.md
index 9e4ebbb..b3fab52 100644
--- a/README.md
+++ b/README.md
@@ -70,12 +70,29 @@ to the "trusted" directory.)
 
 Building on MacOS should not require any special steps.
 
-BUILDING FOR WINDOWS
+USING MTR ON WINDOWS
 ===
 
-Building for Windows requires Cygwin.  To obtain Cygwin, see
+Using mtr on Windows requires Windows Subsystem for Linux (WSL).  
+To install WSL with Ubuntu distribution (Default), see
+[How to install Linux on Windows with WSL](https://learn.microsoft.com/en-us/windows/wsl/install).
+
+After complete initial process,
+simple as:
+
+	sudo apt-get -y install mtr
+
+
+
+BUILDING FOR WINDOWS (TRADITIONAL METHOD)
+===
+
+If you prefer traditional method.
+Obtain Cygwin, see
 https://cygwin.com/install.html.
+
 Next, re-run cygwin's `setup-x86.exe` (or `setup-x86_64.exe` if you're using 64bit cygwin) with the following arguments,
+
 which will install the packages required for building:
 
         setup-x86.exe --package-manager --wait --packages automake,pkg-config,make,gcc-core,libncurses-devel,libjansson-devel
@@ -89,6 +106,8 @@ Finally, install the built binaries:
         make install
 
 
+
+
 WHERE CAN I GET THE LATEST VERSION OR MORE INFORMATION?
 ===
 
diff --git a/configure.ac b/configure.ac
index 3175d56..444aad6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -25,7 +25,7 @@ m4_ifndef([PKG_CHECK_MODULES], [m4_defun([PKG_CHECK_MODULES], [AC_MSG_ERROR(
 [Could not locate the pkg-config autoconf macros.  These are usually located
 in /usr/share/aclocal/pkg.m4.  If your macros are in a different location,
 try setting the environment variable ACLOCAL_OPTS="-I/other/macro/dir"
-before running ./bootstrap.sh again, or configure --without-gtk ----without-jansson ])])
+before running ./bootstrap.sh again, or configure --without-gtk --without-jansson ])])
 ])
 PKG_PROG_PKG_CONFIG
 
@@ -112,6 +112,20 @@ AS_IF([test "x$with_jansson" = "xyes"],
     [with_jansson=no])
 ])
 
+# Find ncursesw
+AC_ARG_WITH([ncursesw],
+  [AS_HELP_STRING([--without-ncursesw], [Build without the ncursesw interface])],
+  [], [with_ncursesw=yes])
+AS_IF([test "x$with_ncursesw" = "xyes"],
+
+  # Prefer ncursesw, if available
+  [AC_SEARCH_LIBS(
+    [wprintw], [ncursesw],
+    [AC_DEFINE([HAVE_CURSESW], [1], [Define if a ncursesw library available])],
+    [with_ncursesw=no])
+])
+AM_CONDITIONAL([WITH_CURSESW], [test "x$with_ncursesw" = xyes])
+
 # Find ncurses
 AC_ARG_WITH([ncurses],
   [AS_HELP_STRING([--without-ncurses], [Build without the ncurses interface])],
@@ -121,15 +135,19 @@ AS_IF([test "x$with_ncurses" = "xyes"],
   # Prefer ncurses over curses, if both are available.
   # (On Solaris 11.3, ncurses builds and links for us, but curses does not.)
   [AC_SEARCH_LIBS(
-    [initscr], [ncurses curses],
+    [initscr], [ncursesw ncurses curses],
     [AC_DEFINE([HAVE_CURSES], [1], [Define if a curses library available])],
     [with_ncurses=no])
 ])
 AM_CONDITIONAL([WITH_CURSES], [test "x$with_ncurses" = xyes])
 
-AC_CHECK_LIB([cap], [cap_set_proc], [have_cap="yes"],
-  AS_IF([test "$host_os" = linux-gnu],
-    AC_MSG_WARN([Capabilities support is strongly recommended for increased security.  See SECURITY for more information.])))
+have_cap="yes"
+AC_CHECK_LIB([cap], [cap_set_proc], [], [
+  have_cap="no"
+  AS_IF([test "$host_os" = linux-gnu], [
+    AC_MSG_WARN([Capabilities support is strongly recommended for increased security.  See SECURITY for more information.])
+  ])
+])
 
 # Enable ipinfo
 AC_ARG_WITH([ipinfo],
@@ -149,6 +167,15 @@ AS_IF([test "x$WANTS_IPV6" = "xyes"], [
   USES_IPV6=yes
 ])
 
+AC_ARG_ENABLE([braille],
+  [AS_HELP_STRING([--disable-braille], [Do not enable barille character graphs])],
+  [WANTS_BRAILLE=$enableval], [WANTS_BRAILLE=yes])
+
+AS_IF([test "x$WANTS_BRAILLE" = "xyes"], [
+  AC_DEFINE([ENABLE_BRAILLE], [1], [Define to enable uncode braille character graphs])
+  USES_BRAILLE=yes
+])
+
 AC_CHECK_FUNC([socket], [],
   [AC_CHECK_LIB([socket], [socket], [], [AC_MSG_ERROR([No socket library found])])])
 
@@ -262,15 +289,17 @@ AC_ARG_ENABLE([bash-completion],
 AM_CONDITIONAL([BUILD_BASH_COMPLETION], [test "x$enable_bash_completion" = xyes])
 echo "build options:"
 echo "--------------"
-echo "libasan :$with_libasan"
-echo "ipv6    :$USES_IPV6"
-echo "ipinfo  :$with_ipinfo"
-echo "ncurses :$with_ncurses"
-echo "gtk     :$with_gtk"
-echo "jansson :$with_jansson"
-echo "cap     :$have_cap"
-echo "libs    :$LIBS"
-echo "cflags  :$CFLAGS"
+echo "libasan  :$with_libasan"
+echo "ipv6     :$USES_IPV6"
+echo "braille  :$USES_BRAILLE"
+echo "ipinfo   :$with_ipinfo"
+echo "ncursesw :$with_ncursesw"
+echo "ncurses  :$with_ncurses"
+echo "gtk      :$with_gtk"
+echo "jansson  :$with_jansson"
+echo "cap      :$have_cap"
+echo "libs     :$LIBS"
+echo "cflags   :$CFLAGS"
 echo "--------------"
 # Prepare config.h, Makefile, and output them.
 AC_CONFIG_HEADERS([config.h])
diff --git a/man/mtr-packet.8.in b/man/mtr-packet.8.in
index 090c3ad..120362c 100644
--- a/man/mtr-packet.8.in
+++ b/man/mtr-packet.8.in
@@ -317,14 +317,18 @@ label, and so on.  The values are provided in this order:
 .IR ttl .
 .HP 7
 .TP
-.B no-route
-There was no route to the host used in a
+.B no-route-network
+.B no-route-host
+There was no route to the network or the host itself for the
 .B send-probe
-request.
+request used to reach the host.
 .TP
 .B network-down
 A probe could not be sent because the network is down.
 .TP
+.B host-down
+A probe could not be sent because the host is down.
+.TP
 .B probes-exhausted
 A probe could not be sent because there are already too many unresolved
 probes in flight.
diff --git a/man/mtr.8.in b/man/mtr.8.in
index 80c4790..47c0cfe 100644
--- a/man/mtr.8.in
+++ b/man/mtr.8.in
@@ -56,6 +56,12 @@ mtr \- a network diagnostic tool
 .B \-\-aslookup\c
 ]
 [\c
+.BI \-\-ipinfo_provider4 \ DOMAIN\c
+]
+[\c
+.BI \-\-ipinfo_provider6 \ DOMAIN\c
+]
+[\c
 .BI \-i \ INTERVAL\c
 ]
 [\c
@@ -92,6 +98,9 @@ mtr \- a network diagnostic tool
 .BI \-U \ MAX\-UNKNOWN\c
 ]
 [\c
+.BI \-E \ MAX\-DISPLAY\-PATH\c
+]
+[\c
 .B \-\-udp\c
 ]
 [\c
@@ -347,6 +356,14 @@ Example (columns to the right not shown for clarity):
 7. AS1850  www.isnic.is
 .fi
 .TP
+.B \-\-ipinfo_provider4 \fIDOMAIN
+Provider for IPv4 AS lookups.  Defaults to origin.asn.cymru.com.
+.fi
+.TP
+.B \-\-ipinfo_provider6 \fIDOMAIN
+Provider for IPv6 AS lookups.  Defaults to origin6.asn.cymru.com.
+.fi
+.TP
 .B \-i \fISECONDS\fR, \fB\-\-interval \fISECONDS
 Use this option to specify the positive number of seconds between ICMP
 ECHO requests.  The default value for this parameter is one second.  The
@@ -407,6 +424,9 @@ probe.  Default is 30.
 .B \-U \fINUM\fR, \fB\-\-max-unknown \fINUM
 Specifies the maximum unknown host. Default is 5.
 .TP
+.B \-E \fINUM\fR, \fB\-\-max-display-path \fINUM
+Specifies the maximum number of ECMP paths to display. Default is 8.
+.TP
 .B \-u\fR, \fB\-\-udp
 Use UDP datagrams instead of ICMP ECHO.
 .TP
@@ -433,7 +453,7 @@ a short interval, will use up a lot of file descriptors.
 Set the mark for each packet sent through this socket similar to the
 netfilter MARK target but socket-based.
 .I MARK
-is 32 unsigned integer.  See
+is a 32-bit unsigned integer.  See
 .BR socket (7)
 for full description of this socket option.
 .SH ENVIRONMENT
diff --git a/packet/command.c b/packet/command.c
index b624ea6..a708841 100644
--- a/packet/command.c
+++ b/packet/command.c
@@ -189,6 +189,11 @@ bool decode_probe_argument(
         param->local_address = value;
     }
 
+    /*  Device name to send from  */
+    if (!strcmp(name, "local-device")) {
+        param->local_device = value;
+    }
+
     /*  Protocol for the probe  */
     if (!strcmp(name, "protocol")) {
         if (!strcmp(value, "icmp")) {
diff --git a/packet/construct_unix.c b/packet/construct_unix.c
index 644536d..95fefba 100644
--- a/packet/construct_unix.c
+++ b/packet/construct_unix.c
@@ -18,6 +18,8 @@
 
 #include "construct_unix.h"
 
+#include "utils.h"
+
 #include <errno.h>
 #include <stdio.h>
 #include <string.h>
@@ -32,6 +34,10 @@
 #define SOL_IP IPPROTO_IP
 #endif
 
+#ifdef HAVE_LIBCAP
+#include <sys/capability.h>
+#endif
+
 /*  A source of data for computing a checksum  */
 struct checksum_source_t {
     const void *data;
@@ -71,19 +77,6 @@ uint16_t compute_checksum(
     return (~sum & 0xffff);
 }
 
-/*  Encode the IP header length field in the order required by the OS.  */
-static
-uint16_t length_byte_swap(
-    const struct net_state_t *net_state,
-    uint16_t length)
-{
-    if (net_state->platform.ip_length_host_order) {
-        return length;
-    } else {
-        return htons(length);
-    }
-}
-
 /*  Construct a combined sockaddr from a source address and source port  */
 static
 void construct_addr_port(
@@ -95,38 +88,9 @@ void construct_addr_port(
     *sockaddr_port_offset(addr_with_port) = htons(port);
 }
 
-/*  Construct a header for IP version 4  */
-static
-void construct_ip4_header(
-    const struct net_state_t *net_state,
-    const struct probe_t *probe,
-    char *packet_buffer,
-    int packet_size,
-    const struct probe_param_t *param)
-{
-    struct IPHeader *ip;
-
-    ip = (struct IPHeader *) &packet_buffer[0];
-
-    memset(ip, 0, sizeof(struct IPHeader));
-
-    ip->version = 0x45;
-    ip->tos = param->type_of_service;
-    ip->len = length_byte_swap(net_state, packet_size);
-    ip->ttl = param->ttl;
-    ip->protocol = param->protocol;
-//    ip->id = htons(getpid());
-    memcpy(&ip->saddr,
-           sockaddr_addr_offset(&probe->local_addr),
-           sockaddr_addr_size(&probe->local_addr));
-    memcpy(&ip->daddr,
-           sockaddr_addr_offset(&probe->remote_addr),
-           sockaddr_addr_size(&probe->remote_addr));
-}
-
 /*  Construct an ICMP header for IPv4  */
 static
-void construct_icmp4_header(
+int construct_icmp4_packet(
     const struct net_state_t *net_state,
     struct probe_t *probe,
     char *packet_buffer,
@@ -134,22 +98,17 @@ void construct_icmp4_header(
     const struct probe_param_t *param)
 {
     struct ICMPHeader *icmp;
-    int icmp_size;
 
-    if (net_state->platform.ip4_socket_raw) {
-        icmp = (struct ICMPHeader *) &packet_buffer[sizeof(struct IPHeader)];
-        icmp_size = packet_size - sizeof(struct IPHeader);
-    } else {
-        icmp = (struct ICMPHeader *) &packet_buffer[0];
-        icmp_size = packet_size;
-    }
+    icmp = (struct ICMPHeader *) packet_buffer;
 
     memset(icmp, 0, sizeof(struct ICMPHeader));
 
     icmp->type = ICMP_ECHO;
     icmp->id = htons(getpid());
     icmp->sequence = htons(probe->sequence);
-    icmp->checksum = htons(compute_checksum(icmp, icmp_size));
+    icmp->checksum = htons(compute_checksum(icmp, packet_size));
+
+    return 0;
 }
 
 /*  Construct an ICMP header for IPv6  */
@@ -238,7 +197,7 @@ int udp4_checksum(void *pheader, void *udata, int psize, int dsize,
     with the probe.
 */
 static
-void construct_udp4_header(
+int construct_udp4_packet(
     const struct net_state_t *net_state,
     struct probe_t *probe,
     char *packet_buffer,
@@ -248,13 +207,8 @@ void construct_udp4_header(
     struct UDPHeader *udp;
     int udp_size;
 
-    if (net_state->platform.ip4_socket_raw) {
-        udp = (struct UDPHeader *) &packet_buffer[sizeof(struct IPHeader)];
-        udp_size = packet_size - sizeof(struct IPHeader);
-    } else {
-        udp = (struct UDPHeader *) &packet_buffer[0];
-        udp_size = packet_size;
-    }
+    udp = (struct UDPHeader *) packet_buffer;
+    udp_size = packet_size;
 
     memset(udp, 0, sizeof(struct UDPHeader));
 
@@ -283,6 +237,8 @@ void construct_udp4_header(
     *checksum_off = htons(udp4_checksum(&udph, udp,
                                         sizeof(struct UDPPseudoHeader),
                                         udp_size, udp->checksum != 0));
+
+    return 0;
 }
 
 /*  Construct a header for UDPv6 probes  */
@@ -306,21 +262,124 @@ int construct_udp6_packet(
     set_udp_ports(udp, probe, param);
     udp->length = htons(udp_size);
 
-    if (net_state->platform.ip6_socket_raw) {
-        /*
-           Instruct the kernel to put the pseudoheader checksum into the
-           UDP header, this is only needed when using RAW socket.
-         */
-        int chksum_offset = (char *) &udp->checksum - (char *) udp;
-        if (setsockopt(udp_socket, IPPROTO_IPV6,
-                       IPV6_CHECKSUM, &chksum_offset, sizeof(int))) {
-            return -1;
-        }
-    }
+    struct IP6PseudoHeader udph = {
+        .zero = {0,0,0},
+        .protocol = 17,
+        .len = udp->length
+    };
+    memcpy(udph.saddr, sockaddr_addr_offset(&probe->local_addr), 16);
+    memcpy(udph.daddr, sockaddr_addr_offset(&probe->remote_addr), 16);
 
+    /* get position to write checksum */
+    uint16_t *checksum_off = &udp->checksum;
+
+    if (udp->checksum != 0)
+    { /* checksum is sequence number - correct the payload to match the checksum
+         checksum_off is udp payload */
+        checksum_off = (uint16_t *)&packet_buffer[sizeof(struct UDPHeader)];
+    }
+    *checksum_off = htons(udp4_checksum(&udph, udp,
+                                        sizeof(struct IP6PseudoHeader),
+                                        udp_size, udp->checksum != 0));
     return 0;
 }
 
+/*
+    This defines a common interface which elevates privileges on
+    platforms with LIBCAP and acts as a NOOP on platforms without
+    it.
+*/
+#ifdef HAVE_LIBCAP
+
+typedef cap_value_t mayadd_cap_value_t;
+#define MAYADD_CAP_NET_RAW CAP_NET_RAW
+#define MAYADD_CAP_NET_ADMIN CAP_NET_ADMIN
+
+#else /* ifdef HAVE_LIBCAP */
+
+typedef int mayadd_cap_value_t;
+#define MAYADD_CAP_NET_RAW ((mayadd_cap_value_t) 0)
+#define MAYADD_CAP_NET_ADMIN ((mayadd_cap_value_t) 0)
+
+#endif /* ifdef HAVE_LIBCAP */
+
+UNUSED static
+int set_privileged_socket_opt(int socket, int option_name,
+    void const * option_value, socklen_t option_len,
+    UNUSED mayadd_cap_value_t required_cap) {
+
+    int result = -1;
+
+    // Add CAP_NET_ADMIN to the effective set if libcap is present
+#ifdef HAVE_LIBCAP
+    static cap_value_t cap_add[1];
+    cap_add[0] = required_cap;
+
+    // Get the capabilities of the current process
+    cap_t cap = cap_get_proc();
+    if (cap == NULL) {
+        goto cleanup_and_exit;
+    }
+
+    // Set the required capability flag
+    if (cap_set_flag(cap, CAP_EFFECTIVE, N_ENTRIES(cap_add), cap_add,
+        CAP_SET)) {
+        goto cleanup_and_exit;
+    }
+
+    // Apply the modified capabilities to the current process
+    if (cap_set_proc(cap)) {
+        goto cleanup_and_exit;
+    }
+#endif /* ifdef HAVE_LIBCAP */
+
+    // Set the socket mark
+    int set_sock_err = setsockopt(socket, SOL_SOCKET, option_name, option_value, option_len);
+
+    // Drop CAP_NET_ADMIN from the effective set if libcap is present
+#ifdef HAVE_LIBCAP
+
+    // Clear the CAP_NET_ADMIN capability flag
+    if (cap_set_flag(cap, CAP_EFFECTIVE, N_ENTRIES(cap_add), cap_add,
+        CAP_CLEAR)) {
+        goto cleanup_and_exit;
+    }
+
+    // Apply the modified capabilities to the current process
+    if (cap_set_proc(cap)) {
+        goto cleanup_and_exit;
+    }
+#endif /* ifdef HAVE_LIBCAP */
+
+    if(!set_sock_err) {
+        result = 0; // Success
+    }
+
+#ifdef HAVE_LIBCAP
+cleanup_and_exit:
+    cap_free(cap);
+#endif /* ifdef HAVE_LIBCAP */
+
+    return result;
+}
+
+/* Set the socket mark */
+#ifdef SO_MARK
+static
+int set_socket_mark(int socket, unsigned int mark) {
+    return set_privileged_socket_opt(socket, SO_MARK, &mark, sizeof(mark),
+        MAYADD_CAP_NET_ADMIN);
+}
+#endif /* ifdef SO_MARK */
+
+#ifdef SO_BINDTODEVICE
+static
+int set_bind_to_device(int socket, char const * device) {
+    return set_privileged_socket_opt(socket, SO_BINDTODEVICE, device,
+            strlen(device), MAYADD_CAP_NET_RAW);
+}
+#endif /* ifdef SO_BINDTODEVICE */
+
 /*
     Set the socket options for an outgoing stream protocol socket based on
     the packet parameters.
@@ -384,8 +443,15 @@ int set_stream_socket_options(
     }
 #ifdef SO_MARK
     if (param->routing_mark) {
-        if (setsockopt(stream_socket, SOL_SOCKET,
-                       SO_MARK, &param->routing_mark, sizeof(int))) {
+        if (set_socket_mark(stream_socket, param->routing_mark)) {
+            return -1;
+        }
+    }
+#endif
+
+#ifdef SO_BINDTODEVICE
+    if (param->local_device) {
+        if (set_bind_to_device(stream_socket, param->local_device)) {
             return -1;
         }
     }
@@ -394,6 +460,7 @@ int set_stream_socket_options(
     return 0;
 }
 
+
 /*
     Open a TCP or SCTP socket, respecting the probe paramters as much as
     we can, and use it as an outgoing probe.
@@ -545,10 +612,10 @@ int construct_ip4_packet(
     int packet_size,
     const struct probe_param_t *param)
 {
-    int send_socket = net_state->platform.ip4_send_socket;
+    int send_socket;
     bool is_stream_protocol = false;
-    int tos, ttl, socket;
-    bool bind_send_socket = false;
+    int tos, ttl;
+    bool bind_send_socket = true;
     struct sockaddr_storage current_sockaddr;
     int current_sockaddr_len;
 
@@ -558,23 +625,34 @@ int construct_ip4_packet(
     } else if (param->protocol == IPPROTO_SCTP) {
         is_stream_protocol = true;
 #endif
-    } else {
+    } else if (param->protocol == IPPROTO_ICMP) {
         if (net_state->platform.ip4_socket_raw) {
-            construct_ip4_header(net_state, probe, packet_buffer, packet_size,
-                                  param);
+            send_socket = net_state->platform.icmp4_send_socket;
+        } else {
+            send_socket = net_state->platform.ip4_txrx_icmp_socket;
         }
-        if (param->protocol == IPPROTO_ICMP) {
-            construct_icmp4_header(net_state, probe, packet_buffer,
-                                   packet_size, param);
-        } else if (param->protocol == IPPROTO_UDP) {
-            construct_udp4_header(net_state, probe, packet_buffer,
-                                  packet_size, param);
+
+        if (construct_icmp4_packet
+            (net_state, probe, packet_buffer, packet_size, param)) {
+            return -1;
+        }
+    } else if (param->protocol == IPPROTO_UDP) {
+        if (net_state->platform.ip4_socket_raw) {
+            send_socket = net_state->platform.udp4_send_socket;
         } else {
-            errno = EINVAL;
+            send_socket = net_state->platform.ip4_txrx_udp_socket;
+        }
+
+        if (construct_udp4_packet
+            (net_state, probe, packet_buffer, packet_size, param)) {
             return -1;
         }
+    } else {
+        errno = EINVAL;
+        return -1;
     }
 
+
     if (is_stream_protocol) {
         send_socket =
             open_stream_socket(net_state, param->protocol, probe->sequence,
@@ -600,62 +678,66 @@ int construct_ip4_packet(
      */
 #ifdef SO_MARK
     if (param->routing_mark) {
-        if (setsockopt(send_socket, SOL_SOCKET,
-                       SO_MARK, &param->routing_mark, sizeof(int))) {
+        if (set_socket_mark(send_socket, param->routing_mark)) {
             return -1;
         }
     }
 #endif
 
-    /*
-       Bind src port when not using raw socket to pass in ICMP id, kernel
-       get ICMP id from src_port when using DGRAM socket.
-     */
-    if (!net_state->platform.ip4_socket_raw &&
-            param->protocol == IPPROTO_ICMP &&
-            !param->is_probing_byte_order) {
-        current_sockaddr_len = sizeof(struct sockaddr_in);
-        bind_send_socket = true;
-        socket = net_state->platform.ip4_txrx_icmp_socket;
-        if (getsockname(socket, (struct sockaddr *) &current_sockaddr,
-                        &current_sockaddr_len)) {
+#ifdef SO_BINDTODEVICE
+    if (param->local_device) {
+        if(set_bind_to_device(send_socket, param->local_device)) {
             return -1;
         }
-        struct sockaddr_in *sin_cur =
-            (struct sockaddr_in *) &current_sockaddr;
+    }
+#endif
 
-        /* avoid double bind */
-        if (sin_cur->sin_port) {
-            bind_send_socket = false;
+    /*
+       Check the current socket address, and if it is the same
+       as the source address we intend, we will skip the bind.
+       This is to accommodate Solaris, which, as of Solaris 11.3,
+       will return an EINVAL error on bind if the socket is already
+       bound, even if the same address is used.
+     */
+    current_sockaddr_len = sizeof(struct sockaddr_in);
+    if (getsockname(send_socket, (struct sockaddr *) &current_sockaddr,
+                    &current_sockaddr_len) == 0) {
+        struct sockaddr_in *sin_cur = (struct sockaddr_in *) &current_sockaddr;
+
+        if (net_state->platform.ip4_socket_raw) {
+            if (memcmp(&current_sockaddr,
+                       &probe->local_addr, sizeof(struct sockaddr_in)) == 0) {
+                bind_send_socket = false;
+            }
+        } else {
+            /* avoid double bind for DGRAM socket */
+            if (sin_cur->sin_port) {
+                bind_send_socket = false;
+            }
         }
     }
 
     /*  Bind to our local address  */
-    if (bind_send_socket && bind(socket, (struct sockaddr *)&probe->local_addr,
+    if (bind_send_socket && bind(send_socket, (struct sockaddr *)&probe->local_addr,
                 sizeof(struct sockaddr_in))) {
         return -1;
     }
 
-    /* set TOS and TTL for non-raw socket */
-    if (!net_state->platform.ip4_socket_raw && !param->is_probing_byte_order) {
-        if (param->protocol == IPPROTO_ICMP) {
-            socket = net_state->platform.ip4_txrx_icmp_socket;
-        } else if (param->protocol == IPPROTO_UDP) {
-            socket = net_state->platform.ip4_txrx_udp_socket;
-        } else {
-            return 0;
-        }
-        tos = param->type_of_service;
-        if (setsockopt(socket, SOL_IP, IP_TOS, &tos, sizeof(int))) {
-            return -1;
-        }
-        ttl = param->ttl;
-        if (setsockopt(socket, SOL_IP, IP_TTL,
-                       &ttl, sizeof(int)) == -1) {
-            return -1;
-        }
+    /*  Set the type of service  */
+    tos = param->type_of_service;
+    if (setsockopt(send_socket, SOL_IP, IP_TOS, &tos, sizeof(int))) {
+        return -1;
     }
 
+    /*  Set the time-to-live  */
+    ttl = param->ttl;
+    if (setsockopt(send_socket, SOL_IP, IP_TTL,
+                   &ttl, sizeof(int)) == -1) {
+        return -1;
+    }
+
+
+
     return 0;
 }
 
@@ -767,9 +849,17 @@ int construct_ip6_packet(
     }
 #ifdef SO_MARK
     if (param->routing_mark) {
+        if (set_socket_mark(send_socket, param->routing_mark)) {
+            return -1;
+        }
+    }
+#endif
+
+#ifdef SO_BINDTODEVICE
+    if (param->local_device) {
         if (setsockopt(send_socket,
-                       SOL_SOCKET, SO_MARK, &param->routing_mark,
-                       sizeof(int))) {
+                       SOL_SOCKET, SO_BINDTODEVICE, param->local_device,
+                       strlen(param->local_device))) {
             return -1;
         }
     }
diff --git a/packet/packet.c b/packet/packet.c
index 3821d91..5f3a39b 100644
--- a/packet/packet.c
+++ b/packet/packet.c
@@ -33,17 +33,94 @@
 #include <sys/capability.h>
 #endif
 
+#include "utils.h"
+
 #include "wait.h"
 
+#ifdef HAVE_LIBCAP
+static
+void drop_excess_capabilities() {
+
+    /*
+      By default, the root user has all capabilities, which poses a security risk.
+
+      Some capabilities must be retained in the permitted set so that it can be added
+      to the effective set when needed.
+    */
+    cap_value_t cap_permitted[] = {
+#ifdef SO_MARK
+        /*
+          CAP_NET_ADMIN is needed to set the routing mark (SO_MARK) on a socket
+        */
+        CAP_NET_ADMIN,
+#endif /* ifdef SOMARK */
+
+#ifdef SO_BINDTODEVICE
+        /*
+          The CAP_NET_RAW capability is necessary for binding to a network device using
+          the SO_BINDTODEVICE socket option. Although this capability is not needed for
+          the initial bind operation, it is required when calling setsockopt after data has
+          been sent.
+
+          Given the current architecture, the socket is re-bound to the device every time
+          a probe is sent. Therefore, CAP_NET_RAW is required when specifying an interface
+          using the -I or --interface options.
+        */
+        CAP_NET_RAW,
+#endif /* ifdef SO_BINDTODEVICE */
+    };
+
+    cap_t current_cap = cap_get_proc();
+    cap_t wanted_cap = cap_get_proc();
+
+    if(!current_cap || !wanted_cap) {
+        goto pcap_error;
+    }
+
+    // Clear all capabilities from the 'wanted_cap' set
+    if(cap_clear(wanted_cap)) {
+        goto pcap_error;
+    }
+
+    // Retain only the necessary capabilities defined in 'cap_permitted' in the permitted set.
+    // This approach ensures the principle of least privilege.
+    // If the user has dropped capabilities, the code assumes those features will not be needed.
+    for(unsigned i = 0; i < N_ENTRIES(cap_permitted); i++) {
+        cap_flag_value_t is_set;
+
+        if(cap_get_flag(current_cap, cap_permitted[i], CAP_PERMITTED, &is_set)) {
+            goto pcap_error;
+        }
+
+        if(cap_set_flag(wanted_cap, CAP_PERMITTED, 1, &cap_permitted[i], is_set)) {
+            goto pcap_error;
+        }
+    }
+
+    // Update the process's capabilities to match 'wanted_cap'
+    if(cap_set_proc(wanted_cap)) {
+        goto pcap_error;
+    }
+
+    if(cap_free(current_cap) || cap_free(wanted_cap)) {
+        goto pcap_error;
+    }
+
+    return;
+
+pcap_error:
+
+    cap_free(current_cap);
+    cap_free(wanted_cap);
+    error(EXIT_FAILURE, errno, "Failed to drop capabilities");
+}
+#endif /* ifdef HAVE_LIBCAP */
+
 /*  Drop SUID privileges.  To be used after acquiring raw sockets.  */
 static
 int drop_elevated_permissions(
     void)
 {
-#ifdef HAVE_LIBCAP
-    cap_t cap;
-#endif
-
     /*  Drop any suid permissions granted  */
     if (setgid(getgid()) || setuid(getuid())) {
         return -1;
@@ -55,19 +132,9 @@ int drop_elevated_permissions(
 
     /*
        Drop all process capabilities.
-       This will revoke anything granted by a commandline 'setcap'
      */
 #ifdef HAVE_LIBCAP
-    cap = cap_get_proc();
-    if (cap == NULL) {
-        return -1;
-    }
-    if (cap_clear(cap)) {
-        return -1;
-    }
-    if (cap_set_proc(cap)) {
-        return -1;
-    }
+    drop_excess_capabilities();
 #endif
 
     return 0;
diff --git a/packet/probe.c b/packet/probe.c
index 6581015..4b265af 100644
--- a/packet/probe.c
+++ b/packet/probe.c
@@ -264,7 +264,8 @@ void respond_to_probe(
     if (icmp_type == ICMP_TIME_EXCEEDED) {
         result = "ttl-expired";
     } else if (icmp_type == ICMP_DEST_UNREACH) {
-        result = "no-route";
+        /* XXX icmphdr->code is not known here, so assume that host is unreachable */
+        result = "no-route-host";
     } else {
         assert(icmp_type == ICMP_ECHOREPLY);
         result = "reply";
diff --git a/packet/probe.h b/packet/probe.h
index e7b8bee..d0ab0cf 100644
--- a/packet/probe.h
+++ b/packet/probe.h
@@ -34,7 +34,7 @@
 #include "probe_unix.h"
 #endif
 
-#define MAX_PROBES 1024
+#define MAX_PROBES 10240
 
 /*  Use the "jumbo" frame size as the max packet size  */
 #define PACKET_BUFFER_SIZE 9000
@@ -53,6 +53,9 @@ struct probe_param_t {
     /*  The local address from which to send probes  */
     const char *local_address;
 
+    /*  The local device from which to send probes  */
+    const char *local_device;
+
     /*  Protocol for the probe, using the IPPROTO_* defines  */
     int protocol;
 
@@ -66,7 +69,7 @@ struct probe_param_t {
     int type_of_service;
 
     /*  The packet "mark" used for mark-based routing on Linux  */
-    int routing_mark;
+    uint32_t routing_mark;
 
     /*  Time to live for the transmitted probe  */
     int ttl;
diff --git a/packet/probe_cygwin.c b/packet/probe_cygwin.c
index f41e514..d6d2b7f 100644
--- a/packet/probe_cygwin.c
+++ b/packet/probe_cygwin.c
@@ -136,8 +136,14 @@ void init_net_state(
     net_state->platform.thread_out_pipe_read = out_pipe[0];
     net_state->platform.thread_out_pipe_write = out_pipe[1];
 
-    net_state->platform.thread_in_pipe_read_handle =
-        (HANDLE)get_osfhandle(in_pipe[0]);
+    InitializeCriticalSection(&net_state->platform.pending_request_cs);
+    net_state->platform.pending_request_count = 0;
+    net_state->platform.pending_request_event =
+        CreateEvent(NULL, TRUE, FALSE, NULL);
+
+    if (net_state->platform.pending_request_event == NULL) {
+        error(EXIT_FAILURE, errno, "Failure creating request event");
+    }
 
     /*
         The read on the out pipe needs to be nonblocking because
@@ -281,7 +287,7 @@ void WINAPI on_icmp_reply(
             remote_addr6->sin6_family = AF_INET6;
             remote_addr6->sin6_port = 0;
             remote_addr6->sin6_flowinfo = 0;
-            memcpy(&remote_addr6->sin6_addr, reply6->AddressBits,
+            memcpy(&remote_addr6->sin6_addr, reply6->Address.sin6_addr,
                    sizeof(struct in6_addr));
             remote_addr6->sin6_scope_id = 0;
         }
@@ -468,109 +474,116 @@ void icmp_handle_probe_request(struct icmp_thread_request_t *request)
 }
 
 /*
-    The main loop of the ICMP service thread.  The loop starts
-    an overlapped read on the incoming request pipe, then waits
-    in an alertable wait for that read to complete.  Because
-    the wait is alertable, ICMP probes can complete through
-    APCs in that wait.
+    Write the next thread request to the request pipe.
+    Update the count of pending requests and set the event
+    indicating that requests are present.
 */
 static
-DWORD WINAPI icmp_service_thread(LPVOID param) {
-    struct net_state_t *net_state;
-    struct icmp_thread_request_t *request;
-    DWORD wait_status;
-    OVERLAPPED overlapped;
-    HANDLE event;
-    BOOL success;
-    bool read_pending;
-    DWORD read_count;
-    int err;
+void send_thread_request(
+    struct net_state_t *net_state,
+    struct icmp_thread_request_t *request)
+{
+    int byte_count;
+    byte_count = write(
+        net_state->platform.thread_in_pipe_write,
+        &request,
+        sizeof(struct icmp_thread_request_t *));
 
-    /*
-        We need an event to signal completion of reads from the request
-        pipe.
-    */
-    event = CreateEvent(NULL, TRUE, FALSE, NULL);
-    if (event == NULL) {
-        error_win(
-            EXIT_FAILURE, GetLastError(),
-            "failure creating ICMP thread event");
+    if (byte_count == -1) {
+        error(
+            EXIT_FAILURE, errno,
+            "failure writing to probe request queue");
     }
 
-    net_state = (struct net_state_t *)param;
-    read_pending = false;
-    while (true) {
-        /*
-            Start a new read on the request pipe if none is
-            currently pending.
-        */
-        if (!read_pending) {
-            request = NULL;
-
-            ResetEvent(event);
-
-            memset(&overlapped, 0, sizeof(OVERLAPPED));
-            overlapped.hEvent = event;
-
-            success = ReadFile(
-                net_state->platform.thread_in_pipe_read_handle,
-                &request,
-                sizeof(struct icmp_thread_request_t *),
-                NULL,
-                &overlapped);
+    EnterCriticalSection(&net_state->platform.pending_request_cs);
+    {
+        net_state->platform.pending_request_count++;
+        SetEvent(net_state->platform.pending_request_event);
+    }
+    LeaveCriticalSection(&net_state->platform.pending_request_cs);
+}
 
-            if (!success) {
-                err = GetLastError();
+/*
+    Read the next thread request from the pipe, if any are pending.
+    If it is the last request in the queue, reset the pending
+    request event.
 
-                if (err != ERROR_IO_PENDING) {
-                    error_win(
-                        EXIT_FAILURE, err,
-                        "failure starting overlapped thread pipe read");
-                }
+    If no requests are pending, return NULL.
+*/
+static
+struct icmp_thread_request_t *receive_thread_request(
+    struct net_state_t *net_state)
+{
+    struct icmp_thread_request_t *request;
+    int byte_count;
+    bool pending_request;
+
+    EnterCriticalSection(&net_state->platform.pending_request_cs);
+    {
+        if (net_state->platform.pending_request_count > 0) {
+            pending_request = true;
+            net_state->platform.pending_request_count--;
+            if (net_state->platform.pending_request_count == 0) {
+                ResetEvent(net_state->platform.pending_request_event);
             }
-
-            read_pending = true;
+        } else {
+            pending_request = false;
         }
+    }
+    LeaveCriticalSection(&net_state->platform.pending_request_cs);
 
-        /*
-            Wait for either the request read to complete, or
-            an APC which completes an ICMP probe.
-        */
-        wait_status = WaitForSingleObjectEx(
-            event,
-            INFINITE,
-            TRUE);
+    if (!pending_request) {
+        return NULL;
+    }
 
-        /*
-            If the event we waited on has been signalled, read
-            the request from the pipe.
-        */
-        if (wait_status == WAIT_OBJECT_0) {
-            read_pending = false;
-
-            success = GetOverlappedResult(
-                net_state->platform.thread_in_pipe_read_handle,
-                &overlapped,
-                &read_count,
-                FALSE);
-
-            if (!success) {
-                error_win(
-                    EXIT_FAILURE, GetLastError(),
-                    "failure completing overlapped thread pipe read");
-            }
+    byte_count = read(
+        net_state->platform.thread_in_pipe_read,
+        &request,
+        sizeof(struct icmp_thread_request_t *));
 
-            if (read_count == 0) {
-                continue;
-            }
+    if (byte_count == -1) {
+        error(
+            EXIT_FAILURE,
+            errno,
+            "failure reading probe request queue");
+    }
+
+    assert(byte_count == sizeof(struct icmp_thread_request_t *));
+
+    return request;
+}
 
-            assert(
-                read_count == sizeof(struct icmp_thread_request_t *));
+/*
+    The main loop of the ICMP service thread.  The loop starts
+    an overlapped read on the incoming request pipe, then waits
+    in an alertable wait for that read to complete.  Because
+    the wait is alertable, ICMP probes can complete through
+    APCs in that wait.
+*/
+static
+DWORD WINAPI icmp_service_thread(LPVOID param) {
+    struct net_state_t *net_state;
+    struct icmp_thread_request_t *request;
 
+    net_state = (struct net_state_t *)param;
+    while (true) {
+        request = receive_thread_request(net_state);
+        if (request != NULL) {
             /*  Start the new probe from the request  */
             icmp_handle_probe_request(request);
+        } else {
+            /*
+                Wait for either a request to be queued or for
+                an APC which completes an ICMP probe.
+            */
+            WaitForSingleObjectEx(
+                net_state->platform.pending_request_event,
+                INFINITE,
+                TRUE);
         }
     }
+
+    return 0;
 }
 
 /*
@@ -587,7 +600,6 @@ void queue_thread_request(
     struct sockaddr_storage *src_sockaddr)
 {
     struct icmp_thread_request_t *request;
-    int byte_count;
 
     request = malloc(sizeof(struct icmp_thread_request_t));
     if (request == NULL) {
@@ -610,16 +622,7 @@ void queue_thread_request(
         The ownership of the request is passed to the ICMP thread
         through the pipe.
     */
-    byte_count = write(
-        net_state->platform.thread_in_pipe_write,
-        &request,
-        sizeof(struct icmp_thread_request_t *));
-
-    if (byte_count == -1) {
-        error(
-            EXIT_FAILURE, errno,
-            "failure writing to probe request queue");
-    }
+    send_thread_request(net_state, request);
 }
 
 /*  Decode the probe parameters and send a probe  */
diff --git a/packet/probe_cygwin.h b/packet/probe_cygwin.h
index eec001f..1e9fde7 100644
--- a/packet/probe_cygwin.h
+++ b/packet/probe_cygwin.h
@@ -19,32 +19,14 @@
 #ifndef PROBE_CYGWIN_H
 #define PROBE_CYGWIN_H
 
+#include <cygwin/in6.h>
+typedef struct in6_addr IN6_ADDR, *PIN6_ADDR, *LPIN6_ADDR;
+
 #include <arpa/inet.h>
 #include <windows.h>
 #include <iphlpapi.h>
 #include <icmpapi.h>
 
-/*
-    This should be in the Windows headers, but is missing from
-    Cygwin's Windows headers.
-*/
-typedef struct icmpv6_echo_reply_lh {
-    /*
-       Although Windows uses an IPV6_ADDRESS_EX here, we are using uint8_t
-       fields to avoid structure padding differences between gcc and
-       Visual C++.  (gcc wants to align the flow info to a 4 byte boundary,
-       and Windows uses it unaligned.)
-     */
-    uint8_t PortBits[2];
-    uint8_t FlowInfoBits[4];
-    uint8_t AddressBits[16];
-    uint8_t ScopeIdBits[4];
-
-    ULONG Status;
-    unsigned int RoundTripTime;
-} ICMPV6_ECHO_REPLY,
-*PICMPV6_ECHO_REPLY;
-
 /*
 	Windows requires an echo reply structure for each in-flight
 	ICMP probe.
@@ -67,9 +49,16 @@ struct net_state_platform_t {
     bool ip4_socket_raw;
     bool ip6_socket_raw;
 
-    HANDLE thread_in_pipe_read_handle;
     int thread_in_pipe_read, thread_in_pipe_write;
     int thread_out_pipe_read, thread_out_pipe_write;
+
+    CRITICAL_SECTION pending_request_cs;
+
+    /*  Guarded by the critical section.  */
+    unsigned int pending_request_count;
+
+    /*  Set when any requests are pending.  */
+    HANDLE pending_request_event;
 };
 
 /*
diff --git a/packet/probe_unix.c b/packet/probe_unix.c
index f7f393f..7362d6d 100644
--- a/packet/probe_unix.c
+++ b/packet/probe_unix.c
@@ -87,16 +87,21 @@ int send_packet(
     } else if (sockaddr->ss_family == AF_INET) {
         sockaddr_length = sizeof(struct sockaddr_in);
 
-        if (net_state->platform.ip4_socket_raw) {
-            send_socket = net_state->platform.ip4_send_socket;
-        } else {
-            if (param->protocol == IPPROTO_ICMP) {
-                if (param->is_probing_byte_order) {
-                    send_socket = net_state->platform.ip4_tmp_icmp_socket;;
-                } else {
-                    send_socket = net_state->platform.ip4_txrx_icmp_socket;
-                }
-            } else if (param->protocol == IPPROTO_UDP) {
+        if (param->protocol == IPPROTO_ICMP) {
+            if (net_state->platform.ip4_socket_raw) {
+                send_socket = net_state->platform.icmp4_send_socket;
+            } else {
+                send_socket = net_state->platform.ip4_txrx_icmp_socket;
+            }
+        } else if (param->protocol == IPPROTO_UDP) {
+            if (net_state->platform.ip4_socket_raw) {
+                send_socket = net_state->platform.udp4_send_socket;
+                /* we got a ipv4 udp raw socket
+                 * the remote port is in the payload
+                 * we do not set in the sockaddr
+                 */
+                *sockaddr_port_offset(&dst) = 0;
+            } else {
                 send_socket = net_state->platform.ip4_txrx_udp_socket;
                 if (param->dest_port) {
                     *sockaddr_port_offset(&dst) = htons(param->dest_port);
@@ -105,6 +110,7 @@ int send_packet(
                 }
             }
         }
+
     }
 
     if (send_socket == 0) {
@@ -236,26 +242,19 @@ static
 int open_ip4_sockets_raw(
     struct net_state_t *net_state)
 {
-    int send_socket;
+    int send_socket_icmp;
+    int send_socket_udp;
     int recv_socket;
-    int trueopt = 1;
 
-    send_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
-    if (send_socket == -1) {
-        send_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
-        if (send_socket == -1) {
-            return -1;
-        }
+    send_socket_icmp = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+    if (send_socket_icmp == -1) {
+        return -1;
     }
 
-    /*
-       We will be including the IP header in transmitted packets.
-       Linux doesn't require this, but BSD derived network stacks do.
-     */
-    if (setsockopt
-        (send_socket, IPPROTO_IP, IP_HDRINCL, &trueopt, sizeof(int))) {
+    send_socket_udp = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
+    if (send_socket_udp == -1) {
+        close(send_socket_icmp);
 
-        close(send_socket);
         return -1;
     }
 
@@ -265,13 +264,15 @@ int open_ip4_sockets_raw(
      */
     recv_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
     if (recv_socket == -1) {
-        close(send_socket);
+        close(send_socket_icmp);
+        close(send_socket_udp);
         return -1;
     }
 
     net_state->platform.ip4_present = true;
     net_state->platform.ip4_socket_raw = true;
-    net_state->platform.ip4_send_socket = send_socket;
+    net_state->platform.icmp4_send_socket = send_socket_icmp;
+    net_state->platform.udp4_send_socket = send_socket_udp;
     net_state->platform.ip4_recv_socket = recv_socket;
 
     return 0;
@@ -295,6 +296,7 @@ int open_ip4_sockets_dgram(
     }
 #ifdef HAVE_LINUX_ERRQUEUE_H
     if (setsockopt(icmp_socket, SOL_IP, IP_RECVERR, &val, sizeof(val)) < 0) {
+        close(icmp_socket);
         return -1;
     }
 #endif
@@ -385,6 +387,7 @@ int open_ip6_sockets_dgram(
     }
 #ifdef HAVE_LINUX_ERRQUEUE_H
     if (setsockopt(icmp_socket, SOL_IPV6, IPV6_RECVERR, &val, sizeof(val)) < 0) {
+        close(icmp_socket);
         return -1;
     }
 #endif
@@ -533,16 +536,20 @@ void report_packet_error(
         printf("%d invalid-argument\n", command_token);
     } else if (errno == ENETDOWN) {
         printf("%d network-down\n", command_token);
+    } else if (errno == EHOSTDOWN) {
+        printf("%d host-down\n", command_token);
     } else if (errno == ENETUNREACH) {
-        printf("%d no-route\n", command_token);
+        printf("%d no-route-network\n", command_token);
     } else if (errno == EHOSTUNREACH) {
-        printf("%d no-route\n", command_token);
+        printf("%d no-route-host\n", command_token);
     } else if (errno == EPERM) {
         printf("%d permission-denied\n", command_token);
     } else if (errno == EADDRINUSE) {
         printf("%d address-in-use\n", command_token);
     } else if (errno == EADDRNOTAVAIL) {
         printf("%d address-not-available\n", command_token);
+    } else if (errno == ETIMEDOUT) {
+        printf("%d wait-tcp-respone-timeout\n", command_token);
     } else {
         printf("%d unexpected-error errno %d\n", command_token, errno);
     }
diff --git a/packet/probe_unix.h b/packet/probe_unix.h
index f217a66..2ba2fac 100644
--- a/packet/probe_unix.h
+++ b/packet/probe_unix.h
@@ -25,7 +25,7 @@
 #endif
 
 /*  The range of local port numbers to use for probes  */
-#define MIN_PORT 33000
+#define MIN_PORT 33434
 #define MAX_PORT 65535
 
 /*  We need to track the transmission and timeouts on Unix systems  */
@@ -54,8 +54,11 @@ struct net_state_platform_t {
     /* true if ipv6 socket is raw socket */
     bool ip6_socket_raw;
 
-    /*  Socket used to send raw IPv4 packets  */
-    int ip4_send_socket;
+    /*  Send socket for ICMPv6 packets  */
+    int icmp4_send_socket;
+
+    /*  Send socket for UDPv6 packets  */
+    int udp4_send_socket;
 
     /*  Socket used to receive IPv4 ICMP replies  */
     int ip4_recv_socket;
diff --git a/packet/utils.h b/packet/utils.h
new file mode 100644
index 0000000..6e5ca8c
--- /dev/null
+++ b/packet/utils.h
@@ -0,0 +1,14 @@
+#ifndef _UTILS_H
+#define _UTILS_H
+
+// Fend off -Wunused-parameter
+#if defined(__GNUC__)
+# define UNUSED __attribute__((__unused__))
+#else
+# define UNUSED
+#endif
+
+// Number of entries in a fixed-length array
+#define N_ENTRIES(x) (sizeof(x) / sizeof(*x))
+
+#endif
diff --git a/test/linux/netem.py b/test/linux/netem.py
new file mode 100644
index 0000000..fb74468
--- /dev/null
+++ b/test/linux/netem.py
@@ -0,0 +1,700 @@
+'''
+MtrNetEm - a small network emulation library
+
+Description
+-----------
+This small, self-contained Python library serves as a high-level API for the
+creation and management of virtual network topologies in a Linux environment.
+By leveraging Linux's networking capabilities, it allows for the dynamic
+establishment of virutal network environments, links, and complex route and rule
+configurations. It only relies on iproute2, libc, and Python 3.10. The core
+architecture revolves around the `Network` base class, from which custom network
+topologies can be designed. A typical use-case involves inheriting from this base
+class and defining `Host` and `Link` objects as class attributes within the
+constructor. This design is essential because the assignment of these objects to
+class attributes during the constructor's execution is what adds them to the
+underlying network topology.
+
+Key Features
+------------
+When defining a custom network topology, `Host` and `Link` objects must be
+explicitly assigned to attributes within the constructor. This allows the base
+class to properly register and manage these resources. Upon creation of the
+network (when entering the `with` block or calling `create` on a network instance),
+the library dynamically generates names for the resources — such as the network namespaces
+for hosts and the names for links — enabling unique identification and isolation.
+
+For example, the `SimpleNetwork` class in the code snippet below demonstrates the
+creation of a basic network with two hosts (`host0` and `host1`) linked by a
+virtual ethernet pair (`link`). IP addresses are then assigned to the interfaces
+on this link for each host. The resource names for `Host` and `Link` objects,
+like the network namespace names for hosts and the names for links, are automatically
+assigned when the network is instantiated.
+
+Usage Example
+-------------
+```python
+# Create a simple network with two hosts connected on a link
+class SimpleNetwork(Network):
+    def __init__(self):
+        super().__init__() # must be called first
+        self.host0 = Host()
+        self.host1 = Host()
+        self.link = Link(self.host0, self.host1)
+        self.host0.add_address('192.168.10.0/31', self.link)
+        self.host1.add_address('192.168.10.1/31', self.link)
+
+# Setup the network topology
+with SimpleNetwork() as net:
+    # Enter host0's network namespace
+    with net.host0.netns():
+        # ping host 1
+        subprocess.run(['ping', '192.168.10.1'])
+```
+
+Debugging
+---------
+If the MTR_NETEM_TRACE environmental variable is defined, a trace
+of all configuration commands will be written to standard error.
+'''
+
+# Standard library imports
+import os
+import sys
+import subprocess
+import platform
+import functools
+from dataclasses import dataclass
+from enum import Enum
+from functools import partial
+from io import IOBase
+from typing import Any, Dict, List, Optional, Tuple, Union, cast
+
+# Third-party imports
+from ctypes import CDLL, get_errno
+
+##########################
+## Network Topology API ##
+##########################
+
+# Enum to represent reverse path filtering options
+# See RFC 3704
+class Rpfilter(Enum):
+    '''Reverse-path filtering kernel options'''
+
+    DISABLED = 0
+    STRICT = 1
+    LOOSE = 2
+
+# Data class to hold interface configuration
+@dataclass
+class Intf():
+    '''Interface configuration'''
+
+    addresses: List[str] # List of IP addresses for this interface
+    name: Optional[str] = None # The link name is determined at configuration time
+    rpfilter: Rpfilter = Rpfilter.LOOSE # Reverse path filter setting
+
+@dataclass
+class Route():
+    '''Route configuration'''
+
+    prefix: str # Network prefix (CIDR notation)
+    device: Optional[Intf] = None # Optional output interface
+    table: Optional[int] = None # Optional routing table ID
+
+
+# Represents a policy routing rule
+@dataclass
+class Rule():
+    '''Policy-routing rule'''
+
+    not_: bool = False # Negate the rule
+    from_: Optional[str] = None # Optional source address
+    to: Optional[str] = None # Optional destination address
+    fwmark: Optional[int] = 0 # Optional firewall mark
+    table: Optional[int] = None # Routing table ID
+
+class LifecycleException(Exception):
+    pass
+
+class Lifecycle(Enum):
+    CONFIG = 0
+    RUNTIME = 1
+
+class NetworkObject():
+    '''Base class for all network properties'''
+
+    _parent : 'Network'
+
+    def __init__(self):
+        self._parent = None
+
+    def _register_parent(self, net : 'Network'):
+        self._parent = net
+
+def lifecycle_method(method, lifecycle : Lifecycle):
+    '''Wraps network object method enforcing it is called at a particular point
+    in the lifecycle. This is important because configuration is static and cannot be
+    changed after the network object is created. Some runtime methods reference data
+    only available at runtime.'''
+
+    @functools.wraps(method)
+    def _ensure_phase(self: NetworkObject, *method_args, **method_kwargs):
+
+        # self._parent may be None during the configuration phase
+        if self._parent is not None or lifecycle != Lifecycle.CONFIG:
+
+            current_phase = self._parent._phase
+
+            if current_phase != lifecycle:
+                raise LifecycleException(
+                    f'{method.__name__} called during an incorrect stage in'
+                    f'the emulation lifecycle: {current_phase}, should be called'
+                    f'during {lifecycle}'
+                )
+
+        return method(self, *method_args, **method_kwargs)
+
+    return _ensure_phase
+
+# Create aliases for config and runtime calls
+config_method = partial(lifecycle_method, lifecycle=Lifecycle.CONFIG)
+runtime_method = partial(lifecycle_method, lifecycle=Lifecycle.RUNTIME)
+
+class Link(NetworkObject):
+    '''A link object represents a virtual ethernet pair that links two hosts'''
+
+    hosts: Tuple[Optional['Host'], Optional['Host']]
+
+    def __init__(self):
+        '''Initialize a Link object with empty hosts.'''
+        super().__init__()
+        self.hosts = (None, None)
+
+    @config_method
+    def connect(self, host1 : 'Host', host2 : 'Host'):
+        '''
+        Connect two Host objects via this Link.
+
+        Parameters:
+        host1: First host to connect
+        host2: Second host to connect
+        '''
+
+        self.hosts = (host1, host2)
+
+        for host in self.hosts:
+            host._register_link(self)
+
+# Define a Host class to represent virtual host
+# This is a network namespace with programmatic configuration
+class Host(NetworkObject):
+    '''A host represent a virtual host and is a member of a Network.
+    This is essentially a network namespace with additional configuration
+    including routes, rules, and interfaces.'''
+
+    netns_name: Optional[str]
+    _intf: Dict[Link, Intf]
+    _routes: List[Route]
+    _rules: List[Rule]
+    ip_forwarding: bool
+
+    def __init__(self, ip_forwarding=False):
+        '''Initialize a Host object with optional IP forwarding.'''
+        super().__init__()
+        self.netns_name = None
+        self._intf = {}
+        self._routes = []
+        self._rules = []
+        self.ip_forwarding = ip_forwarding
+
+    def _register_link(self, link : Link):
+        '''
+        Internal method to register a Link with this Host.
+
+        Parameters:
+        link: The link to register
+        '''
+        self._intf[link] = Intf(addresses = [])
+
+    @config_method
+    def add_address(self, address : str, dev : Link):
+        '''
+        Add an IP address to a specific interface associated with a link.
+
+        Parameters:
+        address (str): The IP address to add.
+        dev (Link): The Link object representing the interface.
+        '''
+
+        self._intf[dev].addresses.append(address)
+
+    @config_method
+    def config_rpfiler(self, rp: Rpfilter, dev : Link):
+        '''
+        Set the reverse-pass filter for an interface associated with a link.
+        '''
+        self._intf[dev].rpfilter = rp
+
+    @runtime_method
+    def netns(self) -> 'NetNamespace':
+        '''
+        Retrieve the network namespace associated with this Host.
+
+        Returns:
+        NetNamespace: The network namespace object.
+        '''
+        return NetNamespace(cast(str, self.netns_name))
+
+    def intf(self, link : Link) -> Intf:
+        '''
+        Retrieve the interface associated with a specific Link.
+
+        Parameters:
+        link (Link): The Link object to query for.
+
+        Returns:
+        Intf: The interface associated with the Link.
+        '''
+        return self._intf[link]
+
+    @config_method
+    def add_route(self, prefix_or_route: Union[str, Route], **kwargs):
+        '''
+        Add a route to the Host's routing table.
+
+        Parameters:
+        prefix_or_route (Union[str, Route]): Either a prefix (in CIDR format) or a Route object.
+        kwargs: Additional optional arguments if prefix_or_route is a string.
+        '''
+        if isinstance(prefix_or_route, str):
+
+            if 'device' in kwargs:
+                device = kwargs['device']
+                device = self._intf[device] if isinstance(device, Link) else device
+                kwargs['device'] = device
+
+            self.add_route(Route(
+                prefix=prefix_or_route,
+                **kwargs
+            ))
+        else:
+            assert len(kwargs) == 0 # noqa: S101
+            self._routes.append(prefix_or_route)
+
+    @config_method
+    def add_rule(self, rule: Optional[Rule] = None, **kwargs):
+        '''
+        Add a policy-based routing rule to this Host.
+
+        Parameters:
+        rule (Optional[Rule]): A Rule object, if None, a Rule will be created from kwargs.
+        kwargs: Additional optional arguments to create a Rule object.
+        '''
+        if not rule:
+            rule = Rule(**kwargs)
+
+        self._rules.append(rule)
+
+class Network():
+    '''
+    Network class that serves as a base class for virtual network
+    topologies.
+    '''
+
+    name: str
+    _hosts: Dict[str, Host]
+    _links: Dict[str, Link]
+    _phase: Lifecycle
+
+    def __init__(self, name: Optional[str] = None):
+        '''
+        Initializes a Network object.
+
+        Args:
+        name (Optional[str], optional): The name of the network.
+        Defaults to the class name if not provided.
+        '''
+        self.name = self.__class__.__name__ if name is None else name
+        self._hosts = {}
+        self._links = {}
+        self._phase = Lifecycle.CONFIG
+
+    def register_host(self, name : str, host : Host):
+        self._hosts[name] = host
+
+    def register_link(self, name : str, link : Link):
+        self._links[name] = link
+
+    def __setattr__(self, name : str, value : Any):
+        if hasattr(value, '__class__') \
+                and issubclass(value.__class__, NetworkObject):
+            value._register_parent(self)
+
+        if isinstance(value, Host):
+            self.register_host(name, value)
+        elif isinstance(value, Link):
+            self.register_link(name, value)
+
+        super().__setattr__(name, value)
+
+    def __enter__(self):
+        self.create()
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.destroy()
+
+    def create(self) -> None:
+        '''
+        Creates the virtual network topology. Creates network namespace and associated
+        resources.
+        '''
+
+        assert self._phase == Lifecycle.CONFIG, \
+                "Repeated calls to create() on a single network object" # noqa: S101
+
+
+        for name, host in self._hosts.items():
+            host.netns_name = f'{self.name}.{name}'
+
+        for name, link in self._links.items():
+            for i, host in enumerate(cast(Tuple[Host, Host], link.hosts)):
+                host._intf[link].name = f'{name}{i}'
+
+        try:
+            create_network(self)
+            self._phase = Lifecycle.RUNTIME
+        except Exception as e:
+            destroy_network(self)
+            raise e
+
+    def destroy(self) -> None:
+        '''
+        Destroys the virtual network topology by removing namespaces and
+        associated resources
+        '''
+        assert self._phase == Lifecycle.RUNTIME, \
+            "Network not setup" # noqa: S101
+
+
+        destroy_network(self)
+        self._phase = Lifecycle.CONFIG
+
+def supported() -> Tuple[bool, Optional[str]]:
+    return _supported()
+
+__all__ = [ 'Link', 'Rpfilter', 'Intf', 'Route', 'Rule', 'Host', 'Network', 'supported' ]
+
+########################
+## NETWORK NAMESPACES ##
+########################
+
+LIB_C_SHARED_OBJ = 'libc.so.6'
+
+# Define possible namespace clone flags
+# Ensure the file descriptor refers to a specific namespace type
+class CloneFlags(Enum):
+    ANY = 0
+    NEWCGROUP = 0x02000000
+    NEWIPC = 0x08000000
+    NEWNET = 0x40000000
+    NEWNS = 0x00020000
+    NEWPID = 0x20000000
+    NEWTIME = 0x00000080
+    NEWUSER = 0x10000000
+    NEWUTS = 0x04000000
+
+
+# Error handler for setns syscall
+def setns_errhandler(ret : int, _func: Any, args: tuple):
+
+    if ret == -1:
+        e = get_errno()
+        raise OSError(e, os.strerror(e))
+
+
+# Initialize libc and setup error handler for setns
+libc = CDLL(LIB_C_SHARED_OBJ)
+libc.setns.errcheck = setns_errhandler
+
+
+def setns(file : IOBase, nstype : CloneFlags):
+    return libc.setns(file.fileno(), nstype.value)
+
+
+# Custom exception for namespace errors
+class NamespaceException(Exception):
+    pass
+
+
+# Class to manage network namespaces with the context manager
+# Moves the process into the namespace specified by "name"
+class NetNamespace(object):
+
+    def __init__(self, name : str):
+        self.name = name
+        self.pid = os.getpid()
+        self._target_ns = f'/var/run/netns/{name}'
+        self._current_ns = f'/proc/{self.pid}/ns/net'
+        self._current_ns_file = None
+
+    def enter(self):
+        try:
+            self._current_ns_file = open(self._current_ns)
+
+            with open(self._target_ns) as file:
+                setns(file, CloneFlags.NEWNET)
+        except FileNotFoundError:
+            raise NamespaceException('Failed to open the namespace file. Does the namespace exit?')
+        except PermissionError:
+            raise NamespaceException('Failed to open the namespace file. Permission denied.')
+
+    def exit(self):
+        setns(self._current_ns_file, CloneFlags.NEWNET)
+        self._current_ns_file.close()
+        self._current_ns_file = None
+
+    def __enter__(self):
+        self.enter()
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.exit()
+
+    def __del__(self):
+        if self._current_ns_file:
+            self._current_ns_file.close()
+
+####################
+## IMPLEMENTATION ##
+####################
+
+# Enable tracing
+MTR_NETEM_TRACE = len(os.getenv('MTR_NETEM_TRACE', '')) > 0
+
+
+def find_ip_command() -> Optional[str]:
+    '''
+    Search for the location of the `ip` command in common directories.
+    '''
+    # List of possible locations where the `ip` command might be located
+    possible_locations = [
+        "/usr/bin/ip",
+        "/sbin/ip",
+        "/usr/sbin/ip",
+        "/bin/ip"
+    ]
+
+    # Loop through the possible locations
+    for location in possible_locations:
+        # Check if the file exists and is executable
+        if os.path.isfile(location) and os.access(location, os.X_OK):
+            return location
+
+    return None
+
+def run_cmd(*args, **kargs):
+    '''
+    Execute a shell command.
+
+    This function takes the same arguments as subprocess.run and executes the command.
+    If MTR_NETEM_TRACE is enabled, the command will be traced (i.e., printed
+    before execution).
+    '''
+
+    if MTR_NETEM_TRACE:
+        cmd = ' '.join(args[0])
+        print(cmd, file=sys.stderr)
+
+    subprocess.run(*args, **kargs)
+
+def rule_spec(rule : Rule) -> List[str]:
+    '''
+    Generate a list of arguments for iproute2 to create or delete a routing
+    rule.
+    This is the concatenation of the SELECTOR and ACTION for a rule.
+    '''
+    cmd: List[str] = []
+
+    if rule.not_:
+        cmd.append('not')
+
+    if rule.from_:
+        cmd.extend(['from', rule.from_])
+
+    if rule.to:
+        cmd.extend(['to', rule.to])
+
+    if rule.fwmark:
+        cmd.extend(['fwmark', str(rule.fwmark)])
+
+    if rule.table:
+        cmd.extend(['table', str(rule.table)])
+
+    return cmd
+
+def route_spec(route : Route) -> List[str]:
+    '''
+    Generate a list of arguments for iproute2 to create or delete a route.
+    This is the concatenation of the SELECTOR and ACTION for a route.
+    '''
+
+    '''Obtain the concatenation of the SELECTOR and ACTION
+    of an ip route command, useful for adding or deleting
+    rules with iproute2'''
+
+    cmd: List[str] = [ route.prefix ]
+
+    if route.device:
+        cmd.extend(['dev', cast(str, route.device.name)])
+
+    if route.table:
+        cmd.extend(['table', str(route.table)])
+
+    return cmd
+
+def set_kernel_opt(path : str, value : Union[str, int]):
+    '''
+    Set a kernel option by writing to a sysfs or procfs entry.
+    '''
+
+    try:
+        with open(path, 'w') as file:
+            file.write(str(value))
+    except Exception as e:
+        raise RuntimeError(f'Failed to configure kernel option: {str(e)}')
+
+def set_interface_rpfiler(intf_name : str, rpfilter : Rpfilter):
+    '''
+    Configure the reverse path filter setting for a network interface.
+    '''
+
+    set_kernel_opt(
+        f'/proc/sys/net/ipv4/conf/{intf_name}/rp_filter', rpfilter.value
+    )
+
+def set_ip_forwarding(forward : bool):
+    '''Enable or disable IP forwarding.'''
+
+    set_kernel_opt('/proc/sys/net/ipv4/ip_forward', int(forward))
+
+def create_network(net : Network):
+    '''
+    Create a virtual network.
+
+    This involves several steps:
+    1. Creating network namespaces for each host.
+    2. Creating virtual ethernet pairs for each link.
+    3. Configuring each network interface and moving it to the appropriate namespace.
+    4. Setting up routes and rules for each host.
+
+    '''
+
+    cmd = partial(run_cmd, check=True)
+
+    ip_cmd = cast(str, find_ip_command())
+
+    # Add host namespaces
+    host : Host
+    for host in net._hosts.values():
+        cmd([ ip_cmd, 'netns', 'add', host.netns_name ], check=True)
+
+    link : Link
+    for link in net._links.values():
+
+        intfs = tuple(host._intf[link] \
+                for host in cast(Tuple[Host, Host], link.hosts))
+
+        # Add a virtual ethernet link
+        cmd([
+            ip_cmd, 'link', 'add', intfs[0].name,
+            'type', 'veth', 'peer', 'name',
+            intfs[1].name
+        ])
+
+        intf : Intf
+        for host, intf in zip(cast(Tuple[Host, Host], link.hosts), intfs):
+            intf_name = cast(str, intf.name)
+            netns_name = cast(str, host.netns_name)
+
+            # Move a end of the link pair into the host's network namespace
+            cmd([ip_cmd, 'link', 'set', intf_name, 'netns', netns_name])
+
+            with NetNamespace(netns_name):
+                # Configure the reverse pass filter
+                set_interface_rpfiler(intf_name, intf.rpfilter)
+
+                # Add IP addresses to the link
+                for addr in intf.addresses:
+                    cmd([ip_cmd, 'addr', 'add', addr, 'dev', intf_name])
+
+                # Activate the interface
+                cmd([ip_cmd, 'link', 'set', intf.name, 'up'])
+
+    for host in net._hosts.values():
+
+        with NetNamespace(cast(str, host.netns_name)):
+            # Configure the host's ip forwarding
+            set_ip_forwarding(host.ip_forwarding)
+
+            # Add the host's routes
+            for route in host._routes:
+                cmd([ip_cmd, 'route', 'add', *route_spec(route)])
+
+            # Add the host's policy-database rules
+            for rule in host._rules:
+                cmd([ip_cmd, 'rule', 'add', *rule_spec(rule)])
+
+def destroy_network(net : Network):
+    '''
+    Destroy a virtual network.
+
+    This will remove all network namespaces and associated resources created during network setup.
+    '''
+
+    ip_cmd = cast(str, find_ip_command())
+
+    host : Host
+    for host in net._hosts.values():
+        run_cmd([ip_cmd, 'netns', 'delete', host.netns_name])
+
+# /usr/bin/ip
+# /sbin/ip
+
+def has_iproute() -> bool:
+    '''Test if the host has iproute2 ensuring `ip -V` returns 0'''
+
+    ip_cmd = find_ip_command()
+
+    if ip_cmd is None:
+        return False
+
+    try:
+        ip_result = subprocess.run([ip_cmd, '-V'], capture_output=True)
+    except:
+        return False
+
+    return ip_result.returncode == 0
+
+def _supported() -> Tuple[bool, Optional[str]]:
+    '''Test if MtrNetEm is supported'''
+
+    if platform.system() != 'Linux':
+        return False, 'Tests are only supported on Linux'
+
+    parts = platform.release().split('.')
+    major, minor = int(parts[0]), int(parts[1])
+
+    # Linux 3.8 added the 'setns' network namespace flag
+    if (major < 3) or (major == 3 and minor < 8):
+        return False, 'Tests are only supported on Linux kernel version >= 3.8'
+
+    if os.getuid() != 0:
+        return False, 'Network emulation test require root'
+
+    if not has_iproute():
+        return False, 'The ip utility must be installed (iproute2)'
+
+    return True, None
+
diff --git a/test/linux/netemtests.py b/test/linux/netemtests.py
new file mode 100644
index 0000000..6d3966c
--- /dev/null
+++ b/test/linux/netemtests.py
@@ -0,0 +1,173 @@
+'''
+Tests requiring network emulation
+'''
+
+import sys
+import unittest
+import netem
+from netem import Host, Link, Network, Rpfilter
+from pathlib import Path
+from typing import TypeVar, Type
+
+# Allow imports from the parent directory
+#
+# This is not a "first-party" test since it is not
+# cross-platform.
+
+dir_path = Path(__file__).resolve().parent
+sys.path.append(str(dir_path.parent))
+
+import mtrpacket
+
+NetworkDerivative = TypeVar('NetworkDerivative', bound=Network)
+
+class MtrEmulatedPacketTest(mtrpacket.MtrPacketTest):
+    '''Base class for network emulation packet tests.
+    Ensures that the network is set up before executing any tests,
+    and tears down the network after all tests have been executed.
+    '''
+
+    Net: NetworkDerivative
+    net: Type[NetworkDerivative]
+
+    @classmethod
+    def setUpClass(cls):
+        net = cls.Net()
+        net.create()
+        cls.net = net
+
+        super().setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.net.destroy()
+        super().tearDownClass()
+
+
+class DualIntf(Network):
+    '''
+     DualIntf Topology: A network with two links
+     useful for testing interface, route, or ip
+     selection.
+
+     HOST0                               HOST1
+     ┌─────────┐ 172.30.1.0              ┌────────┐
+     │ LINKA0  ├─────────────────────────┤ LINKA1 │
+     │         │               172.30.1.1│        │
+     │         │                         │        │
+     │         │ 172.30.2.0              │        │
+     │ LINKB0  ├─────────────────────────┤ LINKB1 │
+     │         │               172.30.2.1│        │
+     └─────────┘                         └────────┘
+    '''
+
+    def __init__(self):
+        super().__init__()
+
+        host0 = Host()
+        host1 = Host()
+
+        link_a = Link()
+        link_a.connect(host0, host1)
+
+        link_b = Link()
+        link_b.connect(host0, host1)
+
+        # Only respond to inbound traffic from the peer link
+        host1.config_rpfiler(Rpfilter.STRICT, link_a)
+        host1.config_rpfiler(Rpfilter.STRICT, link_b)
+
+        host0.add_address('172.30.1.0/31', link_a)
+        host1.add_address('172.30.1.1/31', link_a)
+
+        host0.add_address('172.30.2.0/31', link_b)
+        host1.add_address('172.30.2.1/31', link_b)
+
+        host0.add_route('172.30.1.0/31', device=link_a, table=100)
+        host0.add_rule(fwmark=100, table=100)
+
+        self.host0 = host0
+        self.host1 = host1
+
+        self.link_a = link_a
+        self.link_b = link_b
+
+class DualIntfPacketTest(MtrEmulatedPacketTest):
+    '''Test components that require a reproducible network topology'''
+
+    Net = DualIntf
+
+    def setUp(self):
+        '''Enter the namespace for host0'''
+        self.ns = DualIntfPacketTest.net.host0.netns()
+        self.ns.enter()
+
+        super().setUp()
+
+    def tearDown(self):
+        '''Exit the namespace for host0'''
+        self.ns.exit()
+
+        super().tearDown()
+
+    def test_interface_binding(self):
+        '''Test binding to a specific interface by sending a routable probe to an
+        interface where the probe is not routable.'''
+
+        # use link 'a'
+        intf_a_h0 = self.net.host0.intf(self.net.link_a)
+
+        # Expect a reply because 172.30.1.1 is on link 'a'
+        self.write_command(f'14 send-probe ip-4 172.30.1.1 local-device {intf_a_h0.name} timeout 1')
+        reply = self.parse_reply()
+        self.assertEqual(reply.token, 14)
+        self.assertEqual(reply.command_name, 'reply')
+        self.assertEqual(reply.argument['ip-4'], '172.30.1.1')
+
+        # Expect no reply because 172.30.2.1 is on link 'b'
+        self.write_command(f'15 send-probe ip-4 172.30.2.1 local-device {intf_a_h0.name} timeout 1')
+        reply = self.parse_reply()
+        self.assertEqual(reply.token, 15)
+        self.assertEqual(reply.command_name, 'no-reply')
+
+    def test_packet_marking(self):
+        '''Test if mtr-packet marks outbound packets.'''
+
+        # Probes with mark '100' query a table that can only reach link 'a'
+
+        # A probe destined for 172.30.1.1 on link 'a' should succeed
+        self.write_command('16 send-probe ip-4 172.30.1.1 mark 100')
+        reply = self.parse_reply()
+        self.assertEqual(reply.token, 16)
+        self.assertEqual(reply.command_name, 'reply')
+
+        # A probe destined for 172.30.2.1 on link 'a' should not succeed
+        self.write_command('17 send-probe ip-4 172.30.2.1 mark 100')
+        reply = self.parse_reply()
+        self.assertEqual(reply.token, 17)
+        self.assertEqual(reply.command_name, 'no-reply')
+
+    def test_source_address_selection(self):
+        '''Test manual specification of a source address.'''
+
+        # Send a probe to 172.30.1.1 via 172.30.1.0; host2 should respond
+        self.write_command('18 send-probe ip-4 172.30.1.1 local-address 172.30.1.0')
+        reply = self.parse_reply()
+        self.assertEqual(reply.token, 18)
+        self.assertEqual(reply.command_name, 'reply')
+
+        # Send a probe to 172.30.2.1 via 172.30.1.0; host1 will not respond
+        # because rp_filter is enabled and the probe is sent over link 'a'
+        self.write_command('19 send-probe ip-4 172.30.2.1 local-address 172.30.1.0')
+        reply = self.parse_reply()
+        self.assertEqual(reply.token, 19)
+        self.assertEqual(reply.command_name, 'no-reply')
+
+if __name__ == '__main__':
+    supported, err = netem.supported()
+
+    if not supported:
+        print(err, file=sys.stderr)
+        sys.exit(1)
+
+    unittest.main()
diff --git a/test/probe.py b/test/probe.py
index df5f496..30acd45 100755
--- a/test/probe.py
+++ b/test/probe.py
@@ -263,7 +263,6 @@ class TestProbeICMPv4(mtrpacket.MtrPacketTest):
         required_success = int(loop_count * 0.90)
         self.assertGreaterEqual(success_count, required_success)
 
-
 class TestProbeICMPv6(mtrpacket.MtrPacketTest):
     '''Test sending probes using IP version 6'''
 
diff --git a/ui/asn.c b/ui/asn.c
index 3f424e0..111c394 100644
--- a/ui/asn.c
+++ b/ui/asn.c
@@ -21,6 +21,7 @@
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdbool.h>
 #include <sys/types.h>
 #ifdef HAVE_ERROR_H
 #include <error.h>
@@ -65,7 +66,7 @@ static int iihash = 0;
 static char fmtinfo[32];
 
 /* items width: ASN, Route, Country, Registry, Allocated */
-static const int iiwidth[] = { 7, 19, 4, 8, 11 };       /* item len + space */
+static const int iiwidth[] = { 12, 19, 4, 8, 11 };       /* item len + space */
 
 typedef char *items_t[ITEMSMAX + 1];
 static items_t items_a;         /* without hash: items */
@@ -215,6 +216,13 @@ static void reverse_host6(
 }
 #endif
 
+#ifdef ENABLE_IPV6
+static bool is_well_known_nat64(struct in6_addr *addr){
+    // 64:ff9b::
+    return addr->s6_addr[0] == 0x00 &&  addr->s6_addr[1] == 0x64 && addr->s6_addr[2] == 0xff && addr->s6_addr[3] == 0x9b;
+}
+#endif
+
 static char *get_ipinfo(
     struct mtr_ctl *ctl,
     ip_t * addr)
@@ -229,10 +237,23 @@ static char *get_ipinfo(
 
     if (ctl->af == AF_INET6) {
 #ifdef ENABLE_IPV6
-        reverse_host6(addr, key, NAMELEN);
-        if (snprintf(lookup_key, NAMELEN, "%s.origin6.asn.cymru.com", key)
-            >= NAMELEN)
-            return NULL;
+        if (is_well_known_nat64(addr)) {
+            // Treats the final 4 bytes as IPv4 address
+            unsigned char buff[4];
+            memcpy(buff, addr->s6_addr + 12, 4);
+            if (snprintf
+                (key, NAMELEN, "%d.%d.%d.%d", buff[3], buff[2], buff[1],
+                 buff[0]) >= NAMELEN)
+                return NULL;
+            if (snprintf(lookup_key, NAMELEN, "%s.%s", key, ctl->ipinfo_provider4)
+                >= NAMELEN)
+                return NULL;
+        } else {
+            reverse_host6(addr, key, NAMELEN);
+            if (snprintf(lookup_key, NAMELEN, "%s.%s", key, ctl->ipinfo_provider6)
+                >= NAMELEN)
+                return NULL;
+        }
 #else
         return NULL;
 #endif
@@ -243,7 +264,7 @@ static char *get_ipinfo(
             (key, NAMELEN, "%d.%d.%d.%d", buff[3], buff[2], buff[1],
              buff[0]) >= NAMELEN)
             return NULL;
-        if (snprintf(lookup_key, NAMELEN, "%s.origin.asn.cymru.com", key)
+        if (snprintf(lookup_key, NAMELEN, "%s.%s", key, ctl->ipinfo_provider4)
             >= NAMELEN)
             return NULL;
     }
diff --git a/ui/cmdpipe.c b/ui/cmdpipe.c
index d22b236..bd5606a 100644
--- a/ui/cmdpipe.c
+++ b/ui/cmdpipe.c
@@ -220,10 +220,17 @@ void execute_packet_child(
        the path to the mtr-packet executable.  This is necessary
        for debugging changes for mtr-packet.
      */
-    char *mtr_packet_path = getenv("MTR_PACKET");
-    if (mtr_packet_path == NULL) {
+    char * mtr_packet_path = NULL; 
+
+    // In the rare case that mtr-packet is not setuid-root, 
+    // and a select group of users has sudo privileges to run 
+    // mtr and not much else, THEN create /etc/mtr.is.run.under.sudo
+    // to prevent a privilege escalation when one of those accounts
+    // is compromised.  CVE-2025-49809
+    if (access ("/etc/mtr.is.run.under.sudo", F_OK) != 0)
+        mtr_packet_path = getenv("MTR_PACKET");
+    if (mtr_packet_path == NULL)
         mtr_packet_path = "mtr-packet";
-    }
 
     /*
        First, try to execute mtr-packet from PATH
@@ -417,6 +424,22 @@ void append_command_argument(
     strncat(command, argument, remaining_size);
 }
 
+static
+void append_command_string_argument(
+    char *command,
+    int buffer_size,
+    char *name,
+    char *value)
+{
+    char argument[COMMAND_BUFFER_SIZE];
+    int remaining_size;
+
+    remaining_size = buffer_size - strlen(command) - 1;
+
+    snprintf(argument, buffer_size, " %s %s", name, value);
+    strncat(command, argument, remaining_size);
+}
+
 
 /*  Request a new probe from the "mtr-packet" child process  */
 void send_probe_command(
@@ -466,6 +489,11 @@ void send_probe_command(
     }
 #endif
 
+    if (ctl->InterfaceName) {
+        append_command_string_argument(command, COMMAND_BUFFER_SIZE,
+                                       "local-device", ctl->InterfaceName);
+    }
+
     remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1;
     strncat(command, "\n", remaining_size);
 
@@ -694,10 +722,14 @@ void handle_command_reply(
     if (!strcmp(reply_name, "reply")
             || !strcmp(reply_name, "ttl-expired")) {
         err = 0;
-    } else if (!strcmp(reply_name, "no-route")) {
-        err = ENETUNREACH;
     } else if (!strcmp(reply_name, "network-down")) {
         err = ENETDOWN;
+    } else if (!strcmp(reply_name, "host-down")) {
+        err = EHOSTDOWN;
+    } else if (!strcmp(reply_name, "no-route-network")) {
+        err = ENETUNREACH;
+    } else if (!strcmp(reply_name, "no-route-host")) {
+        err = EHOSTUNREACH;
     } else {
         /*  If the reply type is unknown, ignore it  */
         return;
diff --git a/ui/curses.c b/ui/curses.c
index d01e178..9c0d458 100644
--- a/ui/curses.c
+++ b/ui/curses.c
@@ -20,6 +20,8 @@
 
 #include "mtr.h"
 
+#include <locale.h>
+#include <assert.h>
 #include <strings.h>
 #include <unistd.h>
 
@@ -69,6 +71,11 @@ enum { NUM_FACTORS = 8 };
 static double factors[NUM_FACTORS];
 static int scale[NUM_FACTORS];
 static char block_map[NUM_FACTORS];
+#ifdef WITH_BRAILLE_DISPLAY
+static const wchar_t *braille_map[NUM_FACTORS] = {
+    L"⣀", L"⣀", L"⣤", L"⣤", L"⣶", L"⣶", L"⣿", L"🮐"
+};
+#endif
 
 enum { black = 1, red, green, yellow, blue, magenta, cyan, white };
 static const int block_col[NUM_FACTORS + 1] = {
@@ -172,6 +179,8 @@ int mtr_curses_keyaction(
         return ActionReset;
     case 'd':
         return ActionDisplay;
+    case 'c':
+        return ActionCompact;
     case 'e':
         return ActionMPLS;
     case 'n':
@@ -200,7 +209,10 @@ int mtr_curses_keyaction(
             buf[i++] = c;       /* need more checking on 'c' */
         }
         buf[i] = '\0';
-        ctl->cpacketsize = atoi(buf);
+        int new_packetsize = atoi(buf);
+        if (abs(ctl->cpacketsize) >= MINPACKET && abs(ctl->cpacketsize) < MAXPACKET) {
+            ctl->cpacketsize = new_packetsize;
+        }
         return ActionNone;
     case 'b':
         mvprintw(2, 0, "Ping Bit Pattern: %d\n", ctl->bitpattern);
@@ -345,6 +357,7 @@ int mtr_curses_keyaction(
         printw("  ?|h     help\n");
         printw("  p       pause (SPACE to resume)\n");
         printw("  d       switching display mode\n");
+        printw("  c       switching compact mode\n");
         printw("  e       toggle MPLS information on/off\n");
         printw("  n       toggle DNS on/off\n");
         printw("  r       reset all counters\n");
@@ -421,8 +434,8 @@ static void mtr_curses_hosts(
     for (at = net_min(ctl) + ctl->display_offset; at < max; at++) {
         printw("%2d. ", at + 1);
         err = net_err(at);
-        addr = net_addr(at);
-        mpls = net_mpls(at);
+        addr = net_addrs(at, 0);
+        mpls = net_mplss(at, 0);
 
         addrcmp_result = addrcmp(addr, &ctl->unspec_addr, ctl->af);
 
@@ -471,7 +484,7 @@ static void mtr_curses_hosts(
             }
 
             /* Multi path */
-            for (i = 0; i < MAX_PATH; i++) {
+            for (i = 1; i < ctl->maxDisplayPath; i++) {
                 addrs = net_addrs(at, i);
                 mplss = net_mplss(at, i);
                 if (addrcmp(addrs, addr, ctl->af) == 0)
@@ -570,22 +583,178 @@ static void mtr_curses_init(
     block_map[NUM_FACTORS - 1] = '>';
 }
 
-static void mtr_print_scaled(
+static int ms_to_factor(
     int ms)
 {
     int i;
 
     for (i = 0; i < NUM_FACTORS; i++) {
-        if (ms <= scale[i]) {
-            attrset(block_col[i + 1]);
-            printw("%c", block_map[i]);
-            attrset(A_NORMAL);
-            return;
-        }
+        if (ms <= scale[i])
+            return i;
+    }
+
+    return NUM_FACTORS;
+}
+
+static void mtr_print_scaled(
+    int ms)
+{
+    int f = ms_to_factor(ms);
+
+    if ((unsigned)f < NUM_FACTORS) {
+        attrset(block_col[f + 1]);
+        printw("%c", block_map[f]);
+        attrset(A_NORMAL);
+        return;
     }
     printw(">");
 }
 
+#ifdef WITH_BRAILLE_DISPLAY
+static int current_host_range_low_ms = 1000000;
+static int current_host_range_high_ms = -1;
+
+static void compute_current_host_range(const int *ms_data, size_t length)
+{
+    current_host_range_low_ms = 1000000;
+    current_host_range_high_ms = -1;
+
+    for (int i=0; i<length; ++i) {
+        int ms = ms_data[i];
+        if (ms < 0)
+            continue;
+        if (current_host_range_low_ms > ms)
+            current_host_range_low_ms = ms;
+        if (current_host_range_high_ms < ms)
+            current_host_range_high_ms = ms;
+    }
+}
+
+static const int scale_ms_to_braille_factor(int ms)
+{
+    if (ms <= 0)
+        return 0;
+
+    int ms_range = current_host_range_high_ms - current_host_range_low_ms;
+    if (ms_range < 1)
+        return 0;
+
+    return (ms - current_host_range_low_ms) * 4 / ms_range;
+}
+
+static const wchar_t *braille_char_lookup(
+    int ms,
+    const wchar_t *braille_set[5])
+{
+    if (ms < 0)
+        return L"𜸲"; // this is an error in decoding
+
+    int i = scale_ms_to_braille_factor(ms);
+    if ((unsigned)i >= 4)
+        return L"🮐"; // this is the max
+
+    return braille_set[i];
+}
+
+// handle if left is not provided, but right is
+static const wchar_t *braille_char_left(
+    int left_ms)
+{
+    static const wchar_t *braille_left_lookup[5] =  {
+        L"⡀", L"⡄", L"⡆", L"⡇",
+    };
+
+    return braille_char_lookup(left_ms, braille_left_lookup);
+}
+
+
+// handle if right is not provided, but left is
+static const wchar_t *braille_char_right(
+    int right_ms)
+{
+    static const wchar_t *braille_right_lookup[5] =  {
+        L"⢀", L"⢠", L"⢰", L"⢸",
+    };
+
+    return braille_char_lookup(right_ms, braille_right_lookup);
+}
+
+// handle both left and right being provided
+static const wchar_t *braille_char_double(
+    int left_ms,
+    int right_ms)
+{
+    static const wchar_t *braille_double_lookup[5][5] =  {
+        { L"⣀", L"⣠", L"⣰", L"⣸", },
+        { L"⣄", L"⣤", L"⣴", L"⣼", },
+        { L"⣆", L"⣦", L"⣶", L"⣾", },
+        { L"⣇", L"⣧", L"⣷", L"⣿", }
+    };
+
+    int left_i = scale_ms_to_braille_factor(left_ms);
+    if ((unsigned)left_i >= 4)
+        return L"🮐"; // this is the max
+
+    return braille_char_lookup(right_ms, braille_double_lookup[left_i]);
+}
+
+static void mtr_print_braille(
+    int left_ms,
+    int right_ms)
+{
+    int ms_max = left_ms > right_ms ? left_ms : right_ms;
+    int f = ms_to_factor(ms_max);
+    f = ((unsigned)f < NUM_FACTORS) ? f : NUM_FACTORS - 1;
+
+    const wchar_t *wstr;
+    if (left_ms > 0 && right_ms > 0)
+        wstr = braille_char_double(left_ms, right_ms);
+    else if (left_ms > 0)
+        wstr = braille_char_left(left_ms);
+    else if (right_ms > 0)
+        wstr = braille_char_right(right_ms);
+    else
+        wstr = L"▁";
+
+    attrset(block_col[f + 1]);
+    printw("%ls", wstr);
+    attrset(A_NORMAL);
+}
+
+static void mtr_fill_graph_braille(
+    struct mtr_ctl *ctl,
+    int at,
+    int cols)
+{
+    const int *saved;
+    int i;
+
+    saved = net_saved_pings(at);
+
+    compute_current_host_range(saved, SAVED_PINGS);
+
+    // we can pack twice as many entries into a braille line
+
+    cols = cols * 2;
+    cols = cols <= SAVED_PINGS ? cols : SAVED_PINGS;
+
+    for (i = SAVED_PINGS - cols; i < SAVED_PINGS; i+=2) {
+        int a = saved[i];
+        int b = (i+1 < SAVED_PINGS) ? saved[i+1] : 0;
+
+        if (a == -2 && b == -2) {
+            printw(" ");
+        } else if (a == -1 || b == -1) {
+            attrset(block_col[0]);
+            printw("%c", '?');
+            attrset(A_NORMAL);
+        } else {
+            mtr_print_braille(a, b);
+        }
+    }
+
+}
+#endif
 
 static void mtr_fill_graph(
     struct mtr_ctl *ctl,
@@ -665,7 +834,14 @@ static void mtr_curses_graph(
         move(y, startstat);
 
         printw(" ");
-        mtr_fill_graph(ctl, at, cols);
+#ifdef WITH_BRAILLE_DISPLAY
+        if (ctl->display_mode == DisplayModeBraille) {
+            mtr_fill_graph_braille(ctl, at, cols);
+        } else
+#endif
+        {
+            mtr_fill_graph(ctl, at, cols);
+        }
         printw("\n");
     }
 }
@@ -689,7 +865,7 @@ void mtr_curses_redraw(
     erase();
     getmaxyx(stdscr, __unused_int, maxx);
 
-    rowstat = 5;
+    rowstat = !ctl->CompactLayout;
 
     move(0, 0);
     attron(A_BOLD);
@@ -698,34 +874,38 @@ void mtr_curses_redraw(
     pwcenter(buf);
     attroff(A_BOLD);
 
-    mvprintw(1, 0, "%s (%s) -> %s (%s)",
+    mvprintw(rowstat, 0, "%s (%s) -> %s (%s)",
 	ctl->LocalHostname, net_localaddr(),
 	ctl->Hostname, net_remoteaddr());
     t = time(NULL);
-    mvprintw(1, maxx - 25, "%s", iso_time(&t));
-    printw("\n");
+    mvprintw(rowstat, maxx - 25, "%s", iso_time(&t));
+    if (rowstat) {
+        printw("\n");
 
-    printw("Keys:  ");
-    attron(A_BOLD);
-    printw("H");
-    attroff(A_BOLD);
-    printw("elp   ");
-    attron(A_BOLD);
-    printw("D");
-    attroff(A_BOLD);
-    printw("isplay mode   ");
-    attron(A_BOLD);
-    printw("R");
-    attroff(A_BOLD);
-    printw("estart statistics   ");
-    attron(A_BOLD);
-    printw("O");
-    attroff(A_BOLD);
-    printw("rder of fields   ");
-    attron(A_BOLD);
-    printw("q");
-    attroff(A_BOLD);
-    printw("uit\n");
+        printw("Keys:  ");
+        attron(A_BOLD);
+        printw("H");
+        attroff(A_BOLD);
+        printw("elp   ");
+        attron(A_BOLD);
+        printw("D");
+        attroff(A_BOLD);
+        printw("isplay mode   ");
+        attron(A_BOLD);
+        printw("R");
+        attroff(A_BOLD);
+        printw("estart statistics   ");
+        attron(A_BOLD);
+        printw("O");
+        attroff(A_BOLD);
+        printw("rder of fields   ");
+        attron(A_BOLD);
+        printw("q");
+        attroff(A_BOLD);
+        printw("uit\n");
+    }
+
+    rowstat = rowstat ? 5 : 1;
 
     if (ctl->display_mode == DisplayModeDefault) {
         for (i = 0; i < MAXFLD; i++) {
@@ -761,8 +941,10 @@ void mtr_curses_redraw(
             maxx <= SAVED_PINGS + padding ? maxx - padding : SAVED_PINGS;
         startstat = padding - 2;
 
-        snprintf(msg, sizeof(msg), " Last %3d pings", max_cols);
-        mvprintw(rowstat - 1, startstat, "%s", msg);
+        if (rowstat > 1) {
+            snprintf(msg, sizeof(msg), " Last %3d pings", max_cols);
+            mvprintw(rowstat - 1, startstat, "%s", msg);
+        }
 
         attroff(A_BOLD);
         move(rowstat, 0);
@@ -775,17 +957,23 @@ void mtr_curses_redraw(
         printw("Scale:");
         attroff(A_BOLD);
 
-        for (i = 0; i < NUM_FACTORS - 1; i++) {
+#ifdef WITH_BRAILLE_DISPLAY
+        bool use_braille_map = (ctl->display_mode == DisplayModeBraille);
+#endif
+
+        for (i = 0; i < NUM_FACTORS; i++) {
             printw("  ");
             attrset(block_col[i + 1]);
-            printw("%c", block_map[i]);
+#ifdef WITH_BRAILLE_DISPLAY
+            if (use_braille_map)
+                printw("%ls", braille_map[i]);
+            else
+#endif
+                printw("%c", block_map[i]);
             attrset(A_NORMAL);
-            printw(":%d ms", scale[i] / 1000);
+            if (i < NUM_FACTORS-1)
+                printw(":%d ms", scale[i] / 1000);
         }
-        printw("  ");
-        attrset(block_col[NUM_FACTORS]);
-        printw("%c", block_map[NUM_FACTORS - 1]);
-        attrset(A_NORMAL);
     }
 
     refresh();
@@ -798,6 +986,11 @@ void mtr_curses_open(
     int bg_col = 0;
     int i;
 
+#ifdef WITH_BRAILLE_DISPLAY
+    // initialize all locale variables, before ncurses starts
+    setlocale(LC_ALL, "");
+#endif
+
     initscr();
     raw();
     noecho();
diff --git a/ui/display.c b/ui/display.c
index e457b59..6761b19 100644
--- a/ui/display.c
+++ b/ui/display.c
@@ -266,17 +266,16 @@ void display_clear(
 char *host_error_to_string(
     int err)
 {
-    if (err == ENETUNREACH) {
+    if (err == ENETDOWN)
+        return "network is down";
+    else if (err == EHOSTDOWN)
+        return "host is down";
+    else if (err == ENETUNREACH)
+        return "no route to network";
+    else if (err == EHOSTUNREACH)
         return "no route to host";
-    }
-
-    if (err == ENETDOWN) {
-        return "network down";
-    }
-
-    if (err == 0) {
+    else if (err == 0)
         return "waiting for reply";
-    }
 
     return strerror(err);
 }
diff --git a/ui/display.h b/ui/display.h
index 0c16e90..ad74e63 100644
--- a/ui/display.h
+++ b/ui/display.h
@@ -20,7 +20,7 @@
 
 /* Don't put a trailing comma in enumeration lists. Some compilers
    (notably the one on Irix 5.2) do not like that. */
-enum { ActionNone, ActionQuit, ActionReset, ActionDisplay,
+enum { ActionNone, ActionQuit, ActionReset, ActionDisplay, ActionCompact,
     ActionClear, ActionPause, ActionResume, ActionMPLS, ActionDNS,
 #ifdef HAVE_IPINFO
     ActionII, ActionAS,
@@ -46,10 +46,18 @@ enum {
 #endif
 };
 
+// if we have libncursesw and braille graphs were enabled, build with them
+#if HAVE_CURSESW && ENABLE_BRAILLE
+#define WITH_BRAILLE_DISPLAY 1
+#endif
+
 enum {
     DisplayModeDefault,
     DisplayModeBlockmap,
     DisplayModeBlockmapScale,
+#ifdef WITH_BRAILLE_DISPLAY
+    DisplayModeBraille,
+#endif
     DisplayModeMAX              /* this must be the last DisplayMode entry */
 };
 
diff --git a/ui/mtr.c b/ui/mtr.c
index 2c6fc68..4d5a343 100644
--- a/ui/mtr.c
+++ b/ui/mtr.c
@@ -44,6 +44,7 @@
 #include <assert.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <locale.h>
 #include <sys/stat.h>
 #include <sys/time.h>
 
@@ -94,84 +95,63 @@ static void __attribute__ ((__noreturn__)) usage(FILE * out)
     fputs("\nUsage:\n", out);
     fputs(" mtr [options] hostname\n", out);
     fputs("\n", out);
-    fputs(" -F, --filename FILE        read hostname(s) from a file\n",
-          out);
-    fputs(" -4                         use IPv4 only\n", out);
+    fputs(" -F, --filename FILE              read hostname(s) from a file\n",
+          out);      
+    fputs(" -4                               use IPv4 only\n", out);
+#ifdef ENABLE_IPV6      
+    fputs(" -6                               use IPv6 only\n", out);
+#endif      
+    fputs(" -u, --udp                        use UDP instead of ICMP echo\n", 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);  
+    fputs(" -f, --first-ttl NUMBER           set what TTL to start\n", out);
+    fputs(" -m, --max-ttl NUMBER             maximum number of hops\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);  
+    fputs(" -L, --localport LOCALPORT        source port number for UDP\n", out);
+    fputs(" -s, --psize PACKETSIZE           set the packet size used for probing\n", out);       
+    fputs(" -B, --bitpattern NUMBER          set bit pattern to use in payload\n", out);       
+    fputs(" -i, --interval SECONDS           ICMP echo request interval\n", out);
+    fputs(" -G, --gracetime SECONDS          number of seconds to wait for responses\n", out);       
+    fputs(" -Q, --tos NUMBER                 type of service field in IP header\n", out);       
+    fputs(" -e, --mpls                       display information from ICMP extensions\n", out);       
+    fputs(" -Z, --timeout SECONDS            seconds to keep probe sockets open\n", out);       
+#ifdef SO_MARK       
+    fputs(" -M, --mark MARK                  mark each sent packet\n", out);
+#endif       
+    fputs(" -r, --report                     output using report mode\n", out);
+    fputs(" -w, --report-wide                output wide report\n", out);
+    fputs(" -c, --report-cycles COUNT        set the number of pings sent\n", out);       
+#ifdef HAVE_JANSSON       
+    fputs(" -j, --json                       output json\n", out);
+#endif       
+    fputs(" -x, --xml                        output xml\n", out);
+    fputs(" -C, --csv                        output comma separated values\n", out);       
+    fputs(" -l, --raw                        output raw format\n", out);
+    fputs(" -p, --split                      split output\n", out);
+#ifdef HAVE_CURSES       
+    fputs(" -t, --curses                     use curses terminal interface\n", out);       
+#endif       
+    fputs("     --displaymode MODE           select initial display mode\n", out);       
+#ifdef HAVE_GTK       
+    fputs(" -g, --gtk                        use GTK+ xwindow interface\n", out);
+#endif       
+    fputs(" -n, --no-dns                     do not resolve host names\n", out);
+    fputs(" -b, --show-ips                   show IP numbers and host names\n", out);       
+    fputs(" -o, --order FIELDS               select output fields\n", out);
+#ifdef HAVE_IPINFO       
+    fputs(" -y, --ipinfo NUMBER              select IP information in output\n",
+          out);       
+    fputs(" -z, --aslookup                   display AS number\n", out);
+    fputs("     --ipinfo_provider4           provider for IPv4 AS lookups\n", out);
 #ifdef ENABLE_IPV6
-    fputs(" -6                         use IPv6 only\n", out);
+    fputs("     --ipinfo_provider6           provider for IPv6 AS lookups\n", out);
 #endif
-    fputs(" -u, --udp                  use UDP instead of ICMP echo\n",
-          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);
-    fputs(" -f, --first-ttl NUMBER     set what TTL to start\n", out);
-    fputs(" -m, --max-ttl NUMBER       maximum number of hops\n", out);
-    fputs(" -U, --max-unknown NUMBER   maximum unknown host\n", out);
-    fputs
-        (" -P, --port PORT            target port number for TCP, SCTP, or UDP\n",
-         out);
-    fputs(" -L, --localport LOCALPORT  source port number for UDP\n", out);
-    fputs
-        (" -s, --psize PACKETSIZE     set the packet size used for probing\n",
-         out);
-    fputs
-        (" -B, --bitpattern NUMBER    set bit pattern to use in payload\n",
-         out);
-    fputs(" -i, --interval SECONDS     ICMP echo request interval\n", out);
-    fputs
-        (" -G, --gracetime SECONDS    number of seconds to wait for responses\n",
-         out);
-    fputs
-        (" -Q, --tos NUMBER           type of service field in IP header\n",
-         out);
-    fputs
-        (" -e, --mpls                 display information from ICMP extensions\n",
-         out);
-    fputs
-        (" -Z, --timeout SECONDS      seconds to keep probe sockets open\n",
-         out);
-#ifdef SO_MARK
-    fputs(" -M, --mark MARK            mark each sent packet\n", out);
-#endif
-    fputs(" -r, --report               output using report mode\n", out);
-    fputs(" -w, --report-wide          output wide report\n", out);
-    fputs(" -c, --report-cycles COUNT  set the number of pings sent\n",
-          out);
-#ifdef HAVE_JANSSON
-    fputs(" -j, --json                 output json\n", out);
-#endif
-    fputs(" -x, --xml                  output xml\n", out);
-    fputs(" -C, --csv                  output comma separated values\n",
-          out);
-    fputs(" -l, --raw                  output raw format\n", out);
-    fputs(" -p, --split                split output\n", out);
-#ifdef HAVE_CURSES
-    fputs(" -t, --curses               use curses terminal interface\n",
-          out);
-#endif
-    fputs("     --displaymode MODE     select initial display mode\n",
-          out);
-#ifdef HAVE_GTK
-    fputs(" -g, --gtk                  use GTK+ xwindow interface\n", out);
-#endif
-    fputs(" -n, --no-dns               do not resolve host names\n", out);
-    fputs(" -b, --show-ips             show IP numbers and host names\n",
-          out);
-    fputs(" -o, --order FIELDS         select output fields\n", out);
-#ifdef HAVE_IPINFO
-    fputs(" -y, --ipinfo NUMBER        select IP information in output\n",
-          out);
-    fputs(" -z, --aslookup             display AS number\n", out);
-#endif
-    fputs(" -h, --help                 display this help and exit\n", out);
-    fputs
-        (" -v, --version              output version information and exit\n",
-         out);
+#endif       
+    fputs(" -h, --help                       display this help and exit\n", out);
+    fputs(" -v, --version                    output version information and exit\n", out);  
     fputs("\n", out);
     fputs("See the 'man 8 mtr' for details.\n", out);
     exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
@@ -319,7 +299,11 @@ static void parse_arg(
        3/ update the help message (see usage() function).
      */
     enum {
-        OPT_DISPLAYMODE = CHAR_MAX + 1
+        OPT_DISPLAYMODE = CHAR_MAX + 1,
+        OPT_IPINFO4 = CHAR_MAX + 2,
+#ifdef ENABLE_IPV6
+        OPT_IPINFO6 = CHAR_MAX + 3,
+#endif /* ifdef ENABLE_IPV6 */
     };
     static const struct option long_options[] = {
         /* option name, has argument, NULL, short name */
@@ -356,6 +340,10 @@ static void parse_arg(
 #ifdef HAVE_IPINFO
         {"ipinfo", 1, NULL, 'y'},       /* IP info lookup */
         {"aslookup", 0, NULL, 'z'},     /* Do AS lookup (--ipinfo 0) */
+        {"ipinfo_provider4", 1, NULL, OPT_IPINFO4},
+#ifdef ENABLE_IPV6
+        {"ipinfo_provider6", 1, NULL, OPT_IPINFO6},
+#endif
 #endif
 
         {"interval", 1, NULL, 'i'},
@@ -369,6 +357,7 @@ static void parse_arg(
         {"first-ttl", 1, NULL, 'f'},    /* -f & -m are borrowed from traceroute */
         {"max-ttl", 1, NULL, 'm'},
         {"max-unknown", 1, NULL, 'U'},
+        {"max-display-path", 1, NULL, 'E'},
         {"udp", 0, NULL, 'u'},  /* UDP (default is ICMP) */
         {"tcp", 0, NULL, 'T'},  /* TCP (default is ICMP) */
 #ifdef HAS_SCTP
@@ -400,7 +389,6 @@ static void parse_arg(
         /* optional options need two ':', but ignore them now as they are not in use */
     }
 
-    opt = 0;
     while (1) {
         opt = getopt_long(argc, argv, short_options, long_options, NULL);
         if (opt == -1)
@@ -452,19 +440,22 @@ static void parse_arg(
 
         case OPT_DISPLAYMODE:
             ctl->display_mode =
-                strtonum_or_err(optarg, "invalid argument", STRTO_INT);
+                strtoint_or_err(optarg, "invalid argument");
             if ((DisplayModeMAX - 1) < ctl->display_mode)
                 error(EXIT_FAILURE, 0, "value out of range (%d - %d): %s",
                       DisplayModeDefault, (DisplayModeMAX - 1), optarg);
             break;
         case 'c':
             ctl->MaxPing =
-                strtonum_or_err(optarg, "invalid argument", STRTO_INT);
+                strtoint_or_err(optarg, "invalid argument");
             ctl->ForceMaxPing = 1;
             break;
         case 's':
             ctl->cpacketsize =
-                strtonum_or_err(optarg, "invalid argument", STRTO_INT);
+                strtoint_or_err(optarg, "invalid argument");
+            if (abs(ctl->cpacketsize) < MINPACKET || abs(ctl->cpacketsize) > MAXPACKET) {
+                error(EXIT_FAILURE, 0, "value of of range (%d - %d)", MINPACKET, MAXPACKET);
+            }
             break;
         case 'I':
             ctl->InterfaceName = optarg;
@@ -489,11 +480,7 @@ static void parse_arg(
             }
             break;
         case 'f':
-            ctl->fstTTL =
-                strtonum_or_err(optarg, "invalid argument", STRTO_INT);
-            if (ctl->fstTTL > ctl->maxTTL) {
-                ctl->fstTTL = ctl->maxTTL;
-            }
+            ctl->fstTTL = strtoint_or_err(optarg, "invalid argument");
             if (ctl->fstTTL < 1) {      /* prevent 0 hop */
                 ctl->fstTTL = 1;
             }
@@ -502,25 +489,27 @@ static void parse_arg(
             read_from_file(names, optarg);
             break;
         case 'm':
-            ctl->maxTTL =
-                strtonum_or_err(optarg, "invalid argument", STRTO_INT);
+            ctl->maxTTL = strtoint_or_err(optarg, "invalid argument");
             if (ctl->maxTTL > (MaxHost - 1)) {
                 ctl->maxTTL = MaxHost - 1;
             }
             if (ctl->maxTTL < 1) {      /* prevent 0 hop */
                 ctl->maxTTL = 1;
             }
-            if (ctl->fstTTL > ctl->maxTTL) {    /* don't know the pos of -m or -f */
-                ctl->fstTTL = ctl->maxTTL;
-            }
             break;
         case 'U':
             ctl->maxUnknown =
-                strtonum_or_err(optarg, "invalid argument", STRTO_INT);
+                strtoint_or_err(optarg, "invalid argument");
             if (ctl->maxUnknown < 1) {
                 ctl->maxUnknown = 1;
             }
             break;
+        case 'E':
+            ctl->maxDisplayPath = strtoint_or_err(optarg, "invalid argument");
+            if (ctl->maxDisplayPath > MAX_PATH) {
+                ctl->maxDisplayPath = MAX_PATH;
+            }
+            break;
         case 'o':
             /* Check option before passing it on to fld_active. */
             if (strlen(optarg) > MAXFLD) {
@@ -536,7 +525,7 @@ static void parse_arg(
             break;
         case 'B':
             ctl->bitpattern =
-                strtonum_or_err(optarg, "invalid argument", STRTO_INT);
+                strtoint_or_err(optarg, "invalid argument");
             if (ctl->bitpattern > 255)
                 ctl->bitpattern = -1;
             break;
@@ -548,7 +537,7 @@ static void parse_arg(
             break;
         case 'Q':
             ctl->tos =
-                strtonum_or_err(optarg, "invalid argument", STRTO_INT);
+                strtoint_or_err(optarg, "invalid argument");
             if (ctl->tos > 255 || ctl->tos < 0) {
                 /* error message, should do more checking for valid values,
                  * details in rfc2474 */
@@ -589,7 +578,7 @@ static void parse_arg(
             break;
         case 'P':
             ctl->remoteport =
-                strtonum_or_err(optarg, "invalid argument", STRTO_INT);
+                strtoint_or_err(optarg, "invalid argument");
             if (ctl->remoteport < 1 || MaxPort < ctl->remoteport) {
                 error(EXIT_FAILURE, 0, "Illegal port number: %d",
                       ctl->remoteport);
@@ -597,7 +586,7 @@ static void parse_arg(
             break;
         case 'L':
             ctl->localport =
-                strtonum_or_err(optarg, "invalid argument", STRTO_INT);
+                strtoint_or_err(optarg, "invalid argument");
             if (ctl->localport < MinPort || MaxPort < ctl->localport) {
                 error(EXIT_FAILURE, 0, "Illegal port number: %d",
                       ctl->localport);
@@ -605,7 +594,7 @@ static void parse_arg(
             break;
         case 'Z':
             ctl->probe_timeout =
-                strtonum_or_err(optarg, "invalid argument", STRTO_INT);
+                strtoint_or_err(optarg, "invalid argument");
             ctl->probe_timeout *= 1000000;
             break;
         case '4':
@@ -619,7 +608,7 @@ static void parse_arg(
 #ifdef HAVE_IPINFO
         case 'y':
             ctl->ipinfo_no =
-                strtonum_or_err(optarg, "invalid argument", STRTO_INT);
+                strtoint_or_err(optarg, "invalid argument");
             if (ctl->ipinfo_no < 0 || 4 < ctl->ipinfo_no) {
                 error(EXIT_FAILURE, 0, "value %d out of range (0 - 4)",
                       ctl->ipinfo_no);
@@ -628,11 +617,19 @@ static void parse_arg(
         case 'z':
             ctl->ipinfo_no = 0;
             break;
+        case OPT_IPINFO4:
+            ctl->ipinfo_provider4 = optarg;
+            break;
+#ifdef ENABLE_IPV6
+        case OPT_IPINFO6:
+            ctl->ipinfo_provider6 = optarg;
+            break;
+#endif
 #endif
 #ifdef SO_MARK
         case 'M':
             ctl->mark =
-                strtonum_or_err(optarg, "invalid argument", STRTO_U32INT);
+                strtoulong_or_err(optarg, "invalid argument");
             break;
 #endif
         default:
@@ -649,6 +646,13 @@ static void parse_arg(
         ctl->DisplayMode == DisplayRaw || ctl->DisplayMode == DisplayCSV)
         ctl->Interactive = 0;
 
+    if (ctl->fstTTL > ctl->maxTTL) {
+        fprintf (stderr, "%s: firstTTL(%d) cannot be larger than maxTTL(%d). \n", 
+		argv[0], ctl->fstTTL, ctl->maxTTL);
+        exit (1);
+        //ctl->fstTTL = ctl->maxTTL;
+    }
+
     if (optind > argc - 1)
         return;
 
@@ -728,6 +732,7 @@ int main(
     int argc,
     char **argv)
 {
+    int exit_val = EXIT_SUCCESS;
     names_t *names_head = NULL;
     names_t *names_walk;
 
@@ -747,9 +752,17 @@ int main(
     ctl.fstTTL = 1;
     ctl.maxTTL = 30;
     ctl.maxUnknown = 12;
+    ctl.maxDisplayPath = 8;
     ctl.probe_timeout = 10 * 1000000;
     ctl.ipinfo_no = -1;
     ctl.ipinfo_max = -1;
+#ifdef HAVE_IPINFO
+    ctl.ipinfo_provider4 = "origin.asn.cymru.com";
+#ifdef ENABLE_IPV6
+    ctl.ipinfo_provider6 = "origin6.asn.cymru.com";
+#endif
+#endif
+
     xstrncpy(ctl.fld_active, "LS NABWV", 2 * MAXFLD);
 
     /*
@@ -764,6 +777,9 @@ int main(
     /* This will check if stdout/stderr writing is successful */
     atexit(close_stdout);
 
+    /* Set encoding for reports */
+    setlocale(LC_CTYPE, "C.UTF-8");
+
     /* reset the random seed */
     init_rand();
 
@@ -801,6 +817,7 @@ int main(
             if (ctl.Interactive)
                 exit(EXIT_FAILURE);
             else {
+                exit_val = EXIT_FAILURE;
                 names_walk = names_walk->next;
                 continue;
             }
@@ -811,6 +828,7 @@ int main(
             if (ctl.Interactive)
                 exit(EXIT_FAILURE);
             else {
+                exit_val = EXIT_FAILURE;
                 names_walk = names_walk->next;
                 continue;
             }
@@ -846,5 +864,5 @@ int main(
         item = NULL;
     }
 
-    return 0;
+    return exit_val;
 }
diff --git a/ui/mtr.h b/ui/mtr.h
index 8be1da9..dcf02d7 100644
--- a/ui/mtr.h
+++ b/ui/mtr.h
@@ -63,8 +63,8 @@ typedef int time_t;
 #define FLD_INDEX_SZ 256
 
 /* net related definitions */
-#define SAVED_PINGS 200
-#define MAX_PATH 8
+#define SAVED_PINGS 400
+#define MAX_PATH 128
 #define MaxHost 256
 #define MinPort 1024
 #define MaxPort 65535
@@ -103,6 +103,7 @@ struct mtr_ctl {
     int fstTTL;                 /* initial hub(ttl) to ping byMin */
     int maxTTL;                 /* last hub to ping byMin */
     int maxUnknown;             /* stop ping threshold */
+    int maxDisplayPath;         /* maximum number of ECMP paths to display */
     int remoteport;             /* target port for TCP tracing */
     int localport;              /* source port for UDP tracing */
     int probe_timeout;          /* timeout for probe sockets */
@@ -116,7 +117,13 @@ struct mtr_ctl {
      ForceMaxPing:1,
         use_dns:1,
         show_ips:1,
-        enablempls:1, dns:1, reportwide:1, Interactive:1, DisplayMode:5;
+        enablempls:1, dns:1, reportwide:1, Interactive:1, DisplayMode:5, CompactLayout:1;
+#ifdef HAVE_IPINFO
+#ifdef ENABLE_IPV6
+    char *ipinfo_provider6;
+#endif
+    char *ipinfo_provider4;
+#endif
 };
 
 /* dynamic field drawing */
diff --git a/ui/net.c b/ui/net.c
index efeb782..ce2c2c9 100644
--- a/ui/net.c
+++ b/ui/net.c
@@ -561,8 +561,13 @@ int net_send_batch(
                smaller (reasonable packet sizes), and our rand() range much
                larger, this effect is insignificant. Oh! That other formula
                didn't work. */
-            packetsize =
-                MINPACKET + rand() % (-ctl->cpacketsize - MINPACKET);
+            if (-ctl->cpacketsize <= MINPACKET) {
+                /* There is no room to introduce randomness. */
+                packetsize = MINPACKET;
+            } else {
+                packetsize =
+                    MINPACKET + rand() % (-ctl->cpacketsize - MINPACKET);
+            }
         } else {
             packetsize = ctl->cpacketsize;
         }
@@ -687,8 +692,8 @@ static void net_find_interface_address_from_name(
   host by connecting a UDP socket and checking the address
   the socket is bound to.
 */
-static void net_find_local_address(
-    void)
+static
+void net_find_local_address(struct mtr_ctl * ctl)
 {
     int udp_socket;
     int addr_length;
@@ -700,6 +705,15 @@ static void net_find_local_address(
         error(EXIT_FAILURE, errno, "udp socket creation failed");
     }
 
+#ifdef SO_MARK
+    /* On Linux, the packet mark can affect the selection of the source address */
+    if(ctl->mark) {
+        if(setsockopt(udp_socket, SOL_SOCKET, SO_MARK, &ctl->mark, sizeof(ctl->mark))) {
+            error(EXIT_FAILURE, errno, "failed to set the packet mark");
+        }
+    }
+#endif
+
     /*
        We need to set the port to a non-zero value for the connect
        to succeed.
@@ -778,7 +792,7 @@ void net_reopen(
             &sourcesockaddr_struct, ctl->af, ctl->InterfaceName);
         inet_ntop(sourcesockaddr->sa_family, sourceaddress, localaddr, sizeof(localaddr));
     } else {
-        net_find_local_address();
+        net_find_local_address(ctl);
     }
 
 }
@@ -881,6 +895,7 @@ int addrcmp(
         break;
 #ifdef ENABLE_IPV6
     case AF_INET6:
+    case AF_UNSPEC:
         rc = memcmp(a, b, sizeof(struct in6_addr));
         break;
 #endif
diff --git a/ui/report.c b/ui/report.c
index a63ed55..4b03c0d 100644
--- a/ui/report.c
+++ b/ui/report.c
@@ -140,7 +140,7 @@ void report_close(
             continue;
 
         snprintf(fmt, sizeof(fmt), "%%%ds", data_fields[j].length);
-        snprintf(buf + len, sizeof(buf), fmt, data_fields[j].title);
+        snprintf(buf + len, sizeof(buf) - len, fmt, data_fields[j].title);
         len += data_fields[j].length;
     }
     printf("%s\n", buf);
@@ -172,10 +172,10 @@ void report_close(
 
             /* 1000.0 is a temporary hack for stats usec to ms, impacted net_loss. */
             if (strchr(data_fields[j].format, 'f')) {
-                snprintf(buf + len, sizeof(buf), data_fields[j].format,
+                snprintf(buf + len, sizeof(buf) - len, data_fields[j].format,
                          data_fields[j].net_xxx(at) / 1000.0);
             } else {
-                snprintf(buf + len, sizeof(buf), data_fields[j].format,
+                snprintf(buf + len, sizeof(buf) - len, data_fields[j].format,
                          data_fields[j].net_xxx(at));
             }
             len += data_fields[j].length;
@@ -185,7 +185,7 @@ void report_close(
         /* This feature shows 'loadbalances' on routes */
 
         /* Print list of all hosts that have responded from ttl = at + 1 away */
-        for (z = 0; z < MAX_PATH; z++) {
+        for (z = 0; z < ctl->maxDisplayPath; z++) {
             int found = 0;
             addr2 = net_addrs(at, z);
             mplss = net_mplss(at, z);
@@ -406,8 +406,9 @@ void xml_close(
     int i, j, at, max;
     ip_t *addr;
     char name[MAX_FORMAT_STR];
+    char buf[128];
 
-    printf("<?xml version=\"1.0\"?>\n");
+    printf("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
     printf("<MTR SRC=\"%s\" DST=\"%s\"", ctl->LocalHostname,
            ctl->Hostname);
     printf(" TOS=\"0x%X\"", ctl->tos);
@@ -438,9 +439,6 @@ void xml_close(
             if (j <= 0)
                 continue;       /* Field nr 0, " " shouldn't be printed in this method. */
 
-            snprintf(name, sizeof(name), "%s%s%s", "        <%s>",
-                     data_fields[j].format, "</%s>\n");
-
             /* XML doesn't allow "%" in tag names, rename Loss% to just Loss */
             title = data_fields[j].title;
             if (strcmp(data_fields[j].title, "Loss%") == 0) {
@@ -449,11 +447,12 @@ void xml_close(
 
             /* 1000.0 is a temporary hack for stats usec to ms, impacted net_loss. */
             if (strchr(data_fields[j].format, 'f')) {
-                printf(name,
-                       title, data_fields[j].net_xxx(at) / 1000.0, title);
+                snprintf(buf, sizeof(buf), data_fields[j].format, data_fields[j].net_xxx(at) / 1000.0);
             } else {
-                printf(name, title, data_fields[j].net_xxx(at), title);
+                snprintf(buf, sizeof(buf), data_fields[j].format, data_fields[j].net_xxx(at));
             }
+            trim(buf, 0);
+            printf("        <%s>%s</%s>\n", title, buf, title);
         }
         printf("    </HUB>\n");
     }
@@ -531,7 +530,7 @@ void csv_close(
         if (ctl->reportwide == 0)
             continue;
         
-        for (z = 0; z < MAX_PATH; z++) {
+        for (z = 0; z < ctl->maxDisplayPath; z++) {
             int found = 0;
             addr2 = net_addrs(at, z);
             snprint_addr(ctl, name, sizeof(name), addr2);
diff --git a/ui/select.c b/ui/select.c
index 1a9f5c2..b44fc53 100644
--- a/ui/select.c
+++ b/ui/select.c
@@ -224,6 +224,10 @@ void select_loop(
                 ctl->display_mode =
                     (ctl->display_mode + 1) % DisplayModeMAX;
                 break;
+            case ActionCompact:
+                ctl->CompactLayout = !ctl->CompactLayout;
+                display_clear(ctl);
+                break;
             case ActionClear:
                 display_clear(ctl);
                 break;
diff --git a/ui/split.c b/ui/split.c
index c88b4fe..60520e8 100644
--- a/ui/split.c
+++ b/ui/split.c
@@ -136,7 +136,7 @@ void split_open(
 #endif
     LineCount = -1;
     for (i = 0; i < MAX_LINE_COUNT; i++) {
-        xstrncpy(Lines[i], "???", MAX_LINE_SIZE);
+        xstrncpy(Lines[i], "", MAX_LINE_SIZE);
     }
 }
 
diff --git a/ui/utils.c b/ui/utils.c
index edd5905..8e8c6d8 100644
--- a/ui/utils.c
+++ b/ui/utils.c
@@ -70,28 +70,37 @@ char *trim(
 }
 
 /* Parse string, and return positive signed int. */
-int strtonum_or_err(
+int strtoint_or_err(
     const char *str,
-    const char *errmesg,
-    const int type)
+    const char *errmesg)
+{
+    int num;
+    char *end = NULL;
+
+    if (str != NULL && *str != '\0') {
+        errno = 0;
+        num = strtol(str, &end, 0);
+        if (errno == 0 && str != end && end != NULL && *end == '\0') {
+            return num;
+        }
+    }
+    error(EXIT_FAILURE, errno, "%s: '%s'", errmesg, str);
+    return 0;
+}
+
+/* Parse string, and return positive unsigned long int. */
+unsigned long strtoulong_or_err(
+    const char *str,
+    const char *errmesg)
 {
     unsigned long int num;
     char *end = NULL;
 
     if (str != NULL && *str != '\0') {
         errno = 0;
-        num = strtoul(str, &end, 10);
+        num = strtoul(str, &end, 0);
         if (errno == 0 && str != end && end != NULL && *end == '\0') {
-            switch (type) {
-            case STRTO_INT:
-                if (num < INT_MAX)
-                    return num;
-                break;
-            case STRTO_U32INT:
-                if (num < UINT32_MAX)
-                    return num;
-                break;
-            }
+            return num;
         }
     }
     error(EXIT_FAILURE, errno, "%s: '%s'", errmesg, str);
diff --git a/ui/utils.h b/ui/utils.h
index 1a44a8c..3aa7267 100644
--- a/ui/utils.h
+++ b/ui/utils.h
@@ -17,18 +17,15 @@
     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
-enum {
-    STRTO_INT,
-    STRTO_U32INT
-};
-
 extern char *trim(
     char *s,
     const char c);
-extern int strtonum_or_err(
+extern int strtoint_or_err(
+    const char *str,
+    const char *errmesg);
+extern unsigned long strtoulong_or_err(
     const char *str,
-    const char *errmesg,
-    const int type);
+    const char *errmesg);
 extern float strtofloat_or_err(
     const char *str,
     const char *errmesg);
