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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## v1.2.0

Hostname resolution and hardware manufacturer lookup for subnet scans (`-s`), with zero extra privileges or external tools required:

- `--resolve`: Automatically resolves hostnames (via Reverse DNS PTR, RFC 6762 mDNS port 5353 queries, and RFC 1002 NetBIOS Name Service queries) and hardware manufacturers (via ARP table MAC extraction and embedded OUI lookup).
- `--hostname`: Resolves hostnames only (mDNS, NetBIOS NBNS, and Reverse DNS with domain suffix stripping).
- `--vendor`: Looks up MAC addresses and hardware manufacturer names only, with fallback to Windows `SendARP` for local interface / missing ARP entries.
- `--oui-file <path>`: Allows loading an external Wireshark `manuf` or IEEE OUI database file for custom or offline OUI lookups, with support for colon (`:`) and hyphen (`-`) delimiters and varying hex widths.
- Embedded OUI database: Bundles ~2,000 curated, balanced hardware manufacturer prefixes parsed at compile time from standard Wireshark flat format and sorted in `.rodata` for sub-microsecond binary search lookup (< 50ns per host) with zero external runtime dependencies.
- Ping-blocking host discovery: ARP harvest pass verifies candidates that drop ICMP ping via a parallel SendARP worker pool on Windows and kernel neighbor reachability evaluation (REACHABLE/DELAY states) on Linux, capturing firewalled IoT devices and network gear (GL.iNet, TP-Link, smart home gear) in milliseconds without stalling discovery sweeps.
- Terminal escape sequence filtering: Enforces printable ASCII validation on hostnames retrieved over NetBIOS and mDNS to prevent ANSI escape sequence injection.
- Redesigned usage & help formatting: Clear, structured CLI overview with categorized sections (`COMMANDS`, `SUBNET OPTIONS`, `PORT OPTIONS`, `GLOBAL FLAGS`, and `EXAMPLES`) and `-h` / `-v` shorthands.
- Output formatting: Displays an aligned tabular view (`IP`, `HOSTNAME`, `MAC`, `MANUFACTURER`) when resolution flags are used, while preserving the clean, compact numerical recap when flags are omitted.
- Fully cross-platform across Windows, Linux, and macOS without requiring root or administrator privileges.

## v1.1.0

Port scanning is faster and quieter, with no new privileges required:
Expand Down
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# NetScanner

