Repository: fujiapple852/trippy (language: rust)

Issue:
Automatically fall back to IPv6 if host has no IPv4 addresses
Currently if you don't specify `-6` argument then trippy fails to resolve addresses for IPv6 only hosts and exits with an error. Default seems to be set IPv4. It would be great if it could use any address family available.


Discussion / hints:
Hi there @artizirk !

Thanks for the suggestion.

What would be the expected behaviour if both IPv4 and IPv6 were available?  How would the `-4` and `-6` flags influence that?

Trippy uses `trust-dns` (unless using the system resolver, which is the default) which is configured with a `LookupIpStrategy` that supports various modes:

```rust
pub enum LookupIpStrategy {
    /// Only query for A (Ipv4) records
    Ipv4Only,
    /// Only query for AAAA (Ipv6) records
    Ipv6Only,
    /// Query for A and AAAA in parallel
    Ipv4AndIpv6,
    /// Query for Ipv6 if that fails, query for Ipv4
    Ipv6thenIpv4,
    /// Query for Ipv4 if that fails, query for Ipv6 (default)
    Ipv4thenIpv6,
}
```

Today, Trippy uses only the `Ipv4Only` and `Ipv6Only` modes but it could be enhanced to use the others.  A similar change could _probably_ be made to the system resolver.  I don't think `Ipv4AndIpv6` would be useful.

> Default seems to be set IPv4

