Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/compliance-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
compliance-tests:
Expand Down
96 changes: 64 additions & 32 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ 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"] }
dns-lookup = "3"
libc = "0.2"
nix = { version = "0.30", features = ["hostname", "net"] }
thiserror = "2.0"
Expand Down
21 changes: 16 additions & 5 deletions compliance-tests/tests/arp_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -160,8 +173,6 @@ test_invalid_hostname() {
else
fail "arp::test_invalid_hostname"
fi

cleanup
}

test_device_filter() {
Expand Down
35 changes: 29 additions & 6 deletions src/arp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -302,6 +303,13 @@ fn display_linux_style(entries: &[ArpEntry], numeric: bool) -> Result<usize> {
&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 };
Expand All @@ -314,18 +322,18 @@ fn display_linux_style(entries: &[ArpEntry], numeric: bool) -> Result<usize> {
if (entry.flags & libc::ATF_PUBL) != 0 {
println!(
"{:<23} {:<8}{:<20}{:<6}{:<15} {}",
hostname, "*", "<from_interface>", flags_str, mask, entry.device
truncated_hostname, "*", "<from_interface>", 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
);
}
}
Expand Down Expand Up @@ -421,9 +429,10 @@ fn resolve_or_passthrough(host: &str, verbose: bool) -> Result<String> {
Ok(addr.ip().to_string())
}

/// Reverse DNS lookup - not implemented yet
fn reverse_lookup(_ip: &str) -> Option<String> {
None
/// Reverse DNS lookup
fn reverse_lookup(ip: &str) -> Option<String> {
let addr: IpAddr = ip.parse().ok()?;
lookup_addr(&addr).ok()
}

fn arp_del(_args: &Args) -> Result<()> {
Expand Down Expand Up @@ -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());
}
}