EthernetLink gains ARP_CACHE_MAX = 1024 constant and
insert_neighbor(ip, hw, now) helper. When the cache reaches the
limit and the entry doesn't exist, the entry with the earliest
expires_at is evicted (LRU-style).
Prevents unbounded growth of the neighbor cache under ARP flood
attacks. Mirrors Linux /proc/sys/net/ipv4/neigh/default/gc_thresh3.
netcfg exposes /scheme/netcfg/ifaces/eth0/arp/max → '1024'
All 31 tests pass.
SchemeSocket trait gains handle_sendmsg(file, payload, flags) with
default EOPNOTSUPP. SocketScheme::call_inner now dispatches
SocketCall::SendMsg (previously commented out).
UDP handle_sendmsg parses the sendmsg buffer format:
[name_len: usize][payload_len: usize][msg_controllen: usize]
[address: bytes][payload: bytes]
Extracts the destination IP:port from the address, sends via
smoltcp's send_slice. Enables sendto() on unconnected UDP sockets.
Unconnected UDP write_buf now returns EDESTADDRREQ instead of
EADDRNOTAVAIL — clearer error for unconnected writes without
sendto.
TCP and ICMP use the default EOPNOTSUPP implementation (they
are connection-oriented or use write_buf).
All 31 tests pass.
All three tunnel devices now forward encapsulated packets to their
parent interface via DeviceList lookup, instead of dropping them
(R43 drop fix) or self-looping (original behavior).
Changes (same pattern as VLAN R63 fix):
- Add devices: Rc<RefCell<DeviceList>> field to struct
- Add devices parameter to constructor
- send() looks up parent by name and forwards encapsulated frame
- Falls back to debug log on parent-not-found
VlanDevice already integrated (R63). Bond device already correct
(forwards to slaves, no fix needed).
All 31 tests pass.
VlanDevice gains devices: Rc<RefCell<DeviceList>> field. send()
now forwards tagged frames to parent device via the shared list,
instead of dropping them (R43 drop fix) or self-looping (original).
Constructor changed: VlanDevice::new(name, parent, vlan_id, devices).
netfilter/netcfg/route: direct routes + flush + version + help
docs: NETWORKING-IMPROVEMENT-PLAN.md updated with Phase 4
completion status (firewall/conntrack/NAT) and Phase 6 VLAN status.
All 31 tests pass.
parse_route now supports three syntaxes:
default via 10.0.2.2 metric 100 — gatewayed route (existing)
10.0.0.0/8 via 10.0.0.1 — subnet route (existing)
10.0.0.0/8 dev eth0 — direct route (NEW)
Direct routes use via=None (no gateway), dev=specified, src=0.0.0.0.
The routing layer will use the interface's configured IP as source.
Mirrors Linux:
ip route add 10.0.0.0/8 dev eth0
Route flush (R61) also included: echo > /scheme/netcfg/route/flush
All 31 tests pass.
New /scheme/netcfg/route/flush clears all routes at once:
echo > /scheme/netcfg/route/flush
Mirrors Linux 'ip route flush all'.
Replaces the route table with a new empty RouteTable. All
existing Rc shares see the change immediately. Useful for
bulk route management (clear and re-add). Notifies route/list
subscribers after flush.
All 31 tests pass.
Smolnetd::new() now takes Vec<(Fd, MAC, name)> for multi-adapter
support. Updated main.rs to wrap the single adapter in a Vec.
Also adds:
- netcfg/help node: lists all available paths
- netcfg/nat/bindings: active SNAT session display
- netcfg/nat/stats: NAT rule list
All 31 tests pass.
NatTable gains bindings: Vec<NatBinding> field. record_snat()
called after successful SNAT rewrite captures original→translated
src_addr/port mapping. Bindings capped at 1024 entries (FIFO).
format_bindings() returns:
Active SNAT bindings: 3
10.0.0.1:1234 -> 192.168.1.100:40001
10.0.0.2:5678 -> 192.168.1.100:40002
Useful for diagnosing NAT issues and seeing which internal
connections are being source-NAT'd. Mirrors Linux
/proc/net/ip_conntrack display.
All 31 tests pass.
Rule gains metric: u32 field (default 0). Lower metric = higher
priority. lookup_rule() now selects the matching rule with the
lowest metric among those with the same prefix length.
route/add parser accepts 'metric N':
default via 10.0.2.2 metric 100
default via 10.0.2.254 metric 200
Display shows metric when non-zero:
default via 10.0.2.2 dev eth0 src 10.0.2.15 metric 100
Rule::with_metric(m) builder added. All existing callers use
default metric=0 (backward compatible).
Use case: multi-homed hosts with primary/backup default routes.
The primary route has metric 0, the backup has metric 100.
All 31 tests pass.
ConntrackTable gains max_entries: usize field (default 65536).
When the table reaches this limit, new connections return
ConnState::OverLimit instead of being inserted.
Enforced at both insertion points:
- TCP/UDP track() path (new connections)
- ICMP echo track_icmp() path (new ICMP entries)
Stats output now includes max_entries: N.
Mirrors Linux net.netfilter.nf_conntrack_max (default 65536).
All 31 tests pass.
ConntrackTable gains icmp_error_count: u64 field, incremented
whenever track_icmp_error() processes an ICMP error that doesn't
match an existing tracked connection.
Stats output now includes:
icmp_errors: N
Useful for diagnosing ICMP error processing and detecting
anomalies in ICMP error rate.
All 31 tests pass.
New paths under /scheme/netcfg/conntrack/:
stats → per-protocol breakdown (tcp/udp/icmp + per-state)
list → full connection listing with TCP state
Previously conntrack data was only accessible via netfilter scheme
(/scheme/netfilter/conntrack/*). Now available directly through
the main netcfg monitoring interface.
Output matches the netfilter nodes exactly (same format/stats methods).
All 31 tests pass.
Rule parser now accepts iptables-style:
ACCEPT input -p tcp --ctstate ESTABLISHED,RELATED
DROP forward state NEW
Both 'state' and '--ctstate' keywords work. States are case-
insensitive: NEW/New/new, ESTABLISHED/Established/established,
RELATED/Related/related, INVALID/Invalid/invalid.
Comma-separated lists (e.g. 'ESTABLISHED,RELATED') use the FIRST
recognized state only — FilterRule stores a single StateMatch field.
Docstring updated with new examples.
All 31 tests pass.
Added ConnState::Error to the connection state enum for ICMP error
packets. Added is_error detection for ICMPv4/v6 Dest Unreachable
and Time Exceeded types. When an ICMP error is detected, delegates
to track_icmp_error() which extracts the embedded original packet
tuple and relates it to an existing tracked connection (mirrors
Linux 7.1 nf_conntrack_icmp_error() in net/netfilter/nf_conntrack_proto_icmp.c).
Previously WIP — now committed to stabilize the base fork build.
SO_LINGER controls close() behavior via struct linger
{ int l_onoff; int l_linger; }
- get returns {1, 0} (linger enabled, zero timeout)
- set accepts any value, delegates close semantics to smoltcp
Uses constant 14 since the flat Redox namespace can't use
Linux level SOL_SOCKET=13 (collides with TCP_CONGESTION=13).
All 31 tests pass.
ConnEntry gains fin_from_orig: bool field to track which direction
sent the first FIN. This enables correct TimeWait transition:
FinWait→TimeWait now only fires when the OPPOSITE direction sends
FIN (not when the same side retransmits).
Per Linux nf_conntrack_proto_tcp.c:
- fin_from_orig=true means orig sent first FIN
- TimeWait transition requires reply FIN next
- fin_from_orig=false means reply sent first FIN
- TimeWait transition requires orig FIN next
TCP state now included in format() output:
Established tcp=Established src=10.0.0.1 dst=10.0.0.2 sport=80 dport=1234 ...
New tcp=SynSent src=...
Established tcp=TimeWait src=...
All 31 tests pass.
The TCP three-way handshake completion (initiator's ACK after
SYN-ACK) arrives on the ORIGINAL direction, not the reply
direction. Previously the SynRecv→Established transition was
gated on is_orig==false, which meant it would never fire after
the initial SYN-ACK reply. Connections would stay in SynRecv
state forever with ConnState::New.
Fix: move SynRecv→Established from reply to orig direction, per
Linux nf_conntrack_proto_tcp.c (TCP_CONNTRACK_SYN_RECV fires on
IP_CT_DIR_ORIGINAL packets).
Reply direction now only handles:
- SYN-ACK from responder (SynSent→SynRecv)
- FIN from responder (Established→FinWait)
- Second FIN from responder (FinWait→TimeWait)
- TimeWait timeout extension
Orig direction now handles:
- ACK from initiator (SynRecv→Established)
- FIN from initiator (Established→FinWait)
- Second FIN from initiator (FinWait→TimeWait)
RST still closes from either direction.
All 31 tests pass.
ConntrackTable gains stats() method returning per-protocol breakdown:
tcp_entries: N (est=N syn=N syn_recv=N fin=N tw=N close=N)
udp_entries: N
icmp_entries: N
over_limit: N
total_entries: N
TCP states counted: None, SynSent, SynRecv, Established, FinWait,
TimeWait, Close. Mirrors Linux /proc/net/stat/nf_conntrack.
Exposed via three channels:
- /scheme/netfilter/conntrack/stats (new dedicated node)
- /scheme/netfilter/stats (existing, now includes per-state)
- /scheme/netcfg/summary (includes conntrack stats block)
All 31 tests pass.
Complete TCP connection tracking state machine:
NEW TRANSITIONS (mirrors Linux nf_conntrack_proto_tcp.c):
- RST from either direction → Close (10s timeout, ConnState::New)
- Established + FIN (either dir) → FinWait (120s timeout)
- FinWait + FIN (other dir) → TimeWait (120s timeout)
- TimeWait extends timeout on any traffic (125s idle for safe
delayed-ACK arrival)
PRESERVED TRANSITIONS (from original code):
- None → SynSent (on original SYN) — unchanged
- SynSent → SynRecv (on reply SYN-ACK) — unchanged
- SynRecv → Established (on reply ACK) — unchanged
NEW HELPERS:
- tcp_flags(): extract TCP flags byte from packet
- is_fin(): check FIN bit
- is_rst(): check RST bit
NEW TESTS:
- rst_forces_state_to_new: RST resets conn state to New
(entry stays 10s for lingering cleanup)
- fin_transitions_established_to_timewait: double-FIN
transition through FinWait → TimeWait
FIXED BUGS:
- advance_entry_state never processed FIN/RST/TimeWait, so
connections stayed Established for 5 days after actual close.
Now: FinWait after FIN, TimeWait after both FINs, Close on RST.
All 31 tests pass.
Add the missing tcp_flags(), is_fin(), is_rst() helpers and upgrade
advance_entry_state from a 3-parameter stub to a 4-parameter function
that takes &PacketContext. This completes the WIP conntrack state
machine with proper TCP tracking (SynSent, SynRecv, Established,
FinWait, TimeWait, Close) and RST/session-timeout handling.
Reference: Linux 7.x net/netfilter/nf_conntrack_proto_tcp.c
(tcp_packet, nf_conntrack_tcp_packet state machine).
CRITICAL: main.rs all[] initial event array was missing TcpScheme.
This meant TCP scheme events that arrived before the event loop
started processing would be delayed until the event queue fired.
In practice, TCP connections could stall at startup.
Note: main.rs also already subscribed TcpScheme - only the initial
array was incomplete, not the event queue subscription.
ALSO FIXED:
- scheme/socket.rs handle_block: read/write timeout was swapped.
Op::Read was using write_timeout and Op::Write was using
read_timeout (SO_RCVTIMEO/SO_SNDTIMEO semantics).
- netcfg/mod.rs route/rm: now accepts 'default' keyword same as
route/add does. Writing 'default' to route/rm deletes the
0.0.0.0/0 default route, consistent with parse_route behavior.
All 29 existing tests still pass.
parse_port now rejects port ranges ('1024:65535') with a clear
error message instead of silently using only the first number.
FilterRule uses a single u16 field and cannot represent ranges.
infer_context now documents the hardcoded 40-byte IPv6 header
limitation: smoltcp's next_header() chases extension headers to
return the final protocol, but the transport offset is not
computed. Port extraction silently returns None/None for packets
with extension headers, which is safe but means port-based
filter rules won't match for such packets.
CRITICAL/MEDIUM BUGS FIXED:
1. ethernet.rs send_ndp_solicit: state corruption via recursive call
The function destructured self.ndp_state by value (target, tries,
silent_until) at the start and wrote back at the end. But between
destructuring and writing back, self.drop_waiting_packets_v6(target)
could recursively call self.send_ndp_solicit(Instant::ZERO) for a
different target. The recursive call updated self.ndp_state with the
new target's state. When control returned to the original frame, the
original write-back clobbered the recursive call's state, silently
losing neighbor discovery for the new target.
Fix: use scoped pattern matches to read target/tries/silent_until
from the live state right before they are needed, so the recursive
call's writes are preserved. This matches the pattern used by
send_arp (which uses ref mut and is correct).
2. scheme/socket.rs on_close: port double-free
close_file() was called before the refcount check, so the port was
released on every close. For a dup'd socket, the second close
tried to release the port again — double-free.
Fix: compute the new refcount first, only call close_file and
remove from socket_set when the count reaches 0 (last reference).
3. scheme/tcp.rs new_socket: port + socket leak on connect failure
If get_port() succeeded but connect() later failed, the port was
claimed and the socket was added to socket_set, but new_socket
returned Err. The caller (open_inner) did not insert the file
handle, so on_close was never called. Both the port and the socket
slot leaked.
Fix: when connect() fails, release the auto-allocated port before
returning. Explicit user-provided ports are still released by
on_close when the last file is dropped (preserves the existing
on_close-based release).
4. observer.rs capture: per-packet size limit not enforced
Vec::with_capacity only pre-allocates; extend_from_slice copies the
full packet. The intended per-packet limit (MAX_CAPTURE_BYTES /
max_packets = 256 bytes) was being bypassed, allowing a single
1500-byte packet to consume the full capture buffer.
Fix: truncate to per_packet_limit before extending.
5. observer.rs capture: short packets bypassed filter
Packets < 20 bytes returned true (capture anyway) regardless of
the user's filter. A filter like 'tcp port 80' would capture all
short packets, including non-TCP.
Fix: return false (no match) for short packets. Filter semantics
are now consistent: if a packet can't be matched, it's not captured.
All 29 existing tests still pass.
CRITICAL BUGS FIXED:
1. router/mod.rs: ICMP errors for Unreachable/Prohibit went to rx_buffer
(infinite loop back to input) instead of tx_buffer (sent to source).
Combined Unreachable/Prohibit arms; both now use tx_buffer.
2. scheme/socket.rs dup(): refcount leak in update_with branch.
OLD code: new_handle.socket_handle() got +2 when update_with was
Some, but only decremented once on close. Net: SH1 over-counted (+1),
SH2 (new listening socket from update_with) never tracked at all.
FIX: in update_with branch, increment refcount of 'socket_handle'
(the new SH), not new_handle.socket_handle(). The always-run
increment at the bottom covers new_handle. Both increments serve
different purposes and are now distinct.
3. scheme/udp.rs: 4 .expect() panic vectors in bind/send/recv.
'Can't bind', 'Can't send', 'Can't receive', 'Can't recieve' all
panicked the daemon. Now return EIO via ? operator.
4. link/vlan.rs (and vxlan/gre/ipip already partially fixed in R42):
send() pushed tagged packets into self.recv_queue, creating a
self-loop where packets were never delivered. Now drops packets
with a debug log since no parent device reference exists.
5. link/qdisc.rs: TokenBucket token_add could overflow u64 on long
elapsed durations. Changed to saturating_mul.
DOC FIX:
6. filter/table.rs docstring example used --sport 1024:65535 (port
range) but parse_port only accepts single port. Changed example to
use single port value. Range support is a future enhancement.
All 29 existing tests still pass.
CRITICAL BUGS FIXED:
1. Smolnetd startup panic on missing/malformed ip_router cfg (scheme/mod.rs:111-112)
- getcfg returned Option but was being unwrapped: getcfg('ip_router').unwrap()
- .expect('Can't parse the ip_router cfg.') on malformed IP would panic
- Fix: match on Result, fall back to 0.0.0.0 with warning log
2. Smolnetd default route panic on 0.0.0.0 gateway (scheme/mod.rs:140-142)
- iface.routes_mut().add_default_ipv4_route(0.0.0.0).expect(...) panics
- smoltcp rejects default route with 0.0.0.0 as gateway
- Fix: skip route addition when gateway is unspecified
3. TUN event loop destroyed data (scheme/tun.rs:119-133)
- Loop moved packets from dev.tx to dev.rx - stealing data userspace was
supposed to read (since dev.tx is aliased to device_rx)
- Fix: only clear stale packets from dev.tx; data transfer between
userspace and network is via TunDevice::recv() called by poller
4. ip_forward sysctl broke two-phase write/commit pattern (netcfg/mod.rs:367-389)
- write_line closure immediately called ip_forward.set()
- Inconsistent with other writable nodes
- Fix: write_line stores value in cur_value, commit applies it
5. ICMP Udp socket was non-functional (scheme/icmp.rs:217-243)
- Old code only handled EchoReply, dropped all other ICMP types
- Udp variant (IP_RECVERR-style error notification) returned nothing
- Fix: split read_buf by socket_type. Echo still only matches EchoReply.
Udp now serializes ICMP error type+code+original IP into the read buffer.
SEMANTIC BUGS FIXED:
6. UDP connected local_addr resolution was inverted (scheme/udp.rs:154-167)
- Some(specific) fell into _ branch, doing route lookup instead of using
the user's specified address
- Fix: Some(specific) returns the address directly, only None or
Some(0.0.0.0) trigger route lookup
7. claim_port_reuse lacked documentation (port_set.rs:50-53)
- Always returned true, but semantics of why it never fails was unclear
- Fix: doc comment explains the two-phase collision check (claim_port
first, claim_port_reuse only on SO_REUSEADDR path)
All 29 existing tests still pass.
CRITICAL BUGS FIXED:
1. STP build_bpdu runtime panic (link/stp.rs:121-123)
- Duration::total_millis() returns i64, to_be_bytes() = [u8; 8]
- But destination buffers (buf[29..31], buf[31..33], buf[33..35]) are 2 bytes
- copy_from_slice panics on length mismatch
- Triggered by every root bridge hello BPDU — daemon crash
- Fix: convert to ticks (1s = 256 ticks per IEEE 802.1D) as u16
2. SLAAC PIO off-by-2 byte parsing (slaac.rs:155-180)
- parse_router_advertisement used opt_data[2] for prefix length
- After pos += 2 consumed type+length, opt_data[0] is the prefix length
- All PIO fields were off by 2 — SLAAC got wrong prefix length, flags,
valid_lifetime (read Preferred Lifetime), preferred_lifetime (read Reserved2)
- Fix: use opt_data[0] for prefix length, [1] for flags, [2..6] for valid,
[6..10] for preferred, [14..30] for prefix bytes
3. ICMPv4 error wrong IHL extraction (icmp_error.rs:25, 62)
- (ipv4.version() & 0x0f) * 4 always computed 16
- smoltcp's version() returns 4 (version), not combined byte
- Fix: use ipv4.header_len()
4. UDP can_recv panic on port-only endpoint (scheme/udp.rs:54)
- data.addr.unwrap() panics if addr is None (e.g. udp/:53)
- is_specified() returns true when port is non-zero, even if addr is None
- Fix: use let-else pattern, accept all packets if addr is None
5. TCP .expect() calls crash daemon (scheme/tcp.rs)
- 5 .expect() calls in connect/listen/send/recv paths
- Any socket error panics the entire netstack daemon
- Fix: replace with .map_err() returning EIO
6. ICMP .unwrap() on malformed packets (scheme/icmp.rs:217-220)
- recv().expect() panics, Icmpv4Repr::parse().unwrap() panics
- Crafted/malformed ICMP packets would crash the daemon
- Fix: use match, drop unparseable packets
NEW TESTS (6 added, 29 total now passing):
- icmp_error::icmpv4_short_packet_returns_none
- icmp_error::icmpv4_preserves_destination_address (regression for bug 3)
- icmp_error::icmpv4_with_ip_options_includes_extended_header
- slaac::ra_with_pio_64_parses_correctly (regression for bug 2)
- link::stp::bpdu_minimal_parses
- link::stp::bpdu_short_returns_none
- link::stp::bpdu_wrong_protocol_returns_none
- link::stp::build_bpdu_does_not_panic (regression for bug 1)
The SLAAC test would have failed before the off-by-2 fix.
The STP build_bpdu test would have panicked before the ticks fix.
The ICMP tests verify the full IP header (incl. IHL=6 options) is preserved.
CRITICAL BUG #1: is_syn() IPv4-only
conntrack.rs is_syn() hardcoded offset 33 (IPv4: 20+13).
For IPv6, TCP flags are at offset 53 (40+13). IPv6 SYN packets
were never detected as SYNs, so SYN flood rate limiting was
completely broken for IPv6 traffic.
Fix: pass l3num, compute tcp_offset correctly for both families.
Added ipv6_syn_detection_works regression test with IPv6 SYN packet
at offset 53 — verifies the fix.
CRITICAL BUG #2: netfilter writes all broken
open_path stored paths without leading '/' (e.g. 'rule/add')
but commit() compared against '/rule/add' (with leading '/').
Result: ALL write operations silently failed with EINVAL:
- rule/add - never added rules
- nat/add - never added NAT
- rule/del - never deleted
- nat/del - never deleted
- policy/X - never set policy
- reset - never reset
- counters/reset - never reset
Fix: align commit() paths with the stored convention (no '/').
Removed dead /rule/del/<id> REST-style path (no open_path arm existed).
Updated stored path convention comment.
Tests: 21 total (added ipv6_syn_detection_works), all passing.
Bridge MAC learning tests:
- learn_stores_unicast_mapping: stored MAC resolves on correct port
- learn_ignores_multicast: broadcast/multicast never enters FDB
- learn_replaces_existing_entry_on_new_port: MAC moves update port
- age_entries_removes_expired_macs: 300s timeout respected
- lookup_returns_none_for_unknown_mac: unknown MAC returns None
Total tests across netstack: 20 (5 table + 4 conntrack + 6 nat + 5 bridge)
All passing.
CRITICAL BUG FIX: is_echo_request() and track_icmp() were reading
ctx.packet[0] thinking it was the ICMP type byte. But ctx.packet is
the IP packet, so offset 0 is the IP version/IHL byte (0x45 for IPv4).
Result: ICMP echo detection NEVER matched Type 8 packets.
- ICMP rate limiting (R32) was a no-op — wrong byte read
- ICMP echo tracking in track_icmp() was checking IP version vs 8/128
Fixed: read ICMP type from offset for IPv4, or offset 40 for IPv6.
ALSO: separate TCP SYN and ICMP echo rate limit maps.
Previously both shared one BTreeMap, so a host doing 100 TCP SYNs
+ 1 ICMP echo would be over-limit. Now each has its own budget.
NEW TESTS (4 conntrack + 5 filter table = 9 total, all passing):
- syn_limit_triggers_after_threshold
- icmp_limit_triggers_after_threshold
- syn_and_icmp_have_independent_budgets
- non_syn_tcp_does_not_count_against_syn_limit
These tests caught the ICMP type bug — failed before fix, pass after.
5 new unit tests in filter::table::tests:
- drop_rule_actually_drops (R33 bug regression test)
- accept_rule_actually_accepts (R33 bug regression test)
- rule_matching_other_hook_does_not_apply (hook isolation)
- default_policy_applied_when_no_rules (fallback)
- reset_counters_clears_metrics_keeps_rules (R34 method coverage)
All 5 tests pass — proves the critical verdict bug from R33
is fixed and reset_counters from R34 works correctly.
Also adds FilterTable::chain_summary() returning:
'input: pkts=12 bytes=5678 policy=ACCEPT' (per-chain line)
for use by future netcfg summary extension.
FilterTable::evaluate() set final_verdict for matched rules but never
returned it — always returned the chain's default_policy instead.
This meant every firewall rule (ACCEPT/DROP/REJECT/LOG) was being
evaluated (correctly) but its verdict was DISCARDED.
Symptoms:
- 'DROP' rules had no effect — packets flowed through
- 'ACCEPT' rules had no effect — packets flowed through
- Only default_policy was ever applied
Fix: return final_verdict.unwrap_or(default_policy).
Now 'iptables-style' rules actually take effect:
- DROP rules block traffic
- ACCEPT rules allow traffic (before default)
- REJECT rules send ICMP unreachable
- LOG rules log and continue
This bug was introduced when the filter evaluation logic was
refactored and the return statement was left pointing at the
default policy instead of the verdict from the rule loop.
Confirmed via cargo build; warning 'value assigned to final_verdict
is never read' is now gone.
Conntrack now rate-limits ICMP/ICMPv6 echo requests per source:
- is_echo_request() detects Type 8 (ICMPv4) / Type 128 (ICMPv6)
- check_icmp_limit() enforces 20 echoes/sec per source IP
- Over-limit returns ConnState::OverLimit, increments over_limit_count
Mirrors existing SYN flood protection (100 SYN/sec per source).
Smaller threshold (20/sec) because ICMP is cheaper to generate.
Reuses same rate_limits map — TCP SYNs and ICMP echoes share the
budget. Side effect: 100 SYN + 20 echo = 120 packets/sec from one
source before any trigger, which is reasonable.
/scheme/netcfg/summary outputs:
interfaces: eth0=up lo=up
routes: 4
sockets: 7 (tcp=3 udp=4 icmp=0 raw=0)
ip_forward: 1
eth0 stats: rx=1234/16 tx=5678/12 err=0/0 drop=0/2
Single read aggregates:
- Link state of eth0 and loopback
- Total route count
- Per-protocol socket counts (TCP/UDP/ICMP/Raw)
- ip_forward toggle state
- Full MIB-II stats for eth0
Useful for at-a-glance monitoring without reading 6+ separate nodes.
LinkDevice trait gains is_promiscuous() and set_promiscuous().
EthernetLink stores promiscuous flag (default: false).
Receive path bypasses MAC check when promiscuous enabled —
captures all frames regardless of destination MAC.
netcfg/ifaces/eth0/promiscuous rw:
cat /scheme/netcfg/ifaces/eth0/promiscuous → 'off' or 'on'
echo on > .../promiscuous → enable promiscuous mode
echo off > .../promiscuous → disable
Accepts: on/off/1/0/yes/no/true/false.
Mirrors Linux 'ip link set dev promisc on/off'.
Required for packet capture tools that need to see all traffic.
Reads the default gateway from the route table:
cat /scheme/netcfg/route/gateway
→ 'default via 192.168.1.1\n'
or 'no default route\n' if no default gateway configured.
Looks up the route for 0.0.0.0 (the default route CIDR) and
returns its 'via' address. Mirrors Linux 'ip route show default'.
All 4 RFC 1213 counters now wired to real events:
- rx_errors: malformed Ethernet frame or ARP packet
- tx_errors: network_file.write_all() failed
- rx_dropped: ARP/NDP neighbor resolution failed or queue full
- tx_dropped: qdisc returned None (rate-limit kicked in)
Stats output now reflects actual error/drop conditions.
Mirrors Linux's ifconfig/sar 'errors' and 'dropped' fields.
The new rx_dropped counter is now incremented in real drop paths:
- drop_waiting_packets_v4: ARP/NDP failed after MAX_TRIES, packets dropped
- drop_waiting_packets_v6: same for IPv6 neighbor discovery
- handle_missing_neighbor: waiting queue full, incoming packet dropped
Net effect: stats now reflect actual packet loss conditions visible
via /scheme/netcfg/ifaces/eth0/stats:
rx_dropped=N (was always 0 before)
tx_dropped was already incremented by qdisc drops (R24).
LinkDevice trait gains is_enabled() and set_enabled() with defaults.
EthernetLink stores enabled flag (default: true). When disabled,
link_state() reports 'down' regardless of hardware_address presence.
netcfg/ifaces/eth0/enabled is rw:
cat /scheme/netcfg/ifaces/eth0/enabled → 'up' or 'down'
echo up > .../enabled → bring interface up
echo down > .../enabled → bring interface down
echo on/off/1/0/yes/no → also accepted
Mirrors Linux 'ip link set dev <state>'.
Pre-existing link state (hardware_address Some/None) unchanged;
the enabled flag adds a software override layer that can be
toggled without restarting the netstack.