Note that you can change the default in the `trippy.toml` [config file](https://github.com/fujiapple852/trippy?tab=readme-ov-file#configuration-reference).

```toml
addr-family = "ipv6"
```
Hi!

Flags `-4` and `-6` should still force `Ipv4Only` and `Ipv6Only` modes. Default without those arguments should be `trust-dns` `Ipv4thenIpv6` strategy or whatever the system resolver does without a filter.

I have created an PR #872 that implements this.
Hi again @artizirk, thanks for the PR!

Thinking about this a bit more, some considerations below.

### Defaults and overrides

> Default without those arguments should be trust-dns `Ipv4thenIpv6` strategy

I was a bit uneasy about this at first, as it seems to express an option of favouring Ipv4 over Ipv6.  However...

Trippy currently supports both the `addr-family` config file entry and the `--ipv4` (`-4`) and `--ipv6` (`-6`) command line flags, all of which are optional.  The command line flags (which are mutually exclusive) take precedence over the config file entry and if neither are provided there is a default of Ipv4.  

Therefore Trippy is _already_ opinionated as it defaults to Ipv4.  

However, users are able to _change_ the default opinion in the config file (`addr-family`), and so we would need to provide the same capability here, by exposing all modes (`Ipv4Only`, `Ipv6Only`, `Ipv4thenIpv6` & `Ipv6thenIpv4`) in the config file for `addr-family` as well.

> Flags -4 and -6 should still force Ipv4Only and Ipv6Only modes.

Yes I think that is right.  The `--ipv4` (`-4`) and `--ipv6` (`-6`) can stay as shortcuts for `Ipv4Only` and `Ipv6Only` respectively, and continue to override whatever is set in the config file.

### System Resolver

The logic for the `system` resolver should match the logic for the other resolvers (`google`, `cloudflare` etc) which use `trust-dns`, which is an implementation detail users should not be aware of (fyi as great as it is, I'm looking to swap it for an non-async DNS resolver crate if I can).  This means we need to find a way to emulate the various modes provided by `trust-dns` in the `system` resolver.  

The `system` resolver will (depending on how the system is configured) returns both Ipv4 and Ipv6 results and so Trippy currently [filters](https://github.com/fujiapple852/trippy/blob/ee5fb8a06f619de0aff1ea8245315d648d8657b1/src/dns/lazy_resolver.rs#L214) the results based on the configured mode.

Therefore we would need to make this filtering smarter to respect the various "Fallback" options that `trust-dns` provides.  

Once we have that, it may make sense to _only_ use the `Ipv4AndIpv6` mode for `trust-dns`, and apply the logic after the call in a uniform way that is independent of the resolver used.

### Dns Resolve All 

Trippy 0.9.0 introduced the `dns-resolve-all` option where multiple ICMP traces will be started, one for each IP resolved for a given DNS entry.  Today this will be all Ipv4 or all Ipv6, never a mixture.  It would seems sensible that the same logic would apply, even when users specify the `Ipv4thenIpv6` or `Ipv6thenIpv4` config.  

If we wanted to support mixing (I'm not sure that I do...) then perhaps the `dns-resolve-all` flag should renamed to `dns-resolve-mode` and changed from a boolean to an enum where you can select something like `First`, `AllForFamily` or `All` where the latter mixes Ipv4 and Ipv6.
@artizirk do you think you can your PR to cover the above?  Let me know if anything is unclear, happy to help guide you on chat (i'm fujiapple852 on both Matrix and Discord)

Fix patch:
diff --git a/src/config.rs b/src/config.rs
index eb7ad8785..aed1c55c9 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -10,10 +10,9 @@ use std::collections::HashMap;
 use std::net::IpAddr;
 use std::str::FromStr;
 use std::time::Duration;
-use trippy::dns::ResolveMethod;
+use trippy::dns::{IpAddrFamily, ResolveMethod};
 use trippy::tracing::{
-    defaults, AddrFamily, IcmpExtensionParseMode, MultipathStrategy, PortDirection, PrivilegeMode,
-    Protocol,
+    defaults, IcmpExtensionParseMode, MultipathStrategy, PortDirection, PrivilegeMode, Protocol,
 };
 
 mod binding;
@@ -76,20 +75,28 @@ impl From<Protocol> for ProtocolConfig {
 }
 
 /// The address family.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum, Deserialize)]
 #[serde(rename_all = "kebab-case")]
 pub enum AddressFamilyConfig {
-    /// Internet Protocol V4
+    /// Ipv4 only.
     Ipv4,
-    /// Internet Protocol V6
+    /// Ipv6 only.
     Ipv6,
+    /// Ipv6 with a fallback to Ipv4
+    #[serde(rename = "ipv6-then-ipv4")]
+    Ipv6ThenIpv4,
+    /// Ipv4 with a fallback to Ipv6
+    #[serde(rename = "ipv4-then-ipv6")]
+    Ipv4ThenIpv6,
 }
 
-impl From<AddrFamily> for AddressFamilyConfig {
-    fn from(value: AddrFamily) -> Self {
+impl From<IpAddrFamily> for AddressFamilyConfig {
+    fn from(value: IpAddrFamily) -> Self {
         match value {
-            AddrFamily::Ipv4 => Self::Ipv4,
-            AddrFamily::Ipv6 => Self::Ipv6,
+            IpAddrFamily::Ipv4Only => Self::Ipv4,
+            IpAddrFamily::Ipv6Only => Self::Ipv6,
+            IpAddrFamily::Ipv6thenIpv4 => Self::Ipv6ThenIpv4,
+            IpAddrFamily::Ipv4thenIpv6 => Self::Ipv4ThenIpv6,
         }
     }
 }
@@ -270,7 +277,7 @@ impl TrippyAction {
 pub struct TrippyConfig {
     pub targets: Vec<String>,
     pub protocol: Protocol,
-    pub addr_family: AddrFamily,
+    pub addr_family: IpAddrFamily,
     pub first_ttl: u8,
     pub max_ttl: u8,
     pub min_round_duration: Duration,
@@ -378,6 +385,11 @@ impl TrippyConfig {
             cfg_file_strategy.protocol,
             ProtocolConfig::from(defaults::DEFAULT_STRATEGY_PROTOCOL),
         );
+        let addr_family_cfg = cfg_layer(
+            args.addr_family,
+            cfg_file_strategy.addr_family,
+            constants::DEFAULT_ADDR_FAMILY,
+        );
         let target_port = cfg_layer_opt(args.target_port, cfg_file_strategy.target_port);
         let source_port = cfg_layer_opt(args.source_port, cfg_file_strategy.source_port);
         let source_address = cfg_layer_opt(args.source_address, cfg_file_strategy.source_address);
@@ -541,19 +553,42 @@ impl TrippyConfig {
                     .map_err(|_| anyhow!("invalid source IP address format: {}", addr))
             })
             .transpose()?;
-        let addr_family = match (args.ipv4, args.ipv6, cfg_file_strategy.addr_family) {
-            (false, false, None) => defaults::DEFAULT_ADDRESS_FAMILY,
-            (false, false, Some(AddressFamilyConfig::Ipv4)) | (true, _, _) => AddrFamily::Ipv4,
-            (false, false, Some(AddressFamilyConfig::Ipv6)) | (_, true, _) => AddrFamily::Ipv6,
+
+        #[allow(clippy::match_same_arms)]
+        let addr_family = match (
+            args.ipv4,
+            args.ipv6,
+            addr_family_cfg,
+            multipath_strategy_cfg,
+        ) {
+            (false, false, AddressFamilyConfig::Ipv4, _) => IpAddrFamily::Ipv4Only,
+            (false, false, AddressFamilyConfig::Ipv6, _) => IpAddrFamily::Ipv6Only,
+            // we "downgrade" to `Ipv4Only` for `Dublin` rather than fail.
+            (false, false, AddressFamilyConfig::Ipv4ThenIpv6, MultipathStrategyConfig::Dublin) => {
+                IpAddrFamily::Ipv4Only
+            }
+            (false, false, AddressFamilyConfig::Ipv6ThenIpv4, MultipathStrategyConfig::Dublin) => {
+                IpAddrFamily::Ipv4Only
+            }
+            (false, false, AddressFamilyConfig::Ipv4ThenIpv6, _) => IpAddrFamily::Ipv4thenIpv6,
+            (false, false, AddressFamilyConfig::Ipv6ThenIpv4, _) => IpAddrFamily::Ipv6thenIpv4,
+            (true, _, _, _) => IpAddrFamily::Ipv4Only,
+            (_, true, _, _) => IpAddrFamily::Ipv6Only,
         };
+
+        #[allow(clippy::match_same_arms)]
         let multipath_strategy = match (multipath_strategy_cfg, addr_family) {
             (MultipathStrategyConfig::Classic, _) => Ok(MultipathStrategy::Classic),
             (MultipathStrategyConfig::Paris, _) => Ok(MultipathStrategy::Paris),
-            (MultipathStrategyConfig::Dublin, AddrFamily::Ipv4) => Ok(MultipathStrategy::Dublin),
-            (MultipathStrategyConfig::Dublin, AddrFamily::Ipv6) => Err(anyhow!(
+            (
+                MultipathStrategyConfig::Dublin,
+                IpAddrFamily::Ipv4Only | IpAddrFamily::Ipv4thenIpv6 | IpAddrFamily::Ipv6thenIpv4,
+            ) => Ok(MultipathStrategy::Dublin),
+            (MultipathStrategyConfig::Dublin, IpAddrFamily::Ipv6Only) => Err(anyhow!(
                 "Dublin multipath strategy not implemented for IPv6 yet!"
             )),
         }?;
+
         let port_direction = match (protocol, source_port, target_port, multipath_strategy_cfg) {
             (Protocol::Icmp, _, _, _) => PortDirection::None,
             (Protocol::Udp, None, None, _) => PortDirection::new_fixed_src(pid.max(1024)),
@@ -683,7 +718,7 @@ impl Default for TrippyConfig {
         Self {
             targets: vec![],
             protocol: defaults::DEFAULT_STRATEGY_PROTOCOL,
-            addr_family: defaults::DEFAULT_ADDRESS_FAMILY,
+            addr_family: dns_resolve_family(constants::DEFAULT_ADDR_FAMILY),
             first_ttl: defaults::DEFAULT_STRATEGY_FIRST_TTL,
             max_ttl: defaults::DEFAULT_STRATEGY_MAX_TTL,
             min_round_duration: defaults::DEFAULT_STRATEGY_MIN_ROUND_DURATION,
@@ -743,6 +778,15 @@ fn dns_resolve_method(dns_resolve_method: DnsResolveMethodConfig) -> ResolveMeth
     }
 }
 
+fn dns_resolve_family(dns_resolve_family: AddressFamilyConfig) -> IpAddrFamily {
+    match dns_resolve_family {
+        AddressFamilyConfig::Ipv4 => IpAddrFamily::Ipv4Only,
+        AddressFamilyConfig::Ipv6 => IpAddrFamily::Ipv6Only,
+        AddressFamilyConfig::Ipv6ThenIpv4 => IpAddrFamily::Ipv6thenIpv4,
+        AddressFamilyConfig::Ipv4ThenIpv6 => IpAddrFamily::Ipv4thenIpv6,
+    }
+}
+
 fn cfg_layer<T>(fst: Option<T>, snd: Option<T>, def: T) -> T {
     match (fst, snd) {
         (Some(val), _) | (None, Some(val)) => val,
@@ -1093,7 +1137,7 @@ mod tests {
     #[test_case("trip example.com", Ok(cfg().multipath_strategy(MultipathStrategy::Classic).build()); "default strategy")]
     #[test_case("trip example.com --multipath-strategy classic", Ok(cfg().multipath_strategy(MultipathStrategy::Classic).build()); "classic strategy")]
     #[test_case("trip example.com --multipath-strategy paris --udp", Ok(cfg().multipath_strategy(MultipathStrategy::Paris).protocol(Protocol::Udp).port_direction(PortDirection::FixedSrc(Port(1024))).build()); "paris strategy")]
-    #[test_case("trip example.com --multipath-strategy dublin --udp", Ok(cfg().multipath_strategy(MultipathStrategy::Dublin).protocol(Protocol::Udp).port_direction(PortDirection::FixedSrc(Port(1024))).build()); "dublin strategy")]
+    #[test_case("trip example.com --multipath-strategy dublin --udp", Ok(cfg().multipath_strategy(MultipathStrategy::Dublin).protocol(Protocol::Udp).addr_family(IpAddrFamily::Ipv4Only).port_direction(PortDirection::FixedSrc(Port(1024))).build()); "dublin strategy")]
     #[test_case("trip example.com --multipath-strategy tokyo", Err(anyhow!("error: one of the values isn't valid for an argument")); "invalid strategy")]
     #[test_case("trip example.com", Ok(cfg().protocol(Protocol::Icmp).port_direction(PortDirection::None).build()); "default protocol")]
     #[test_case("trip example.com --protocol icmp", Ok(cfg().protocol(Protocol::Icmp).port_direction(PortDirection::None).build()); "icmp protocol")]
@@ -1116,18 +1160,24 @@ mod tests {
     #[test_case("trip example.com --udp --multipath-strategy paris --source-port 33000", Ok(cfg().protocol(Protocol::Udp).multipath_strategy(MultipathStrategy::Paris).port_direction(PortDirection::FixedSrc(Port(33000))).build()); "udp protocol paris strategy custom src port")]
     #[test_case("trip example.com --udp --multipath-strategy paris --target-port 5000", Ok(cfg().protocol(Protocol::Udp).multipath_strategy(MultipathStrategy::Paris).port_direction(PortDirection::FixedDest(Port(5000))).build()); "udp protocol paris strategy custom target port")]
     #[test_case("trip example.com --udp --multipath-strategy paris --source-port 33000 --target-port 5000", Ok(cfg().protocol(Protocol::Udp).multipath_strategy(MultipathStrategy::Paris).port_direction(PortDirection::FixedBoth(Port(33000), Port(5000))).build()); "udp protocol paris strategy custom both ports")]
-    #[test_case("trip example.com --udp --multipath-strategy dublin", Ok(cfg().protocol(Protocol::Udp).multipath_strategy(MultipathStrategy::Dublin).port_direction(PortDirection::FixedSrc(Port(1024))).build()); "udp protocol dublin strategy default ports")]
-    #[test_case("trip example.com --udp --multipath-strategy dublin --source-port 33000", Ok(cfg().protocol(Protocol::Udp).multipath_strategy(MultipathStrategy::Dublin).port_direction(PortDirection::FixedSrc(Port(33000))).build()); "udp protocol dublin strategy custom src port")]
-    #[test_case("trip example.com --udp --multipath-strategy dublin --target-port 5000", Ok(cfg().protocol(Protocol::Udp).multipath_strategy(MultipathStrategy::Dublin).port_direction(PortDirection::FixedDest(Port(5000))).build()); "udp protocol dublin strategy custom target port")]
-    #[test_case("trip example.com --udp --multipath-strategy dublin --source-port 33000 --target-port 5000", Ok(cfg().protocol(Protocol::Udp).multipath_strategy(MultipathStrategy::Dublin).port_direction(PortDirection::FixedBoth(Port(33000), Port(5000))).build()); "udp protocol dublin strategy custom both ports")]
+    #[test_case("trip example.com --udp --multipath-strategy dublin", Ok(cfg().protocol(Protocol::Udp).multipath_strategy(MultipathStrategy::Dublin).addr_family(IpAddrFamily::Ipv4Only).port_direction(PortDirection::FixedSrc(Port(1024))).build()); "udp protocol dublin strategy default ports")]
+    #[test_case("trip example.com --udp --multipath-strategy dublin --source-port 33000", Ok(cfg().protocol(Protocol::Udp).multipath_strategy(MultipathStrategy::Dublin).addr_family(IpAddrFamily::Ipv4Only).port_direction(PortDirection::FixedSrc(Port(33000))).build()); "udp protocol dublin strategy custom src port")]
+    #[test_case("trip example.com --udp --multipath-strategy dublin --target-port 5000", Ok(cfg().protocol(Protocol::Udp).multipath_strategy(MultipathStrategy::Dublin).addr_family(IpAddrFamily::Ipv4Only).port_direction(PortDirection::FixedDest(Port(5000))).build()); "udp protocol dublin strategy custom target port")]
+    #[test_case("trip example.com --udp --multipath-strategy dublin --source-port 33000 --target-port 5000", Ok(cfg().protocol(Protocol::Udp).multipath_strategy(MultipathStrategy::Dublin).addr_family(IpAddrFamily::Ipv4Only).port_direction(PortDirection::FixedBoth(Port(33000), Port(5000))).build()); "udp protocol dublin strategy custom both ports")]
     #[test_case("trip example.com --icmp --multipath-strategy paris", Err(anyhow!("Paris multipath strategy not support for icmp")); "paris with invalid protocol icmp")]
     #[test_case("trip example.com --icmp --multipath-strategy dublin", Err(anyhow!("Dublin multipath strategy not support for icmp")); "dublin with invalid protocol icmp")]
     #[test_case("trip example.com --tcp --multipath-strategy paris", Err(anyhow!("Paris multipath strategy not yet supported for tcp")); "paris with invalid protocol tcp")]
     #[test_case("trip example.com --tcp --multipath-strategy dublin", Err(anyhow!("Dublin multipath strategy not yet supported for tcp")); "dublin with invalid protocol tcp")]
     #[test_case("trip example.com --udp --source-port 33000 --target-port 5000", Err(anyhow!("only one of source-port and target-port may be fixed (except IPv4/udp protocol with dublin or paris strategy)")); "udp protocol custom both ports with invalid strategy")]
-    #[test_case("trip example.com", Ok(cfg().addr_family(AddrFamily::Ipv4).build()); "default address family shortcut")]
-    #[test_case("trip example.com -4", Ok(cfg().addr_family(AddrFamily::Ipv4).build()); "ipv4 address family shortcut")]
-    #[test_case("trip example.com -6", Ok(cfg().addr_family(AddrFamily::Ipv6).build()); "ipv6 address family shortcut")]
+    #[test_case("trip example.com", Ok(cfg().addr_family(IpAddrFamily::Ipv4thenIpv6).build()); "default address family")]
+    #[test_case("trip example.com --addr-family ipv4", Ok(cfg().addr_family(IpAddrFamily::Ipv4Only).build()); "ipv4 address family")]
+    #[test_case("trip example.com --addr-family ipv6", Ok(cfg().addr_family(IpAddrFamily::Ipv6Only).build()); "ipv6 address family")]
+    #[test_case("trip example.com --addr-family ipv4-then-ipv6", Ok(cfg().addr_family(IpAddrFamily::Ipv4thenIpv6).build()); "ipv4 then ipv6 address family")]
+    #[test_case("trip example.com --addr-family ipv6-then-ipv4", Ok(cfg().addr_family(IpAddrFamily::Ipv6thenIpv4).build()); "ipv6 then ipv4 address family")]
+    #[test_case("trip example.com -F ipv4", Ok(cfg().addr_family(IpAddrFamily::Ipv4Only).build()); "custom address family short")]
+    #[test_case("trip example.com --addr-family foo", Err(anyhow!("error: one of the values isn't valid for an argument")); "invalid address family")]
+    #[test_case("trip example.com -4", Ok(cfg().addr_family(IpAddrFamily::Ipv4Only).build()); "ipv4 address family shortcut")]
+    #[test_case("trip example.com -6", Ok(cfg().addr_family(IpAddrFamily::Ipv6Only).build()); "ipv6 address family shortcut")]
     #[test_case("trip example.com -5", Err(anyhow!("error: unexpected argument found")); "invalid address family shortcut")]
     #[test_case("trip example.com", Ok(cfg().first_ttl(1).build()); "default first ttl")]
     #[test_case("trip example.com --first-ttl 5", Ok(cfg().first_ttl(5).build()); "custom first ttl")]
@@ -1316,7 +1366,7 @@ mod tests {
             }
         }
 
-        pub fn addr_family(self, addr_family: AddrFamily) -> Self {
+        pub fn addr_family(self, addr_family: IpAddrFamily) -> Self {
             Self {
                 config: TrippyConfig {
                     addr_family,
diff --git a/src/config/cmd.rs b/src/config/cmd.rs
index 2e58e96cb..6299c06de 100644
--- a/src/config/cmd.rs
+++ b/src/config/cmd.rs
@@ -1,8 +1,9 @@
 use crate::config::binding::TuiCommandItem;
 use crate::config::theme::TuiThemeItem;
 use crate::config::{
-    AddressMode, AsMode, DnsResolveMethodConfig, GeoIpMode, IcmpExtensionMode, LogFormat,
-    LogSpanEvents, Mode, MultipathStrategyConfig, ProtocolConfig, TuiColor, TuiKeyBinding,
+    AddressFamilyConfig, AddressMode, AsMode, DnsResolveMethodConfig, GeoIpMode, IcmpExtensionMode,
+    LogFormat, LogSpanEvents, Mode, MultipathStrategyConfig, ProtocolConfig, TuiColor,
+    TuiKeyBinding,
 };
 use anyhow::anyhow;
 use clap::builder::Styles;
@@ -60,12 +61,26 @@ pub struct Args {
     )]
     pub icmp: bool,
 
+    /// The address family [default: Ipv4thenIpv6]
+    #[arg(value_enum, short = 'F', long)]
+    pub addr_family: Option<AddressFamilyConfig>,
+
     /// Use IPv4 only
-    #[arg(short = '4', long, conflicts_with = "ipv6")]
+    #[arg(
+        short = '4',
+        long,
+        conflicts_with = "ipv6",
+        conflicts_with = "addr_family"
+    )]
     pub ipv4: bool,
 
     /// Use IPv6 only
-    #[arg(short = '6', long, conflicts_with = "ipv4")]
+    #[arg(
+        short = '6',
+        long,
+        conflicts_with = "ipv4",
+        conflicts_with = "addr_family"
+    )]
     pub ipv6: bool,
 
     /// The target port (TCP & UDP only) [default: 80]
diff --git a/src/config/constants.rs b/src/config/constants.rs
index 32a2211b1..babec3606 100644
--- a/src/config/constants.rs
+++ b/src/config/constants.rs
@@ -1,6 +1,6 @@
 use crate::config::{
-    AddressMode, AsMode, DnsResolveMethodConfig, GeoIpMode, IcmpExtensionMode, LogFormat,
-    LogSpanEvents, Mode,
+    AddressFamilyConfig, AddressMode, AsMode, DnsResolveMethodConfig, GeoIpMode, IcmpExtensionMode,
+    LogFormat, LogSpanEvents, Mode,
 };
 use std::time::Duration;
 
@@ -61,6 +61,9 @@ pub const DEFAULT_TUI_PRIVACY_MAX_TTL: u8 = 0;
 /// The default value for `dns-resolve-method`.
 pub const DEFAULT_DNS_RESOLVE_METHOD: DnsResolveMethodConfig = DnsResolveMethodConfig::System;
 
+/// The default value for `addr-family`.
+pub const DEFAULT_ADDR_FAMILY: AddressFamilyConfig = AddressFamilyConfig::Ipv4ThenIpv6;
+
 /// The default value for `dns-lookup-as-info`.
 pub const DEFAULT_DNS_LOOKUP_AS_INFO: bool = false;
 
diff --git a/src/config/file.rs b/src/config/file.rs
index 4819ac9cb..5b457b761 100644
--- a/src/config/file.rs
+++ b/src/config/file.rs
@@ -147,7 +147,7 @@ impl Default for ConfigStrategy {
     fn default() -> Self {
         Self {
             protocol: Some(ProtocolConfig::from(defaults::DEFAULT_STRATEGY_PROTOCOL)),
-            addr_family: Some(AddressFamilyConfig::from(defaults::DEFAULT_ADDRESS_FAMILY)),
+            addr_family: Some(super::constants::DEFAULT_ADDR_FAMILY),
             target_port: None,
             source_port: None,
             source_address: None,
diff --git a/src/dns.rs b/src/dns.rs
index 4aa29d5a4..8c59773e4 100644
--- a/src/dns.rs
+++ b/src/dns.rs
@@ -7,5 +7,5 @@
 mod lazy_resolver;
 mod resolver;
 
-pub use lazy_resolver::{Config, DnsResolver, ResolveMethod};
+pub use lazy_resolver::{Config, DnsResolver, IpAddrFamily, ResolveMethod};
 pub use resolver::{AsInfo, DnsEntry, Error, Resolved, Resolver, Result, Unresolved};
diff --git a/src/dns/lazy_resolver.rs b/src/dns/lazy_resolver.rs
index 6d6b569fc..366c36522 100644
--- a/src/dns/lazy_resolver.rs
+++ b/src/dns/lazy_resolver.rs
@@ -1,4 +1,5 @@
 use crate::dns::resolver::{DnsEntry, ResolvedIpAddrs, Resolver, Result};
+use std::fmt::{Display, Formatter};
 use std::net::IpAddr;
 use std::rc::Rc;
 use std::time::Duration;
@@ -8,7 +9,7 @@ use std::time::Duration;
 pub struct Config {
     /// The method to use for DNS resolution.
     pub resolve_method: ResolveMethod,
-    /// The address family to lookup.
+    /// The IP address resolution family.
     pub addr_family: IpAddrFamily,
     /// The timeout for DNS resolution.
     pub timeout: Duration,
@@ -27,32 +28,41 @@ pub enum ResolveMethod {
     Cloudflare,
 }
 
-/// The address family.
-#[derive(Debug, Copy, Clone)]
+/// How to resolve IP addresses.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub enum IpAddrFamily {
-    /// Internet Protocol v4.
-    Ipv4,
-    /// Internet Protocol v6.
-    Ipv6,
+    /// Lookup Ipv4 only.
+    Ipv4Only,
+    /// Lookup Ipv6 only.
+    Ipv6Only,
+    /// Lookup Ipv6 with a fallback to Ipv4
+    Ipv6thenIpv4,
+    /// Lookup Ipv4 with a fallback to Ipv6
+    Ipv4thenIpv6,
 }
 
-impl Config {
-    /// Create an IPv4 `Config`.
-    #[must_use]
-    pub fn new_ipv4(resolve_method: ResolveMethod, timeout: Duration) -> Self {
-        Self {
-            resolve_method,
-            addr_family: IpAddrFamily::Ipv4,
-            timeout,
+impl Display for IpAddrFamily {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Ipv4Only => write!(f, "Ipv4Only"),
+            Self::Ipv6Only => write!(f, "Ipv6Only"),
+            Self::Ipv6thenIpv4 => write!(f, "Ipv6thenIpv4"),
+            Self::Ipv4thenIpv6 => write!(f, "Ipv4thenIpv6"),
         }
     }
+}
 
-    /// Create an IPv6 `Config`.
+impl Config {
+    /// Create a `Config`.
     #[must_use]
-    pub fn new_ipv6(resolve_method: ResolveMethod, timeout: Duration) -> Self {
+    pub fn new(
+        resolve_method: ResolveMethod,
+        addr_family: IpAddrFamily,
+        timeout: Duration,
+    ) -> Self {
         Self {
             resolve_method,
-            addr_family: IpAddrFamily::Ipv6,
+            addr_family,
             timeout,
         }
     }
@@ -118,7 +128,7 @@ mod inner {
     use hickory_resolver::error::ResolveErrorKind;
     use hickory_resolver::proto::rr::RecordType;
     use hickory_resolver::{Name, Resolver};
-    use itertools::Itertools;
+    use itertools::{Either, Itertools};
     use parking_lot::RwLock;
     use std::collections::HashMap;
     use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
@@ -168,8 +178,10 @@ mod inner {
                 let mut options = ResolverOpts::default();
                 options.timeout = config.timeout;
                 options.ip_strategy = match config.addr_family {
-                    IpAddrFamily::Ipv4 => LookupIpStrategy::Ipv4Only,
-                    IpAddrFamily::Ipv6 => LookupIpStrategy::Ipv6Only,
+                    IpAddrFamily::Ipv4Only => LookupIpStrategy::Ipv4Only,
+                    IpAddrFamily::Ipv6Only => LookupIpStrategy::Ipv6Only,
+                    IpAddrFamily::Ipv6thenIpv4 => LookupIpStrategy::Ipv6thenIpv4,
+                    IpAddrFamily::Ipv4thenIpv6 => LookupIpStrategy::Ipv4thenIpv6,
                 };
                 let res = match config.resolve_method {
                     ResolveMethod::Resolv => Resolver::from_system_conf(),
@@ -208,17 +220,45 @@ mod inner {
                     .map_err(|err| Error::LookupFailed(Box::new(err)))?
                     .iter()
                     .collect::<Vec<_>>()),
-                DnsProvider::DnsLookup => Ok(dns_lookup::lookup_host(hostname)
-                    .map_err(|err| Error::LookupFailed(Box::new(err)))?
-                    .into_iter()
-                    .filter(|addr| {
-                        matches!(
-                            (self.config.addr_family, addr),
-                            (IpAddrFamily::Ipv4, IpAddr::V4(_))
-                                | (IpAddrFamily::Ipv6, IpAddr::V6(_))
-                        )
+                DnsProvider::DnsLookup => {
+                    let (ipv4, ipv6): (Vec<_>, Vec<_>) = dns_lookup::lookup_host(hostname)
+                        .map_err(|err| Error::LookupFailed(Box::new(err)))?
+                        .into_iter()
+                        .partition_map(|ip| match ip {
+                            IpAddr::V4(_) => Either::Left(ip),
+                            IpAddr::V6(_) => Either::Right(ip),
+                        });
+                    Ok(match self.config.addr_family {
+                        IpAddrFamily::Ipv4Only => {
+                            if ipv4.is_empty() {
+                                vec![]
+                            } else {
+                                ipv4
+                            }
+                        }
+                        IpAddrFamily::Ipv6Only => {
+                            if ipv6.is_empty() {
+                                vec![]
+                            } else {
+                                ipv6
+                            }
+                        }
+                        IpAddrFamily::Ipv6thenIpv4 => {
+                            if ipv6.is_empty() {
+                                ipv4
+                            } else {
+                                ipv6
+                            }
+                        }
+                        IpAddrFamily::Ipv4thenIpv6 => {
+                            if ipv4.is_empty() {
+                                ipv6
+                            } else {
+                                ipv4
+                            }
+                        }
                     })
-                    .collect::<Vec<_>>()),
+                }
             }
             .map(ResolvedIpAddrs)
         }
diff --git a/src/frontend/render/header.rs b/src/frontend/render/header.rs
index 680cd98a9..0ca075c90 100644
--- a/src/frontend/render/header.rs
+++ b/src/frontend/render/header.rs
@@ -6,6 +6,7 @@ use ratatui::style::{Modifier, Style};
 use ratatui::text::{Line, Span};
 use ratatui::widgets::{Block, BorderType, Borders, Paragraph};
 use ratatui::Frame;
+use std::net::IpAddr;
 use std::time::Duration;
 use trippy::dns::{ResolveMethod, Resolver};
 use trippy::tracing::{PortDirection, Protocol};
@@ -42,18 +43,18 @@ pub fn render(f: &mut Frame<'_>, app: &TuiApp, rect: Rect) {
     let protocol = match app.tracer_config().protocol {
         Protocol::Icmp => format!(
             "icmp({}, {})",
-            app.tracer_config().addr_family,
+            render_target_family(app.tracer_config().target_addr),
             app.tracer_config().privilege_mode
         ),
         Protocol::Udp => format!(
             "udp({}, {}, {})",
-            app.tracer_config().addr_family,
+            render_target_family(app.tracer_config().target_addr),
             app.tracer_config().multipath_strategy,
             app.tracer_config().privilege_mode
         ),
         Protocol::Tcp => format!(
             "tcp({}, {})",
-            app.tracer_config().addr_family,
+            render_target_family(app.tracer_config().target_addr),
             app.tracer_config().privilege_mode
         ),
     };
@@ -120,6 +121,13 @@ pub fn render(f: &mut Frame<'_>, app: &TuiApp, rect: Rect) {
     f.render_widget(left, rect);
 }
 
+fn render_target_family(target: IpAddr) -> &'static str {
+    match target {
+        IpAddr::V4(_) => "v4",
+        IpAddr::V6(_) => "v6",
+    }
+}
+
 /// Render the source address of the trace.
 fn render_source(app: &TuiApp) -> String {
     let src_hostname = app
diff --git a/src/main.rs b/src/main.rs
index b00fb9892..295b74311 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -39,8 +39,7 @@ use tracing_subscriber::layer::SubscriberExt;
 use tracing_subscriber::util::SubscriberInitExt;
 use trippy::dns::{DnsResolver, Resolver};
 use trippy::tracing::{
-    AddrFamily, ChannelConfig, Config, IcmpExtensionParseMode, MultipathStrategy, PortDirection,
-    Protocol,
+    ChannelConfig, Config, IcmpExtensionParseMode, MultipathStrategy, PortDirection, Protocol,
 };
 use trippy::tracing::{PrivilegeMode, SourceAddr};
 
@@ -72,9 +71,9 @@ fn run_trippy(cfg: &TrippyConfig, platform: &Platform) -> anyhow::Result<()> {
     let addrs = resolve_targets(cfg, &resolver)?;
     if addrs.is_empty() {
         return Err(anyhow!(
-            "failed to find any valid IP{} addresses for {}",
+            "failed to find any valid IP addresses for {} for address family {}",
+            cfg.targets.join(", "),
             cfg.addr_family,
-            cfg.targets.join(", ")
         ));
     }
     let traces = start_tracers(cfg, &addrs, platform.pid)?;
@@ -104,16 +103,11 @@ fn print_shell_completions(shell: Shell) -> anyhow::Result<()> {
 
 /// Start the DNS resolver.
 fn start_dns_resolver(cfg: &TrippyConfig) -> anyhow::Result<DnsResolver> {
-    Ok(match cfg.addr_family {
-        AddrFamily::Ipv4 => DnsResolver::start(trippy::dns::Config::new_ipv4(
-            cfg.dns_resolve_method,
-            cfg.dns_timeout,
-        ))?,
-        AddrFamily::Ipv6 => DnsResolver::start(trippy::dns::Config::new_ipv6(
-            cfg.dns_resolve_method,
-            cfg.dns_timeout,
-        ))?,
-    })
+    Ok(DnsResolver::start(trippy::dns::Config::new(
+        cfg.dns_resolve_method,
+        cfg.addr_family,
+        cfg.dns_timeout,
+    ))?)
 }
 
 fn create_geoip_lookup(cfg: &TrippyConfig) -> anyhow::Result<GeoIpLookup> {
@@ -316,7 +310,6 @@ fn make_trace_info(
         args.multipath_strategy,
         args.port_direction,
         args.protocol,
-        args.addr_family,
         args.first_ttl,
         args.max_ttl,
         args.grace_duration,
@@ -372,7 +365,6 @@ pub struct TraceInfo {
     pub multipath_strategy: MultipathStrategy,
     pub port_direction: PortDirection,
     pub protocol: Protocol,
-    pub addr_family: AddrFamily,
     pub first_ttl: u8,
     pub max_ttl: u8,
     pub grace_duration: Duration,
@@ -401,7 +393,6 @@ impl TraceInfo {
         multipath_strategy: MultipathStrategy,
         port_direction: PortDirection,
         protocol: Protocol,
-        addr_family: AddrFamily,
         first_ttl: u8,
         max_ttl: u8,
         grace_duration: Duration,
@@ -426,7 +417,6 @@ impl TraceInfo {
             multipath_strategy,
             port_direction,
             protocol,
-            addr_family,
             first_ttl,
             max_ttl,
             grace_duration,
diff --git a/src/tracing.rs b/src/tracing.rs
index bfcdff28c..3ef6b7451 100644
--- a/src/tracing.rs
+++ b/src/tracing.rs
@@ -12,8 +12,8 @@ pub mod packet;
 
 pub use builder::Builder;
 pub use config::{
-    defaults, AddrFamily, ChannelConfig, ChannelConfigBuilder, Config, ConfigBuilder,
-    IcmpExtensionParseMode, MultipathStrategy, PortDirection, PrivilegeMode, Protocol,
+    defaults, ChannelConfig, ChannelConfigBuilder, Config, ConfigBuilder, IcmpExtensionParseMode,
+    MultipathStrategy, PortDirection, PrivilegeMode, Protocol,
 };
 pub use net::channel::TracerChannel;
 pub use net::source::SourceAddr;
diff --git a/src/tracing/config.rs b/src/tracing/config.rs
index 1c4ebe67f..8c69e74c0 100644
--- a/src/tracing/config.rs
+++ b/src/tracing/config.rs
@@ -11,7 +11,7 @@ use std::time::Duration;
 
 pub mod defaults {
     use crate::tracing::config::IcmpExtensionParseMode;
-    use crate::tracing::{AddrFamily, MultipathStrategy, PrivilegeMode, Protocol};
+    use crate::tracing::{MultipathStrategy, PrivilegeMode, Protocol};
     use std::time::Duration;
 
     /// The default value for `unprivileged`.
@@ -20,9 +20,6 @@ pub mod defaults {
     /// The default value for `protocol`.
     pub const DEFAULT_STRATEGY_PROTOCOL: Protocol = Protocol::Icmp;
 
-    /// The default value for `addr-family`.
-    pub const DEFAULT_ADDRESS_FAMILY: AddrFamily = AddrFamily::Ipv4;
-
     /// The default value for `multipath-strategy`.
     pub const DEFAULT_STRATEGY_MULTIPATH: MultipathStrategy = MultipathStrategy::Classic;
 
@@ -123,24 +120,6 @@ impl Display for IcmpExtensionParseMode {
     }
 }
 
-/// The address family.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub enum AddrFamily {
-    /// Internet Protocol V4
-    Ipv4,
-    /// Internet Protocol V6
-    Ipv6,
-}
-
-impl Display for AddrFamily {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Self::Ipv4 => write!(f, "v4"),
-            Self::Ipv6 => write!(f, "v6"),
-        }
-    }
-}
-
 /// The tracing protocol.
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub enum Protocol {
diff --git a/trippy-config-sample.toml b/trippy-config-sample.toml
index 3f5abbd9c..1bdfd4753 100644
--- a/trippy-config-sample.toml
+++ b/trippy-config-sample.toml
@@ -78,9 +78,11 @@ protocol = "icmp"
 # The address family.
 #
 # Allowed values are:
-#   ipv4 [default]
-#   ipv6
-addr-family = "ipv4"
+#   ipv4            - Lookup Ipv4 only
+#   ipv6            - Lookup Ipv6 only
+#   ipv6-then-ipv4  - Lookup Ipv6 with a fallback to Ipv4
+#   ipv4-then-ipv6  - Lookup Ipv4 with a fallback to Ipv6 [default]
+addr-family = "ipv4-then-ipv6"
 
 # The target port (TCP & UDP only) [default: 80]
 #


Test patch:
diff --git a/test_resources/completions_bash.txt b/test_resources/completions_bash.txt
index 29f21927f..7ca4b7cc7 100644
--- a/test_resources/completions_bash.txt
+++ b/test_resources/completions_bash.txt
@@ -19,7 +19,7 @@ _trip() {
 
     case "${cmd}" in
         trip)
-            opts="-c -m -u -p -4 -6 -P -S -A -I -i -T -g -R -U -f -t -Q -e -r -y -z -a -M -s -C -G -v -h -V --config-file --mode --unprivileged --protocol --udp --tcp --icmp --ipv4 --ipv6 --target-port --source-port --source-address --interface --min-round-duration --max-round-duration --grace-duration --initial-sequence --multipath-strategy --max-inflight --first-ttl --max-ttl --packet-size --payload-pattern --tos --icmp-extensions --read-timeout --dns-resolve-method --dns-resolve-all --dns-timeout --dns-lookup-as-info --tui-address-mode --tui-as-mode --tui-custom-columns --tui-icmp-extension-mode --tui-geoip-mode --tui-max-addrs --tui-max-samples --tui-max-flows --tui-preserve-screen --tui-refresh-rate --tui-privacy-max-ttl --tui-theme-colors --print-tui-theme-items --tui-key-bindings --print-tui-binding-commands --report-cycles --geoip-mmdb-file --generate --print-config-template --log-format --log-filter --log-span-events --verbose --help --version [TARGETS]..."
+            opts="-c -m -u -p -F -4 -6 -P -S -A -I -i -T -g -R -U -f -t -Q -e -r -y -z -a -M -s -C -G -v -h -V --config-file --mode --unprivileged --protocol --udp --tcp --icmp --addr-family --ipv4 --ipv6 --target-port --source-port --source-address --interface --min-round-duration --max-round-duration --grace-duration --initial-sequence --multipath-strategy --max-inflight --first-ttl --max-ttl --packet-size --payload-pattern --tos --icmp-extensions --read-timeout --dns-resolve-method --dns-resolve-all --dns-timeout --dns-lookup-as-info --tui-address-mode --tui-as-mode --tui-custom-columns --tui-icmp-extension-mode --tui-geoip-mode --tui-max-addrs --tui-max-samples --tui-max-flows --tui-preserve-screen --tui-refresh-rate --tui-privacy-max-ttl --tui-theme-colors --print-tui-theme-items --tui-key-bindings --print-tui-binding-commands --report-cycles --geoip-mmdb-file --generate --print-config-template --log-format --log-filter --log-span-events --verbose --help --version [TARGETS]..."
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
                 COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
@@ -49,6 +49,14 @@ _trip() {
                     COMPREPLY=($(compgen -W "icmp udp tcp" -- "${cur}"))
                     return 0
                     ;;
+                --addr-family)
+                    COMPREPLY=($(compgen -W "ipv4 ipv6 ipv6-then-ipv4 ipv4-then-ipv6" -- "${cur}"))
+                    return 0
+                    ;;
+                -F)
+                    COMPREPLY=($(compgen -W "ipv4 ipv6 ipv6-then-ipv4 ipv4-then-ipv6" -- "${cur}"))
+                    return 0
+                    ;;
                 --target-port)
                     COMPREPLY=($(compgen -f "${cur}"))
                     return 0
@@ -280,3 +288,4 @@ if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERS
 else
     complete -F _trip -o bashdefault -o default trip
 fi
+
diff --git a/test_resources/completions_elvish.txt b/test_resources/completions_elvish.txt
index e9aa5823a..c4c307522 100644
--- a/test_resources/completions_elvish.txt
+++ b/test_resources/completions_elvish.txt
@@ -24,6 +24,8 @@ set edit:completion:arg-completer[trip] = {|@words|
             cand --mode 'Output mode [default: tui]'
             cand -p 'Tracing protocol [default: icmp]'
             cand --protocol 'Tracing protocol [default: icmp]'
+            cand -F 'The address family [default: Ipv4thenIpv6]'
+            cand --addr-family 'The address family [default: Ipv4thenIpv6]'
             cand -P 'The target port (TCP & UDP only) [default: 80]'
             cand --target-port 'The target port (TCP & UDP only) [default: 80]'
             cand -S 'The source port (TCP & UDP only) [default: auto]'
@@ -107,3 +109,4 @@ set edit:completion:arg-completer[trip] = {|@words|
     ]
     $completions[$command]
 }
+
diff --git a/test_resources/completions_fish.txt b/test_resources/completions_fish.txt
index d8da08332..aafd8220d 100644
--- a/test_resources/completions_fish.txt
+++ b/test_resources/completions_fish.txt
@@ -1,6 +1,7 @@
 complete -c trip -s c -l config-file -d 'Config file' -r -F
-complete -c trip -s m -l mode -d 'Output mode [default: tui]' -r -f -a "{tui    'Display interactive TUI',stream        'Display a continuous stream of tracing data',pretty    'Generate an pretty text table report for N cycles',markdown    'Generate a markdown text table report for N cycles',csv        'Generate a CSV report for N cycles',json       'Generate a JSON report for N cycles',dot       'Generate a Graphviz DOT file for N cycles',flows       'Display all flows',silent      'Do not generate any tracing output for N cycles'}"
-complete -c trip -s p -l protocol -d 'Tracing protocol [default: icmp]' -r -f -a "{icmp 'Internet Control Message Protocol',udp 'User Datagram Protocol',tcp    'Transmission Control Protocol'}"
+complete -c trip -s m -l mode -d 'Output mode [default: tui]' -r -f -a "{tui	'Display interactive TUI',stream	'Display a continuous stream of tracing data',pretty	'Generate an pretty text table report for N cycles',markdown	'Generate a markdown text table report for N cycles',csv	'Generate a CSV report for N cycles',json	'Generate a JSON report for N cycles',dot	'Generate a Graphviz DOT file for N cycles',flows	'Display all flows',silent	'Do not generate any tracing output for N cycles'}"
+complete -c trip -s p -l protocol -d 'Tracing protocol [default: icmp]' -r -f -a "{icmp	'Internet Control Message Protocol',udp	'User Datagram Protocol',tcp	'Transmission Control Protocol'}"
+complete -c trip -s F -l addr-family -d 'The address family [default: Ipv4thenIpv6]' -r -f -a "{ipv4	'Ipv4 only',ipv6	'Ipv6 only',ipv6-then-ipv4	'Ipv6 with a fallback to Ipv4',ipv4-then-ipv6	'Ipv4 with a fallback to Ipv6'}"
 complete -c trip -s P -l target-port -d 'The target port (TCP & UDP only) [default: 80]' -r
 complete -c trip -s S -l source-port -d 'The source port (TCP & UDP only) [default: auto]' -r
 complete -c trip -s A -l source-address -d 'The source IP address [default: auto]' -r
@@ -9,7 +10,7 @@ complete -c trip -s i -l min-round-duration -d 'The minimum duration of every ro
 complete -c trip -s T -l max-round-duration -d 'The maximum duration of every round [default: 1s]' -r
 complete -c trip -s g -l grace-duration -d 'The period of time to wait for additional ICMP responses after the target has responded [default: 100ms]' -r
 complete -c trip -l initial-sequence -d 'The initial sequence number [default: 33000]' -r
-complete -c trip -s R -l multipath-strategy -d 'The Equal-cost Multi-Path routing strategy (UDP only) [default: classic]' -r -f -a "{classic    'The src or dest port is used to store the sequence number',paris       'The UDP `checksum` field is used to store the sequence number',dublin  'The IP `identifier` field is used to store the sequence number'}"
+complete -c trip -s R -l multipath-strategy -d 'The Equal-cost Multi-Path routing strategy (UDP only) [default: classic]' -r -f -a "{classic	'The src or dest port is used to store the sequence number',paris	'The UDP `checksum` field is used to store the sequence number',dublin	'The IP `identifier` field is used to store the sequence number'}"
 complete -c trip -s U -l max-inflight -d 'The maximum number of in-flight ICMP echo requests [default: 24]' -r
 complete -c trip -s f -l first-ttl -d 'The TTL to start from [default: 1]' -r
 complete -c trip -s t -l max-ttl -d 'The maximum number of TTL hops [default: 64]' -r
@@ -17,13 +18,13 @@ complete -c trip -l packet-size -d 'The size of IP packet to send (IP header + I
 complete -c trip -l payload-pattern -d 'The repeating pattern in the payload of the ICMP packet [default: 0]' -r
 complete -c trip -s Q -l tos -d 'The TOS (i.e. DSCP+ECN) IP header value (TCP and UDP only) [default: 0]' -r
 complete -c trip -l read-timeout -d 'The socket read timeout [default: 10ms]' -r
-complete -c trip -s r -l dns-resolve-method -d 'How to perform DNS queries [default: system]' -r -f -a "{system 'Resolve using the OS resolver',resolv  'Resolve using the `/etc/resolv.conf` DNS configuration',google 'Resolve using the Google `8.8.8.8` DNS service',cloudflare     'Resolve using the Cloudflare `1.1.1.1` DNS service'}"
+complete -c trip -s r -l dns-resolve-method -d 'How to perform DNS queries [default: system]' -r -f -a "{system	'Resolve using the OS resolver',resolv	'Resolve using the `/etc/resolv.conf` DNS configuration',google	'Resolve using the Google `8.8.8.8` DNS service',cloudflare	'Resolve using the Cloudflare `1.1.1.1` DNS service'}"
 complete -c trip -l dns-timeout -d 'The maximum time to wait to perform DNS queries [default: 5s]' -r
-complete -c trip -s a -l tui-address-mode -d 'How to render addresses [default: host]' -r -f -a "{ip    'Show IP address only',host     'Show reverse-lookup DNS hostname only',both    'Show both IP address and reverse-lookup DNS hostname'}"
-complete -c trip -l tui-as-mode -d 'How to render AS information [default: asn]' -r -f -a "{asn 'Show the ASN',prefix   'Display the AS prefix',country-code    'Display the country code',registry     'Display the registry name',allocated   'Display the allocated date',name       'Display the AS name'}"
+complete -c trip -s a -l tui-address-mode -d 'How to render addresses [default: host]' -r -f -a "{ip	'Show IP address only',host	'Show reverse-lookup DNS hostname only',both	'Show both IP address and reverse-lookup DNS hostname'}"
+complete -c trip -l tui-as-mode -d 'How to render AS information [default: asn]' -r -f -a "{asn	'Show the ASN',prefix	'Display the AS prefix',country-code	'Display the country code',registry	'Display the registry name',allocated	'Display the allocated date',name	'Display the AS name'}"
 complete -c trip -l tui-custom-columns -d 'Custom columns to be displayed in the TUI hops table [default: HOLSRAVBWDT]' -r
-complete -c trip -l tui-icmp-extension-mode -d 'How to render ICMP extensions [default: off]' -r -f -a "{off    'Do not show `icmp` extensions',mpls    'Show MPLS label(s) only',full  'Show full `icmp` extension data for all known extensions',all  'Show full `icmp` extension data for all classes'}"
-complete -c trip -l tui-geoip-mode -d 'How to render GeoIp information [default: short]' -r -f -a "{off 'Do not display GeoIp data',short       'Show short format',long        'Show long format',location     'Show latitude and Longitude format'}"
+complete -c trip -l tui-icmp-extension-mode -d 'How to render ICMP extensions [default: off]' -r -f -a "{off	'Do not show `icmp` extensions',mpls	'Show MPLS label(s) only',full	'Show full `icmp` extension data for all known extensions',all	'Show full `icmp` extension data for all classes'}"
+complete -c trip -l tui-geoip-mode -d 'How to render GeoIp information [default: short]' -r -f -a "{off	'Do not display GeoIp data',short	'Show short format',long	'Show long format',location	'Show latitude and Longitude format'}"
 complete -c trip -s M -l tui-max-addrs -d 'The maximum number of addresses to show per hop [default: auto]' -r
 complete -c trip -s s -l tui-max-samples -d 'The maximum number of samples to record per hop [default: 256]' -r
 complete -c trip -l tui-max-flows -d 'The maximum number of flows to show [default: 64]' -r
@@ -33,10 +34,10 @@ complete -c trip -l tui-theme-colors -d 'The TUI theme colors [item=color,item=c
 complete -c trip -l tui-key-bindings -d 'The TUI key bindings [command=key,command=key,..]' -r
 complete -c trip -s C -l report-cycles -d 'The number of report cycles to run [default: 10]' -r
 complete -c trip -s G -l geoip-mmdb-file -d 'The MaxMind City GeoLite2 mmdb file' -r -F
-complete -c trip -l generate -d 'Generate shell completion' -r -f -a "{bash     '',elvish       '',fish '',powershell   '',zsh  ''}"
-complete -c trip -l log-format -d 'The debug log format [default: pretty]' -r -f -a "{compact   'Display log data in a compact format',pretty   'Display log data in a pretty format',json      'Display log data in a json format',chrome      'Display log data in Chrome trace format'}"
+complete -c trip -l generate -d 'Generate shell completion' -r -f -a "{bash	'',elvish	'',fish	'',powershell	'',zsh	''}"
+complete -c trip -l log-format -d 'The debug log format [default: pretty]' -r -f -a "{compact	'Display log data in a compact format',pretty	'Display log data in a pretty format',json	'Display log data in a json format',chrome	'Display log data in Chrome trace format'}"
 complete -c trip -l log-filter -d 'The debug log filter [default: trippy=debug]' -r
-complete -c trip -l log-span-events -d 'The debug log format [default: off]' -r -f -a "{off     'Do not display event spans',active     'Display enter and exit event spans',full       'Display all event spans'}"
+complete -c trip -l log-span-events -d 'The debug log format [default: off]' -r -f -a "{off	'Do not display event spans',active	'Display enter and exit event spans',full	'Display all event spans'}"
 complete -c trip -s u -l unprivileged -d 'Trace without requiring elevated privileges on supported platforms [default: false]'
 complete -c trip -l udp -d 'Trace using the UDP protocol'
 complete -c trip -l tcp -d 'Trace using the TCP protocol'
@@ -52,4 +53,5 @@ complete -c trip -l print-tui-binding-commands -d 'Print all TUI commands that c
 complete -c trip -l print-config-template -d 'Print a template toml config file and exit'
 complete -c trip -s v -l verbose -d 'Enable verbose debug logging'
 complete -c trip -s h -l help -d 'Print help (see more with \'--help\')'
-complete -c trip -s V -l version -d 'Print version'
\ No newline at end of file
+complete -c trip -s V -l version -d 'Print version'
+
diff --git a/test_resources/completions_powershell.txt b/test_resources/completions_powershell.txt
index c5e659514..93157c045 100644
--- a/test_resources/completions_powershell.txt
+++ b/test_resources/completions_powershell.txt
@@ -27,6 +27,8 @@ Register-ArgumentCompleter -Native -CommandName 'trip' -ScriptBlock {
             [CompletionResult]::new('--mode', 'mode', [CompletionResultType]::ParameterName, 'Output mode [default: tui]')
             [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Tracing protocol [default: icmp]')
             [CompletionResult]::new('--protocol', 'protocol', [CompletionResultType]::ParameterName, 'Tracing protocol [default: icmp]')
+            [CompletionResult]::new('-F', 'F ', [CompletionResultType]::ParameterName, 'The address family [default: Ipv4thenIpv6]')
+            [CompletionResult]::new('--addr-family', 'addr-family', [CompletionResultType]::ParameterName, 'The address family [default: Ipv4thenIpv6]')
             [CompletionResult]::new('-P', 'P ', [CompletionResultType]::ParameterName, 'The target port (TCP & UDP only) [default: 80]')
             [CompletionResult]::new('--target-port', 'target-port', [CompletionResultType]::ParameterName, 'The target port (TCP & UDP only) [default: 80]')
             [CompletionResult]::new('-S', 'S ', [CompletionResultType]::ParameterName, 'The source port (TCP & UDP only) [default: auto]')
@@ -113,3 +115,4 @@ Register-ArgumentCompleter -Native -CommandName 'trip' -ScriptBlock {
     $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
         Sort-Object -Property ListItemText
 }
+
diff --git a/test_resources/completions_zsh.txt b/test_resources/completions_zsh.txt
index 3b977a850..a87aae18e 100644
--- a/test_resources/completions_zsh.txt
+++ b/test_resources/completions_zsh.txt
@@ -41,6 +41,14 @@ tcp\:"Transmission Control Protocol"))' \
 '--protocol=[Tracing protocol \[default\: icmp\]]:PROTOCOL:((icmp\:"Internet Control Message Protocol"
 udp\:"User Datagram Protocol"
 tcp\:"Transmission Control Protocol"))' \
+'-F+[The address family \[default\: Ipv4thenIpv6\]]:ADDR_FAMILY:((ipv4\:"Ipv4 only"
+ipv6\:"Ipv6 only"
+ipv6-then-ipv4\:"Ipv6 with a fallback to Ipv4"
+ipv4-then-ipv6\:"Ipv4 with a fallback to Ipv6"))' \
+'--addr-family=[The address family \[default\: Ipv4thenIpv6\]]:ADDR_FAMILY:((ipv4\:"Ipv4 only"
+ipv6\:"Ipv6 only"
+ipv6-then-ipv4\:"Ipv6 with a fallback to Ipv4"
+ipv4-then-ipv6\:"Ipv4 with a fallback to Ipv6"))' \
 '-P+[The target port (TCP & UDP only) \[default\: 80\]]:TARGET_PORT: ' \
 '--target-port=[The target port (TCP & UDP only) \[default\: 80\]]:TARGET_PORT: ' \
 '-S+[The source port (TCP & UDP only) \[default\: auto\]]:SOURCE_PORT: ' \
@@ -130,10 +138,10 @@ full\:"Display all event spans"))' \
 '(-p --protocol --tcp --icmp)--udp[Trace using the UDP protocol]' \
 '(-p --protocol --udp --icmp)--tcp[Trace using the TCP protocol]' \
 '(-p --protocol --udp --tcp)--icmp[Trace using the ICMP protocol]' \
-'(-6 --ipv6)-4[Use IPv4 only]' \
-'(-6 --ipv6)--ipv4[Use IPv4 only]' \
-'(-4 --ipv4)-6[Use IPv6 only]' \
-'(-4 --ipv4)--ipv6[Use IPv6 only]' \
+'(-6 --ipv6 -F --addr-family)-4[Use IPv4 only]' \
+'(-6 --ipv6 -F --addr-family)--ipv4[Use IPv4 only]' \
+'(-4 --ipv4 -F --addr-family)-6[Use IPv6 only]' \
+'(-4 --ipv4 -F --addr-family)--ipv6[Use IPv6 only]' \
 '-e[Parse ICMP extensions]' \
 '--icmp-extensions[Parse ICMP extensions]' \
 '-y[Trace to all IPs resolved from DNS lookup \[default\: false\]]' \
@@ -165,3 +173,4 @@ if [ "$funcstack[1]" = "_trip" ]; then
 else
     compdef _trip trip
 fi
+
diff --git a/test_resources/usage_long.txt b/test_resources/usage_long.txt
index d5cad83db..13a541e1f 100644
--- a/test_resources/usage_long.txt
+++ b/test_resources/usage_long.txt
@@ -44,6 +44,15 @@ Options:
       --icmp
           Trace using the ICMP protocol
 
+  -F, --addr-family <ADDR_FAMILY>
+          The address family [default: Ipv4thenIpv6]
+
+          Possible values:
+          - ipv4:           Ipv4 only
+          - ipv6:           Ipv6 only
+          - ipv6-then-ipv4: Ipv6 with a fallback to Ipv4
+          - ipv4-then-ipv6: Ipv4 with a fallback to Ipv6
+
   -4, --ipv4
           Use IPv4 only
 
diff --git a/test_resources/usage_short.txt b/test_resources/usage_short.txt
index f88449419..b2299d122 100644
--- a/test_resources/usage_short.txt
+++ b/test_resources/usage_short.txt
@@ -7,12 +7,15 @@ Arguments:
 
 Options:
   -c, --config-file <CONFIG_FILE>                          Config file
-  -m, --mode <MODE>                                        Output mode [default: tui] [possible values: tui, stream, pretty, markdown, csv, json, dot, flows, silent]
+  -m, --mode <MODE>                                        Output mode [default: tui] [possible values: tui, stream, pretty, markdown, csv, json, dot,
+                                                           flows, silent]
   -u, --unprivileged                                       Trace without requiring elevated privileges on supported platforms [default: false]
   -p, --protocol <PROTOCOL>                                Tracing protocol [default: icmp] [possible values: icmp, udp, tcp]
       --udp                                                Trace using the UDP protocol
       --tcp                                                Trace using the TCP protocol
       --icmp                                               Trace using the ICMP protocol
+  -F, --addr-family <ADDR_FAMILY>                          The address family [default: Ipv4thenIpv6] [possible values: ipv4, ipv6, ipv6-then-ipv4,
+                                                           ipv4-then-ipv6]
   -4, --ipv4                                               Use IPv4 only
   -6, --ipv6                                               Use IPv6 only
   -P, --target-port <TARGET_PORT>                          The target port (TCP & UDP only) [default: 80]
@@ -21,9 +24,11 @@ Options:
   -I, --interface <INTERFACE>                              The network interface [default: auto]
   -i, --min-round-duration <MIN_ROUND_DURATION>            The minimum duration of every round [default: 1s]
   -T, --max-round-duration <MAX_ROUND_DURATION>            The maximum duration of every round [default: 1s]
-  -g, --grace-duration <GRACE_DURATION>                    The period of time to wait for additional ICMP responses after the target has responded [default: 100ms]
+  -g, --grace-duration <GRACE_DURATION>                    The period of time to wait for additional ICMP responses after the target has responded [default:
+                                                           100ms]
       --initial-sequence <INITIAL_SEQUENCE>                The initial sequence number [default: 33000]
-  -R, --multipath-strategy <MULTIPATH_STRATEGY>            The Equal-cost Multi-Path routing strategy (UDP only) [default: classic] [possible values: classic, paris, dublin]
+  -R, --multipath-strategy <MULTIPATH_STRATEGY>            The Equal-cost Multi-Path routing strategy (UDP only) [default: classic] [possible values:
+                                                           classic, paris, dublin]
   -U, --max-inflight <MAX_INFLIGHT>                        The maximum number of in-flight ICMP echo requests [default: 24]
   -f, --first-ttl <FIRST_TTL>                              The TTL to start from [default: 1]
   -t, --max-ttl <MAX_TTL>                                  The maximum number of TTL hops [default: 64]
@@ -32,12 +37,14 @@ Options:
   -Q, --tos <TOS>                                          The TOS (i.e. DSCP+ECN) IP header value (TCP and UDP only) [default: 0]
   -e, --icmp-extensions                                    Parse ICMP extensions
       --read-timeout <READ_TIMEOUT>                        The socket read timeout [default: 10ms]
-  -r, --dns-resolve-method <DNS_RESOLVE_METHOD>            How to perform DNS queries [default: system] [possible values: system, resolv, google, cloudflare]
+  -r, --dns-resolve-method <DNS_RESOLVE_METHOD>            How to perform DNS queries [default: system] [possible values: system, resolv, google,
+                                                           cloudflare]
   -y, --dns-resolve-all                                    Trace to all IPs resolved from DNS lookup [default: false]
       --dns-timeout <DNS_TIMEOUT>                          The maximum time to wait to perform DNS queries [default: 5s]
   -z, --dns-lookup-as-info                                 Lookup autonomous system (AS) information during DNS queries [default: false]
   -a, --tui-address-mode <TUI_ADDRESS_MODE>                How to render addresses [default: host] [possible values: ip, host, both]
-      --tui-as-mode <TUI_AS_MODE>                          How to render AS information [default: asn] [possible values: asn, prefix, country-code, registry, allocated, name]
+      --tui-as-mode <TUI_AS_MODE>                          How to render AS information [default: asn] [possible values: asn, prefix, country-code,
+                                                           registry, allocated, name]
       --tui-custom-columns <TUI_CUSTOM_COLUMNS>            Custom columns to be displayed in the TUI hops table [default: HOLSRAVBWDT]
       --tui-icmp-extension-mode <TUI_ICMP_EXTENSION_MODE>  How to render ICMP extensions [default: off] [possible values: off, mpls, full, all]
       --tui-geoip-mode <TUI_GEOIP_MODE>                    How to render GeoIp information [default: short] [possible values: off, short, long, location]