Repository: fujiapple852/trippy (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.

Pull request:
feat: add support `dns-resolve-family` (#864)
Closes #864 

Patch:
diff --git a/src/config.rs b/src/config.rs
index eb7ad87..aed1c55 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 2e58e96..6299c06 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 32a2211..babec36 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 4819ac9..5b457b7 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 4aa29d5..8c59773 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 6d6b569..366c365 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 680cd98..0ca075c 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 b00fb98..295b743 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 bfcdff2..3ef6b74 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 1c4ebe6..8c69e74 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 3f5abbd..1bdfd47 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 29f2192..7ca4b7c 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 e9aa582..c4c3075 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 d8da083..aafd822 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 c5e6595..93157c0 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 3b977a8..a87aae1 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 d5cad83..13a541e 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 f884494..b2299d1 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]