Repository navigation
IPv4 Routing
IPv4 route identity, longest-prefix matching, connected-route maintenance, and the
route/ifconfigprefix syntax that fill JNode'sIPv4RoutingTable.
JNode's IPv4 routing is split across three layers:
-
IPv4RoutingTablestoresIPv4Routeentries in aVectorand selects the best route for a destination. -
IPv4Routegives a route its identity (destination,subnetmask,gateway,device) and decides whether it is a host route or a network route. -
IfconfigandRoutetranslate configuration into table operations, including the on-link routes that must follow an interface address.
The implementation was corrected by 67819dead (#768/#770) and 69df8bd10 (#773/#777). Before those changes a route lookup could prefer a less-specific route, the table could end up with duplicate or stale entries, a re-addressed interface kept its old connected route, and route could not parse the BSD order. The current code uses explicit longest-prefix matching, identity-based insertion/removal, connected-route add/remove around ifconfig, and a prefix-aware command argument.
The relevant regression tests live in net/src/test/org/jnode/net/ipv4/config/impl/RouteTest.java (13 tests) and net/src/test/org/jnode/net/syntax/IPv4NetworkArgumentTest.java (12 tests). The IPv4 suite reported 68 passing tests after the routing fixes.
| Class / File | Role |
|---|---|
net/src/net/org/jnode/net/ipv4/IPv4RoutingTable.java |
Entry storage, duplicate replacement, search() with host → LPM → default-gateway fallback |
net/src/net/org/jnode/net/ipv4/IPv4Route.java |
Route value object, isUp/isHost/isNetwork/isGateway, equals/hashCode, use count |
net/src/net/org/jnode/net/ipv4/IPv4Address.java |
Address arithmetic plus prefixLength() and fromPrefixLength(int)
|
net/src/net/org/jnode/net/ipv4/config/impl/Route.java |
Normalizes a target to target & netmask, resolves a device, adds to the table |
net/src/net/org/jnode/net/ipv4/config/impl/Ifconfig.java |
Adds the connected route for a configured address and removes the old one on re-addressing |
net/src/net/org/jnode/net/syntax/IPv4NetworkArgument.java |
Parses a.b.c.d, a.b.c.d/N, a.b.c.d/netmask, and default
|
cli/src/commands/org/jnode/command/net/RouteCommand.java |
User-facing route command |
cli/src/test/org/jnode/test/cli/RouteSyntaxTest.java |
Loads the real descriptor and pins both argument orders plus rejections |
IPv4Route.equals() compares destination, subnet mask, gateway and device. Flags and the use count are deliberately excluded:
destination.equals(other.destination)
&& subnetmask.equals(other.subnetmask)
&& (gateway == null) == (other.gateway == null)
&& (gateway == null || gateway.equals(other.gateway))
&& device == other.deviceThe device comparison is identity (==), because the Device object is the table's handle to the interface. hashCode() mirrors the same fields and uses System.identityHashCode(device).
This makes IPv4RoutingTable.remove(route) a value operation: an equal route can be removed even if it was not the exact object originally inserted.
IPv4RoutingTable.add() removes an existing route only when destination, prefix, gateway presence/value and device all match, then appends the new entry:
for (IPv4Route r : entries.toArray(new IPv4Route[0])) {
if (r.getDestination().equals(entry.getDestination())
&& r.getSubnetmask().equals(entry.getSubnetmask())
&& /* same gateway, null-safe */
&& r.getDevice() == entry.getDevice()) {
entries.remove(r);
break;
}
}
entries.add(entry);The subnet mask is part of the key. 10.0.0.0/8 and 10.0.0.0/16 via the same gateway and device are different routes and can coexist — which is exactly what longest-prefix matching needs. Earlier code replaced by destination alone, silently discarding the more-specific route.
Route.addRoute() normalizes the destination before insertion:
if (netmask == null) {
netmask = target.getDefaultSubnetmask();
subnet = target; // classful route keeps the typed address
} else {
subnet = target.and(netmask); // explicit mask is applied
}
rt.add(new IPv4Route(subnet, netmask, gateway, device));IPv4RoutingTable.search(destination) has three ordered tiers:
-
Host routes — first
isHost() && isUp()route whose destination equals the target exactly. -
Network routes — every
isNetwork() && isUp()route whosedestination.matches(target, subnetmask)succeeds. The route with the highestsubnetmask.prefixLength()wins. On an equal prefix length, a gateway route beats a direct route. -
Default gateway — the first
isGateway() && isUp()route.
NoRouteToHostException is thrown only after all three tiers fail. The whole search is wrapped in a retry loop because entries is a Vector and can be structurally modified by another thread; a ConcurrentModificationException restarts the scan instead of escaping to the caller.
IPv4Route.isHost() handles three cases that a naive "destination has host bits set" test gets wrong:
return (subnetmask.prefixLength() == 32) ||
(destination.isHost() && !destination.equals(destination.and(subnetmask)));- A
/32mask is always a host route. - A destination with host bits set is a host route only when masking it actually changes the address.
- A classful network address with a zero fourth octet (for example
10.0.9.0typed with the default/8) stays a network route even though the second octet has bits set for that mask. - A masked subnet (
10.0.0.0/25) is already normalized, so it stays a network route.
Without the fourth-octet exception, classful class A/B routes fell into the host-route tier and disappeared from longest-prefix matching.
Ifconfig.setDefault(device, address, netmask) keeps the table consistent with the interface:
- If an address was already configured, compute its subnet and call
removeConnectedRoute(device, previousAddress, previousNetmask). - Add the new address to the device's
IPv4ProtocolAddressInfoand make it the default. - Call
addConnectedRoute(device, address, netmask).
addConnectedRoute() computes subnet = address.and(netmask), refuses to install a route for the any-address (subnet.isAny()), and inserts new IPv4Route(subnet, netmask, null, device) — an on-link route with no gateway, always flagged up.
The matching removeConnectedRoute() skips null address/mask and removes the exact route. This closes the bug where re-addressing an interface left the old on-link subnet reachable after the address had changed.
IPv4NetworkArgument accepts:
| Form | Result |
|---|---|
a.b.c.d |
address with null mask — use the classful default |
a.b.c.d/N |
address plus IPv4Address.fromPrefixLength(N)
|
a.b.c.d/dotted.mask |
address plus the literal mask, rejected if non-contiguous |
default |
IPv4Address.ANY with null mask |
Address octets are parsed locally with 0 <= v < 256. It does not delegate to new IPv4Address(String), whose unchecked (byte) cast silently turned 10.0.2.300 into 10.0.2.44. Bad octets and wrong component counts are syntax errors instead.
IPv4Address.prefixLength() simply popcounts set bits. That is only a meaningful prefix for a contiguous mask, so the dotted-mask path verifies round-tripping through fromPrefixLength():
if (!IPv4Address.fromPrefixLength(mask.prefixLength()).equals(mask)) {
throw new CommandSyntaxException("non-contiguous IPv4 subnetmask '" + text + "'");
}Without that check, 255.255.0.255 would report prefix length 24 and compete at the wrong specificity in the LPM search.
cli/descriptors/org.jnode.command.net.xml accepts both argument orders:
route <target> <device> [gateway]
route <target> <gateway> <device>
The second order is the BSD/route(8) form and is the one needed to install a static route through a gateway. Before the grammar used an <alternatives> of two <sequence> branches, <target> <gateway> <device> failed with Command syntax error(s): unknown device: <gateway>. The binding layer was not at fault; the descriptor simply did not describe the order.
RouteSyntaxTest loads the real plugin descriptor and pins both accepted orders and the rejected forms. cli/build-tests.xml's all-junit target was enabled in the same change.
Assume interface eth0 is configured as 192.168.1.10/24 with default gateway 192.168.1.1.
| Action | Table effect |
|---|---|
ifconfig eth0 192.168.1.10/24 |
Adds connected 192.168.1.0/24 -> eth0 (no gateway) |
ifconfig eth0 192.168.1.10/25 |
Removes 192.168.1.0/24, adds connected 192.168.1.0/25
|
route 10.0.0.0/8 192.168.1.1 eth0 |
Adds 10.0.0.0/8 -> 192.168.1.1 via eth0
|
route 10.1.0.0/16 192.168.1.1 eth0 |
Adds a second route; does not replace the /8
|
route 10.1.2.3/32 192.168.1.1 eth0 |
Adds a host route selected before any network route |
route default 192.168.1.1 eth0 |
Adds the default gateway fallback |
A packet to 10.1.2.3 then matches the /32 host route. A packet to 10.1.99.1 has no host route, matches both 10.0.0.0/8 and 10.1.0.0/16, and selects the /16 because 16 > 8. A packet to 8.8.8.8 matches no network route and falls through to the default gateway. A packet to 192.168.1.20 matches the connected /25 (or /24 before the re-addressing) directly.
The equivalent Java calls are:
rt.add(new IPv4Route(new IPv4Address("10.0.0.0"),
new IPv4Address("255.0.0.0"),
new IPv4Address("192.168.1.1"), eth0));Route.addRoute() is preferred in command code because it normalizes 10.1.2.3/24 to 10.1.2.0/24 and resolves the device name; constructing IPv4Route directly assumes the caller has already masked the destination.
RouteTest covers insertion, duplicate replacement, removal by value, host/network classification, LPM ordering, gateway tie-break, default fallback and connected-route add/remove. IPv4NetworkArgumentTest covers the four target forms, octet bounds, component counts, and non-contiguous masks. RouteSyntaxTest is the black-box guard for the command descriptor; it is the test that fails first when an argument order is edited away.
When a route lookup misbehaves, check in this order:
- Was the destination masked? A route inserted as
10.1.2.3/24without normalization never matches10.1.2.0. - Is the mask contiguous? A non-contiguous dotted mask is rejected by the syntax layer but can still reach the table through direct
IPv4Routeconstruction. - Is the interface still the same
Deviceinstance? A replaced device object makes the old connected route unreachable by identity. - Did re-addressing run? If the old subnet still matches,
Ifconfig.setDefault()was bypassed.
-
A route is identified by the full tuple, not by destination alone. Same destination with a different mask is a different route. Do not "simplify"
add()back to a destination-only lookup. -
remove()is value-based;add()is tuple-based. An equal route can be removed even if a different instance was inserted, but replacement requires the same four fields. Keep both paths consistent when adding a new field. -
Device identity is intentional. Two
Deviceinstances representing the same hardware do not compare equal for routing; routes are bound to the object the interface registered. - LPM is a linear scan. The table is small and the code prioritizes clarity over a trie. A very large routing table would scan every network route per packet.
-
prefixLength()does not validate contiguity. It is a popcount. Any caller accepting a dotted mask must round-trip it throughfromPrefixLength()first. -
Prefix 0 produces the all-zero mask.
fromPrefixLength(0)is valid and yields0.0.0.0; it is not rejected as an invalid prefix. -
addConnectedRoute()skips the any-address subnet. Configuring0.0.0.0does not create an on-link route; the default route is a separate, explicit gateway route. -
Re-addressing must remove first.
Ifconfig.setDefault()tracks the previous default address and mask precisely so the old on-link route does not survive the change. -
search()retries onConcurrentModificationException. The table is mutated by configuration and read by the packet path, so a failed traversal is retried rather than propagated. - Gateway preference only breaks equal-prefix ties. It never beats a longer prefix; a more-specific direct route still wins over a shorter gateway route.
-
Classful addresses are special-cased.
10.0.9.0with the default/8is a network route, not a host route, even though its second octet has host bits set for that mask.
- IPv4-Protocol — Parent hub: header parsing, fragment reassembly, outbound transmission
- Network-Layers — How IPv4 sits above the link layer and below TCP/UDP
- Network-Stack — Overall network architecture
- Ethernet-Driver — Link-layer devices used by routes
- ARP — Next-hop MAC resolution for a selected route
- DNS-Resolver — Hostname-to-address lookups that feed route selection
-
Shell-Commands — The
routeandifconfigcommands in context