![Version](https://img.shields.io/badge/Version-v1.1.0-red)
![Version](https://img.shields.io/badge/Version-v1.2.0-red)
![Language](https://img.shields.io/badge/Language-0.16.0-orange?logo=zig&logoSize=auto)
![OS](https://img.shields.io/badge/OS-Linux%2C%20MacOS%2C%20Windows-blue)
![License](https://img.shields.io/badge/License-GNU%20GPL--3.0-green)
Expand Down Expand Up @@ -33,14 +33,29 @@ zig build
## Usage

```sh
ns -s <subnet> [--ping] # Find live hosts (example: 192.168.0.1/24)
ns -p <ip> <port-range> # Scan one host for open ports (example: 192.168.1.1 1-1024)
ns --help # Display help message
ns --version # Display version
ns -s <subnet> [options] # Find live hosts (example: 192.168.0.0/24)
ns -p <ip> <port-range> # Scan one host for open ports (example: 192.168.1.1 1-1024)
ns --help # Display help message
ns --version # Display version
```

`ns -s` uses fast TCP plus ARP discovery unless you pass `--ping`,
which uses ICMP instead.
`ns -s` uses fast TCP plus ARP discovery by default (`--ping` uses ICMP).
Add `--resolve` (or `--hostname`, `--vendor`) to see device names and hardware manufacturers:

```sh
ns -s 192.168.0.0/24 --resolve
```

Output:
```text
IP HOSTNAME MAC MANUFACTURER
192.168.0.1 - 64:fa:2b:b0:93:f1 Sagemcom Broadband SAS
192.168.0.17 DRAGONAS 00:11:32:4f:c2:75 Synology Incorporated
192.168.0.30 LAURPI 2c:cf:67:89:ea:27 Raspberry Pi (Trading) Ltd
192.168.0.39 Laur-PC 9c:6b:00:42:1d:a6 ASRock Incorporation
192.168.0.153 ecb5fa31ae69 ec:b5:fa:31:ae:69 Philips Lighting BV
192.168.0.167 PS5-8ADCDF 5c:96:66:8a:dc:df Sony Interactive Entertainment Inc.
```

## Docs

Expand Down
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ fn buildExe(
/// the system libraries ICMP needs.
fn linkNativeDeps(b: *std.Build, mod: *std.Build.Module, target: std.Build.ResolvedTarget) void {
mod.addCSourceFile(.{ .file = b.path("src/c/ping.c"), .flags = &[_][]const u8{"-Wall"} });
mod.addCSourceFile(.{ .file = b.path("src/c/resolver.c"), .flags = &[_][]const u8{"-Wall"} });
mod.addIncludePath(b.path("src/c"));
mod.addIncludePath(b.path("."));
if (target.result.os.tag == .windows) {
Expand Down
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.{
.name = .NetScanner,
.version = "1.1.0",
.version = "1.2.0",
.minimum_zig_version = "0.16.0",
.paths = .{""},
.fingerprint = 0x5a293d2f4a628cf2,
Expand Down
41 changes: 27 additions & 14 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# NetScanner

![Version](https://img.shields.io/badge/Version-v1.1.0-red)
![Version](https://img.shields.io/badge/Version-v1.2.0-red)
![Language](https://img.shields.io/badge/Language-0.16.0-orange?logo=zig&logoSize=auto)
![OS](https://img.shields.io/badge/OS-Linux%2C%20MacOS%2C%20Windows-blue)
![License](https://img.shields.io/badge/License-GNU%20GPL--3.0-green)
Expand All @@ -23,22 +23,35 @@ file is the full guide.
## Usage

```sh
ns -s <subnet> [--ping] # Find live hosts (example: 192.168.0.1/24)
ns -p <ip> <port-range> [--timeout <ms>] # Scan one host for open ports (example: 192.168.1.1 1-1024)
ns --help # Display help message
ns --version # Display version
ns -s <subnet> [options] # Find live hosts (example: 192.168.0.0/24)
ns -p <ip> <port-range> [--timeout <ms>] # Scan one host for open ports (example: 192.168.1.1 1-1024)
ns --help # Display help message
ns --version # Display version
```

`ns -s` runs fast TCP plus ARP discovery by default. The TCP sweep
finds hosts with open ports, and the ARP harvest pass catches quiet
hosts that answer ARP but drop TCP. Harvest only candidates are
verified with a targeted probe before being reported, so stale table
entries do not turn into false positives. Pass `--ping` to use one
ICMP ping per host instead.
### Discovery options (`ns -s`)

- `ns -s <subnet>`: Fast TCP plus ARP discovery (default).
- `--resolve`: Automatically resolves both hostnames (mDNS port 5353 + NetBIOS port 137 + Reverse DNS PTR) and hardware manufacturers (MAC extraction + OUI database lookup).
- `--hostname`: Resolves hostnames only (mDNS, NetBIOS, Reverse DNS).
- `--vendor`: Resolves MAC addresses and hardware manufacturers only.
- `--oui-file <path>`: Load an external Wireshark `manuf` or IEEE OUI database file.
- `--ping`: Uses ICMP ping sweep instead of TCP plus ARP.

When `--resolve`, `--hostname`, or `--vendor` is passed, results are formatted into an aligned table:

```text
IP HOSTNAME MAC MANUFACTURER
192.168.0.1 - 64:fa:2b:b0:93:f1 Sagemcom Broadband SAS
192.168.0.17 DRAGONAS 00:11:32:4f:c2:75 Synology Incorporated
192.168.0.30 LAURPI 2c:cf:67:89:ea:27 Raspberry Pi (Trading) Ltd
192.168.0.39 Laur-PC 9c:6b:00:42:1d:a6 ASRock Incorporation
192.168.0.153 ecb5fa31ae69 ec:b5:fa:31:ae:69 Philips Lighting BV
192.168.0.167 PS5-8ADCDF 5c:96:66:8a:dc:df Sony Interactive Entertainment Inc.
8 hosts up (6.6s)
```

Results stream as hosts are found, then a sorted recap lists every
host numerically followed by a one line summary such as
`12 hosts up (2.1s)`, so output is easy to scan and diff.
When run without resolution flags, `ns -s` outputs the classic compact numerical list followed by the host count and elapsed time.

## Installation

Expand Down
47 changes: 47 additions & 0 deletions src/c/c_bindings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,55 @@ pub const c = struct {
pub extern "c" fn ping_host(ip_address: [*:0]const u8) bool;
pub extern "c" fn tcp_probe(ip_address: [*:0]const u8, port: u16, timeout_ms: c_int) c_int;
pub extern "c" fn ping_last_error() u32;
pub extern "c" fn resolve_ptr(ip_address: [*:0]const u8, out_buf: [*]u8, out_len: usize) c_int;
pub extern "c" fn query_netbios(ip_address: [*:0]const u8, out_buf: [*]u8, out_len: usize, timeout_ms: c_int) c_int;
pub extern "c" fn query_mdns(ip_address: [*:0]const u8, out_buf: [*]u8, out_len: usize, timeout_ms: c_int) c_int;
pub extern "c" fn read_file_content(path: [*:0]const u8, out_len: *usize) ?[*]u8;
pub extern "c" fn free_file_content(ptr: [*]u8) void;
pub extern "c" fn get_mac_sendarp(ip_address: [*:0]const u8, out_mac: [*]u8) c_int;
};

const std = @import("std");

pub fn getMacSendArp(ip_null_terminated: [*:0]const u8) ?[6]u8 {
var mac: [6]u8 = undefined;
if (c.get_mac_sendarp(ip_null_terminated, &mac) == 0) {
return mac;
}
return null;
}

pub fn readFileContent(path_null_terminated: [*:0]const u8) ?[]const u8 {
var len: usize = 0;
const ptr = c.read_file_content(path_null_terminated, &len) orelse return null;
return ptr[0..len];
}

pub fn freeFileContent(slice: []const u8) void {
c.free_file_content(@constCast(slice.ptr));
}

pub fn resolvePtr(ip_null_terminated: [*:0]const u8, buf: []u8) ?[]const u8 {
if (c.resolve_ptr(ip_null_terminated, buf.ptr, buf.len) == 0) {
return std.mem.sliceTo(buf, 0);
}
return null;
}

pub fn queryNetbios(ip_null_terminated: [*:0]const u8, buf: []u8, timeout_ms: c_int) ?[]const u8 {
if (c.query_netbios(ip_null_terminated, buf.ptr, buf.len, timeout_ms) == 0) {
return std.mem.sliceTo(buf, 0);
}
return null;
}

pub fn queryMdns(ip_null_terminated: [*:0]const u8, buf: []u8, timeout_ms: c_int) ?[]const u8 {
if (c.query_mdns(ip_null_terminated, buf.ptr, buf.len, timeout_ms) == 0) {
return std.mem.sliceTo(buf, 0);
}
return null;
}

pub fn pingHost(ip: ?[*:0]const u8) bool {
if (ip == null) {
return false;
Expand Down
Loading
Loading