Skip to content
Open
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
25 changes: 25 additions & 0 deletions .github/workflows/compliance-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Compliance Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
compliance-tests:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Build compliance test image
run: docker build -f compliance-tests/Dockerfile -t net-tools-compliance .

- name: Run compliance tests
run: |
docker run --rm \
--privileged \
--cap-add=NET_ADMIN \
--cap-add=SYS_ADMIN \
net-tools-compliance
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ thiserror = "2.0"
name = "hostname"
path = "bin/hostname.rs"

[[bin]]
name = "nameif"
path = "bin/nameif.rs"

[lints.rust]
unsafe_op_in_unsafe_fn = { level = "deny" }

Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# net-tools-rs

Rust implementation of [net-tools](https://sourceforge.net/projects/net-tools/).

The status of implemented commands is tracked in
https://github.com/rust-swifties/net-tools-rs/issues/3

## Testing

Compliance tests verify that net-tools-rs commands behave identically to the original
net-tools implementation. See [compliance-tests/README.md](compliance-tests/README.md) for
details on running the tests.
3 changes: 3 additions & 0 deletions bin/nameif.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
net_tools_rs::nameif_main();
}
15 changes: 15 additions & 0 deletions compliance-tests/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM rust:1-trixie

RUN apt-get update && apt-get install -y \
net-tools \
iproute2

WORKDIR /workspace
COPY . /workspace/

RUN cargo build --release

