From d078b1a160599eca4a3d7b6ee89fc2ace89e66fe Mon Sep 17 00:00:00 2001 From: Nadzeya Hutsko Date: Sat, 27 Dec 2025 21:40:03 +0100 Subject: [PATCH 1/3] feat(arp): implement reverse DNS lookup --- Cargo.lock | 96 ++++++++++++++++++++---------- Cargo.toml | 1 + compliance-tests/tests/arp_test.sh | 21 +++++-- src/arp/mod.rs | 35 +++++++++-- 4 files changed, 110 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7700864..2769206 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,22 +34,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -60,15 +60,15 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -78,9 +78,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.48" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "colorchoice" @@ -122,6 +122,18 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "dns-lookup" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e39034cee21a2f5bbb66ba0e3689819c4bb5d00382a282006e802a7ffa6c41d" +dependencies = [ + "cfg-if", + "libc", + "socket2", + "windows-sys 0.60.2", +] + [[package]] name = "heck" version = "0.5.0" @@ -130,15 +142,15 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "memoffset" @@ -154,6 +166,7 @@ name = "net-tools-rs" version = "0.1.1" dependencies = [ "clap", + "dns-lookup", "libc", "nix", "thiserror", @@ -174,28 +187,38 @@ dependencies = [ [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + [[package]] name = "strsim" version = "0.11.1" @@ -204,9 +227,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -235,9 +258,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "utf8parse" @@ -260,6 +283,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.53.5" diff --git a/Cargo.toml b/Cargo.toml index b2831a9..70d6195 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ categories = ["command-line-interface"] [dependencies] clap = { version = "4.5", features = ["derive"] } +dns-lookup = "3" libc = "0.2" nix = { version = "0.30", features = ["hostname", "net"] } thiserror = "2.0" diff --git a/compliance-tests/tests/arp_test.sh b/compliance-tests/tests/arp_test.sh index 6835bc4..16b4cc0 100755 --- a/compliance-tests/tests/arp_test.sh +++ b/compliance-tests/tests/arp_test.sh @@ -46,9 +46,19 @@ cleanup() { ip link delete veth2 2>/dev/null || true ip link delete veth3 2>/dev/null || true ip netns delete arp_test 2>/dev/null || true + + sed -i '/test-short-name/d' /etc/hosts 2>/dev/null || true + sed -i '/test-very-long-hostname-that-exceeds-twentythree-characters/d' /etc/hosts 2>/dev/null || true + + # Restore DNS configuration + echo "nameserver 8.8.8.8" > /etc/resolv.conf } setup_test_arp_entries() { + # Configure DNS to prevent external network queries during reverse lookup + # This prevents the race condition where DNS triggers gateway ARP + echo "nameserver 127.0.0.1" > /etc/resolv.conf + ip link add veth0 type veth peer name veth1 ip addr add 192.168.100.1/24 dev veth0 ip addr add 192.168.100.2/24 dev veth1 @@ -67,11 +77,15 @@ setup_test_arp_entries() { ping -c 1 -W 1 192.168.101.2 >/dev/null 2>&1 || true # Add a static permanent entry to test ATF_PERM flag - # Use a dummy MAC address for testing arp -s 192.168.100.10 aa:bb:cc:dd:ee:01 -i veth0 >/dev/null 2>&1 || true - arp -s 192.168.101.10 aa:bb:cc:dd:ee:02 -i veth2 >/dev/null 2>&1 || true + # Add temporary /etc/hosts entries for reverse lookup testing + echo "192.168.100.50 test-short-name" >> /etc/hosts + echo "192.168.100.51 test-very-long-hostname-that-exceeds-twentythree-characters.example.com" >> /etc/hosts + arp -s 192.168.100.50 bb:cc:dd:ee:ff:01 -i veth0 >/dev/null 2>&1 || true + arp -s 192.168.100.51 bb:cc:dd:ee:ff:02 -i veth0 >/dev/null 2>&1 || true + # Wait for ARP entries to stabilize sleep 0.5 } @@ -145,7 +159,6 @@ test_bsd_style_flag() { test_invalid_hostname() { echo -n "test arp::test_invalid_hostname ... " cleanup - setup_test_arp_entries set +e $ORIGINAL_ARP invalid.hostname.that.does.not.exist.12345 >/tmp/original_invalid 2>&1 @@ -160,8 +173,6 @@ test_invalid_hostname() { else fail "arp::test_invalid_hostname" fi - - cleanup } test_device_filter() { diff --git a/src/arp/mod.rs b/src/arp/mod.rs index 02b471b..e99ee26 100644 --- a/src/arp/mod.rs +++ b/src/arp/mod.rs @@ -4,6 +4,7 @@ mod hwtype; use crate::{NetToolsError, RELEASE, Result}; use clap::Parser; +use dns_lookup::lookup_addr; use hwtype::{hwtype_num_to_name, hwtype_to_num}; use std::fs::File; use std::io::{BufRead, BufReader}; @@ -302,6 +303,13 @@ fn display_linux_style(entries: &[ArpEntry], numeric: bool) -> Result { &resolved_hostname }; + // Truncate hostname to 23 characters max to match original C implementation + let truncated_hostname = if hostname.len() > 23 { + &hostname[..23] + } else { + hostname + }; + let hw_name = hwtype_num_to_name(entry.hw_type); let flags_str = format_flags(entry.flags); let mask = if entry.mask == "*" { "" } else { &entry.mask }; @@ -314,18 +322,18 @@ fn display_linux_style(entries: &[ArpEntry], numeric: bool) -> Result { if (entry.flags & libc::ATF_PUBL) != 0 { println!( "{:<23} {:<8}{:<20}{:<6}{:<15} {}", - hostname, "*", "", flags_str, mask, entry.device + truncated_hostname, "*", "", flags_str, mask, entry.device ); } else { println!( "{:<23} {:<8}{:<20}{:<6}{:<15} {}", - hostname, "", "(incomplete)", flags_str, mask, entry.device + truncated_hostname, "", "(incomplete)", flags_str, mask, entry.device ); } } else { println!( "{:<23} {:<8}{:<20}{:<6}{:<15} {}", - hostname, hw_name, entry.mac, flags_str, mask, entry.device + truncated_hostname, hw_name, entry.mac, flags_str, mask, entry.device ); } } @@ -421,9 +429,10 @@ fn resolve_or_passthrough(host: &str, verbose: bool) -> Result { Ok(addr.ip().to_string()) } -/// Reverse DNS lookup - not implemented yet -fn reverse_lookup(_ip: &str) -> Option { - None +/// Reverse DNS lookup +fn reverse_lookup(ip: &str) -> Option { + let addr: IpAddr = ip.parse().ok()?; + lookup_addr(&addr).ok() } fn arp_del(_args: &Args) -> Result<()> { @@ -672,4 +681,18 @@ mod tests { let filtered = filter_entries(entries, Some("192.168.1.2"), None, None, false).unwrap(); assert_eq!(filtered.len(), 0); } + + #[test] + fn test_reverse_lookup_localhost() { + let result = reverse_lookup("127.0.0.1"); + assert!(result.is_some()); + let hostname = result.unwrap(); + assert!(hostname.contains("localhost") || hostname.contains("127.0.0.1")); + } + + #[test] + fn test_reverse_lookup_invalid_ip() { + let result = reverse_lookup("invalid"); + assert!(result.is_none()); + } } From 98e4846ed9962539babcdacd7c5d22954f590814 Mon Sep 17 00:00:00 2001 From: Nadzeya Hutsko Date: Sat, 27 Dec 2025 23:47:25 +0100 Subject: [PATCH 2/3] feat(ci): run compliance tests even if PR doesn't target main --- .github/workflows/compliance-tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/compliance-tests.yml b/.github/workflows/compliance-tests.yml index 09932b4..44cedc0 100644 --- a/.github/workflows/compliance-tests.yml +++ b/.github/workflows/compliance-tests.yml @@ -4,7 +4,6 @@ on: push: branches: [ main ] pull_request: - branches: [ main ] jobs: compliance-tests: From 18226cf125528000184369a87d8600c8338f6c53 Mon Sep 17 00:00:00 2001 From: Nadzeya Hutsko Date: Sun, 28 Dec 2025 10:45:50 +0100 Subject: [PATCH 3/3] feat: update keywords and categories in Cargo.toml --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 70d6195..42bb2c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,8 @@ authors = [ license = "GPL-2.0-or-later" description = "Rust implementation of net-tools (Linux networking base tools)" repository = "https://github.com/rust-swifties/net-tools-rs" -categories = ["command-line-interface"] +keywords = ["cli", "ifconfig", "hostname", "arp", "netstat"] +categories = ["command-line-utilities", "network-programming"] [dependencies] clap = { version = "4.5", features = ["derive"] }