netpulse is an experimental Linux network CLI written in Rust. It reads and changes kernel networking state directly through Netlink without invoking the ip command.
The project is a learning-oriented implementation of a small subset of iproute2. It currently works with links, interface addresses, routes, and neighbours, with human-readable or JSON output.
It is not yet a production-ready network manager.
netpulse CLI
|
| rtnetlink messages
v
Linux networking subsystem
|
+-- links
+-- addresses
+-- routes
+-- neighbour table
Main dependencies:
rtnetlinkandnetlink-packet-routefor Netlink communicationtokioandfuturesfor asynchronous requests and streamsclapfor nested command-line parsingserdeandserde_jsonfor JSON outputcoloredfor terminal formattingipnetworkfor CIDR parsing
- Linux
- Rust and Cargo
- A kernel with rtnetlink support
Read-only commands normally do not need root. Commands that add, delete, or modify network state require root or CAP_NET_ADMIN.
cargo build
cargo build --releaseThe binaries are created at:
target/debug/netpulse
target/release/netpulse
During development:
cargo run -- link showThe -- separates Cargo's arguments from arguments passed to netpulse.
After building:
./target/release/netpulse link shownetpulse [GLOBAL OPTIONS] <OBJECT> <ACTION> [ACTION OPTIONS]
Available commands:
link show | add | del | set-mtu
address show | add | del
route show | add | del
neighbours show | add | del
Inspect help at any level:
netpulse --help
netpulse link --help
netpulse link add --help
netpulse route show --help--json is global, so it can appear before or after a subcommand:
netpulse --json link show
netpulse link show --json
netpulse address show --interface wlan0 --jsonCurrent limitation: address, route, and neighbours print a debug representation of the selected action before their normal output. Their --json output therefore contains one non-JSON line. link --json does not have that debug line.
The global option below is accepted but currently unused:
--route-ipversion <ROUTE_IPVERSION>
Use the implemented route option instead:
netpulse route show --ip-version v4
netpulse route show --ip-version v6A link is a kernel network interface such as lo, wlan0, eth0, docker0, or wg0.
netpulse link show
netpulse link show --interface wlan0
netpulse link show -i wlan0
netpulse --json link showThe output includes interface index, name, state, MTU, MAC address, and selected flags. Without --interface, all links are shown.
sudo netpulse link add --name dummy0 --kind dummy
sudo netpulse link add -n wg0 -k wireguard| Option | Short | Meaning |
|---|---|---|
--name <NAME> |
-n |
New interface name |
--kind <KIND> |
-k |
Link kind such as dummy, bridge, or wireguard |
Creating a link does not assign an address or necessarily set the link up.
sudo netpulse link set-mtu --interface dummy0 --mtu 1400
sudo netpulse link set-mtu -i dummy0 -m 1400sudo netpulse link del --name dummy0
sudo netpulse link del -n dummy0Deleting a link also removes kernel state attached to it, including connected addresses and routes.
An address belongs to an interface and includes a prefix length, such as 192.0.2.10/24 or 2001:db8::10/64.
netpulse address show
netpulse address show --interface wlan0
netpulse address show -i wlan0The output includes the interface name, address/prefix, and scope.
IPv4:
sudo netpulse address add --interface dummy0 --address 192.0.2.10/24IPv6:
sudo netpulse address add --interface dummy0 --address 2001:db8::10/64Short form:
sudo netpulse address add -i dummy0 -a 192.0.2.10/24--address is parsed as IpNetwork, so include the CIDR prefix.
Use the same interface, address, and prefix:
sudo netpulse address del --interface dummy0 --address 192.0.2.10/24
sudo netpulse address del -i dummy0 -a 192.0.2.10/24A route tells the kernel where packets for a destination network should go.
Show IPv4 and IPv6 routes:
netpulse route showFilter by family:
netpulse route show --ip-version v4
netpulse route show --ip-version v6
netpulse route show -i v4Valid values are v4 and v6. The output includes destination, table, gateway, output interface, protocol, and scope.
sudo netpulse route add \
--dest 203.0.113.0/24 \
--gateway 192.168.1.1/32 \
--table-id 254Short form:
sudo netpulse route add -d 203.0.113.0/24 -g 192.168.1.1/32 -t 254| Option | Short | Meaning |
|---|---|---|
--dest <DEST> |
-d |
IPv4 destination in CIDR notation |
--gateway <GATEWAY> |
-g |
IPv4 next-hop gateway, currently parsed as CIDR |
--table-id <TABLE_ID> |
-t |
Numeric Linux routing-table ID |
Common table IDs:
255 local
254 main
253 default
Add/delete currently support IPv4 only. The request does not specify an output interface, so Linux must already be able to resolve the gateway through a connected route.
Describe the same destination, gateway, and table used when adding it:
sudo netpulse route del \
--dest 203.0.113.0/24 \
--gateway 192.168.1.1/32 \
--table-id 254Short form:
sudo netpulse route del -d 203.0.113.0/24 -g 192.168.1.1/32 -t 254Adding an existing route normally returns File exists. Deleting a missing route normally returns No such process.
The neighbour table maps network addresses to link-layer addresses:
IPv4 -> MAC using ARP
IPv6 -> MAC using NDP
Linux normally learns these mappings automatically. Static entries are mainly useful in controlled networks, virtual networking, broken discovery environments, and tests.
netpulse neighbours show
netpulse neighbours show --interface wlan0
netpulse neighbours show -i wlan0
netpulse neighbours show --family bridge
netpulse neighbours show -f bridgeCurrent behavior: only the exact string bridge selects the bridge family. Any other value, or no value, selects IPv4. IPv6 neighbour display is not currently selected by this parameter.
The request also sets the Own neighbour flag, so results may differ from an unfiltered ip neighbour show.
sudo netpulse neighbours add \
--interface wlan0 \
--ip-addr 192.168.1.50 \
--mac-address 10:20:30:40:50:60Short form:
sudo netpulse neighbours add -i wlan0 -a 192.168.1.50 -m 10:20:30:40:50:60| Option | Short | Meaning |
|---|---|---|
--interface <INTERFACE> |
-i |
Interface owning the entry |
--ip-addr <IP_ADDR> |
-a |
IPv4 or IPv6 neighbour address |
--mac-address <MAC_ADDRESS> |
-m |
Six colon-separated byte components |
Important current limitation: MAC components are parsed in base 10, not base 16. A conventional value containing a through f, such as aa:bb:cc:dd:ee:ff, fails. The accepted example 10:20:30:40:50:60 represents bytes that display in hexadecimal as 0a:14:1e:28:32:3c. Fix the parser before relying on conventional MAC input.
New entries use the rtnetlink default permanent neighbour state.
sudo netpulse neighbours del \
--interface wlan0 \
--ip-addr 192.168.1.50 \
--mac-address 10:20:30:40:50:60The current CLI requires --mac-address, although deletion identifies the entry by interface and destination IP and does not include the parsed MAC in the Netlink deletion message.
Experiment on a disposable dummy interface instead of changing the primary network interface:
sudo netpulse link add --name dummy0 --kind dummy
sudo netpulse address add --interface dummy0 --address 192.0.2.10/24
netpulse link show --interface dummy0
netpulse address show --interface dummy0
sudo netpulse link set-mtu --interface dummy0 --mtu 1400
sudo netpulse address del --interface dummy0 --address 192.0.2.10/24
sudo netpulse link del --name dummy0192.0.2.0/24 is reserved for documentation and is suitable for isolated examples.
| netpulse | Approximate ip equivalent |
|---|---|
netpulse link show |
ip link show |
netpulse link show -i wlan0 |
ip link show dev wlan0 |
netpulse link add -n dummy0 -k dummy |
ip link add dummy0 type dummy |
netpulse link del -n dummy0 |
ip link del dummy0 |
netpulse link set-mtu -i wlan0 -m 1400 |
ip link set dev wlan0 mtu 1400 |
netpulse address show |
ip address show |
netpulse address add -i dummy0 -a 192.0.2.10/24 |
ip address add 192.0.2.10/24 dev dummy0 |
netpulse address del -i dummy0 -a 192.0.2.10/24 |
ip address del 192.0.2.10/24 dev dummy0 |
netpulse route show -i v4 |
ip -4 route show |
netpulse route show -i v6 |
ip -6 route show |
netpulse route add -d NET -g GW -t 254 |
ip route add NET via GW table 254 |
netpulse route del -d NET -g GW -t 254 |
ip route del NET via GW table 254 |
netpulse neighbours show |
ip neighbour show (not an exact filter match) |
Mutation requires administrative capability:
sudo netpulse ...The interface, address, route, or neighbour already exists. Inspect state before adding it again:
netpulse link show
netpulse address show
netpulse route show
netpulse neighbours shownetpulse link showInterface names are exact and case-sensitive.
A gateway must already be reachable through a connected route:
netpulse address show
netpulse route show --ip-version v4address, route, and neighbours currently emit one debug line before JSON. link --json is currently clean.
- Linux only and only a subset of
iproute2. - No command currently sets a link up or down.
- Link-kind input is not validated by Clap.
- Route addition and deletion support IPv4 only.
- Route add/delete always require a gateway and table ID and do not accept an output interface.
- Global
--route-ipversionis unused. - Routing-rule code exists internally but is not exposed through the CLI.
- Network namespace and monitoring commands are not exposed.
- Neighbour family parsing only recognizes
bridge; other values select IPv4. - Neighbour display sets the
Ownflag and may omit expected entries. - MAC input is parsed as decimal rather than hexadecimal.
- Neighbour deletion requires a MAC argument that is not used in the deletion message.
- Some missing-link paths return success without changing anything.
- Debug prints contaminate JSON for address, route, and neighbour commands.
- Some internal errors are unwrapped instead of propagated.
src/main.rs CLI definitions and command dispatch
src/links.rs Link query and mutation
src/addresses.rs Address query and mutation
src/routes.rs Route query and mutation
src/neighbours.rs Neighbour query and mutation
src/rules.rs Internal rule display code, not exposed by CLI
src/display.rs Human-readable formatting and colors
- Review mutation commands before running them with
sudo. - Do not delete or reconfigure the interface carrying an SSH session.
- Prefer a dummy interface or network namespace for experiments.
- Static neighbour entries can redirect local traffic to a chosen MAC address.
- Incorrect routes can disconnect the machine or bypass expected paths.
- Compare results with
ip link,ip address,ip route, andip neighbourwhile learning.