RUN chmod +x compliance-tests/tests/*.sh
WORKDIR /workspace/compliance-tests

CMD ["./run-tests.sh"]
52 changes: 52 additions & 0 deletions compliance-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Compliance Tests

This directory contains compliance tests that verify net-tools-rs commands behave identically to the original net-tools implementation.

## Structure

- `tests/` - Individual test scripts for each command
- `Dockerfile` - Test environment with both implementations installed
- `run-tests.sh` - Runner script to execute all tests

## Running Tests

### Using Docker (recommended)

```bash
cd compliance-tests

# Build the test image (build context is parent directory)
docker build -f Dockerfile -t net-tools-compliance ..

# Run all tests
docker run --rm --privileged net-tools-compliance

# Run specific test
docker run --rm --privileged net-tools-compliance ./tests/nameif_test.sh
```

### Local testing (requires root)

```bash
cd ../
cargo build --release

# Run tests
sudo ./run-tests.sh
```

## Writing Tests

Each test script should:
1. Set up test environment (create dummy interfaces, config files, etc.)
2. Run the original command and capture output/behavior
3. Reset environment
4. Run the Rust implementation and capture output/behavior
5. Compare results
6. Clean up

Tests run in isolated network namespaces when possible to avoid interfering with the host system.

## CI Integration

These tests run in GitHub Actions using Docker containers with `--privileged` flag to allow network interface manipulation.
32 changes: 32 additions & 0 deletions compliance-tests/run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/bash
set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TESTS_DIR="$SCRIPT_DIR/tests"

RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

TOTAL_FAILED=0
TOTAL_PASSED=0

echo "========================================"
echo "Running net-tools compliance tests"
echo "========================================"
echo

for test_script in "$TESTS_DIR"/*_test.sh; do
if [ -f "$test_script" ]; then
if ! bash "$test_script"; then
TOTAL_FAILED=$((TOTAL_FAILED + 1))
fi
echo
fi
done

if [ $TOTAL_FAILED -gt 0 ]; then
exit 1
fi

exit 0
172 changes: 172 additions & 0 deletions compliance-tests/tests/nameif_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#!/bin/bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ORIGINAL_NAMEIF="${ORIGINAL_NAMEIF:-/sbin/nameif}"
RUST_NAMEIF="${RUST_NAMEIF:-/workspace/target/release/nameif}"

FAILED=0
PASSED=0
FAILED_TESTS=()

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

fail() {
echo -e "${RED}FAILED${NC}"
FAILED=$((FAILED + 1))
FAILED_TESTS+=("$1")
}

pass() {
echo -e "${GREEN}ok${NC}"
PASSED=$((PASSED + 1))
}

cleanup() {
ip link delete dummy0 2>/dev/null || true
ip link delete testif0 2>/dev/null || true
ip link delete testif1 2>/dev/null || true
rm -f /tmp/test_mactab
}

setup_interface() {
local name=$1
local mac=$2
ip link add "$name" type dummy
ip link set "$name" address "$mac"
}

test_basic_rename() {
echo -n "test nameif::test_basic_rename ... "
cleanup

setup_interface dummy0 "00:11:22:33:44:55"

set +e
$ORIGINAL_NAMEIF testif0 00:11:22:33:44:55 2>/tmp/original_stderr
ORIG_EXIT=$?
set -e
ORIG_EXISTS=$(ip link show testif0 2>/dev/null && echo "yes" || echo "no")

if [ "$ORIG_EXISTS" = "yes" ]; then
ip link set testif0 name dummy0 2>/dev/null || true
fi

set +e
$RUST_NAMEIF testif0 00:11:22:33:44:55 2>/tmp/rust_stderr
RUST_EXIT=$?
set -e
RUST_EXISTS=$(ip link show testif0 2>/dev/null && echo "yes" || echo "no")

if [ "$ORIG_EXIT" -eq "$RUST_EXIT" ] && [ "$ORIG_EXISTS" = "$RUST_EXISTS" ]; then
pass
else
fail "nameif::test_basic_rename"
fi

cleanup
}

test_config_file() {
echo -n "test nameif::test_config_file ... "
cleanup

setup_interface dummy0 "aa:bb:cc:dd:ee:ff"

cat > /tmp/test_mactab <<EOF
testif1 aa:bb:cc:dd:ee:ff
EOF

set +e
$ORIGINAL_NAMEIF -c /tmp/test_mactab 2>/tmp/original_stderr
ORIG_EXIT=$?
set -e
ORIG_EXISTS=$(ip link show testif1 2>/dev/null && echo "yes" || echo "no")

if [ "$ORIG_EXISTS" = "yes" ]; then
ip link set testif1 name dummy0 2>/dev/null || true
fi

set +e
$RUST_NAMEIF -c /tmp/test_mactab 2>/tmp/rust_stderr
RUST_EXIT=$?
set -e
RUST_EXISTS=$(ip link show testif1 2>/dev/null && echo "yes" || echo "no")

if [ "$ORIG_EXIT" -eq "$RUST_EXIT" ] && [ "$ORIG_EXISTS" = "$RUST_EXISTS" ]; then
pass
else
fail "nameif::test_config_file"
fi

cleanup
}

test_missing_interface() {
echo -n "test nameif::test_missing_interface ... "
cleanup

set +e
$ORIGINAL_NAMEIF testnonexist 00:00:00:00:00:00 2>/tmp/original_stderr
ORIG_EXIT=$?

$RUST_NAMEIF testnonexist 00:00:00:00:00:00 2>/tmp/rust_stderr
RUST_EXIT=$?
set -e

if [ "$ORIG_EXIT" -eq "$RUST_EXIT" ]; then
pass
else
fail "nameif::test_missing_interface"
fi

cleanup
}

test_invalid_mac() {
echo -n "test nameif::test_invalid_mac ... "
cleanup

set +e
$ORIGINAL_NAMEIF testif0 "invalid:mac" 2>/tmp/original_stderr
ORIG_EXIT=$?

$RUST_NAMEIF testif0 "invalid:mac" 2>/tmp/rust_stderr
RUST_EXIT=$?
set -e

# Both should fail
if [ "$ORIG_EXIT" -ne 0 ] && [ "$RUST_EXIT" -ne 0 ]; then
pass
else
fail "nameif::test_invalid_mac"
fi

cleanup
}

echo "running nameif tests"
test_basic_rename
test_config_file
test_missing_interface
test_invalid_mac

echo
if [ $FAILED -gt 0 ]; then
echo "failures:"
for test in "${FAILED_TESTS[@]}"; do
echo " $test"
done
echo
fi

if [ $FAILED -eq 0 ]; then
echo -e "test result: ${GREEN}ok${NC}. $PASSED passed; $FAILED failed"
exit 0
else
echo -e "test result: ${RED}FAILED${NC}. $PASSED passed; $FAILED failed"
exit 1
fi
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
pub mod error;
pub mod hostname;
pub mod nameif;

pub use error::{NetToolsError, Result};
pub use hostname::main as hostname_main;
pub use nameif::main as nameif_main;

pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const RELEASE: &str = concat!("net-tools-rs ", env!("CARGO_PKG_VERSION"));
Loading