Read Linux Routes Clearly with routel

routel turns the kernel's routing table into a compact, labelled view that is far easier to read than raw ip route output. You will filter to IPv4 or IPv6, pick a specific table, and learn to recognise when a strange result is your command rather than a network problem. Allow about ten minutes; these examples only read routing state and normally need no elevated privileges.

1. Check the installed command

This guide describes the Ubuntu package version iproute2 6.1.0-1ubuntu6.4. The installed manual page is dated 1 September 2021, and the script itself is a Python program that asks ip for JSON route data before formatting it nicely. Other package builds can differ, so check your own command first:

$ dpkg-query -W -f='${Package} ${Version}\n' iproute2
iproute2 6.1.0-1ubuntu6.4
$ command -v routel
/usr/bin/routel

On a non-Debian system, use its package manager or just run command -v routel. Missing entirely? Install iproute2 through your normal system-management process. Do not copy the version string above into an installation command: it is a host-specific observation, not an instruction.

2. List the default route view

Run routel with no arguments:

$ routel
Dst             Gateway         Prefsrc         Protocol Scope   Dev              Table
default         136.243.171.1                   static           enp0s31f6
172.17.0.0/16                   172.17.0.1      kernel   link    docker0
127.0.0.0/8                     127.0.0.1       kernel   host    lo               local

Your addresses, interfaces and route count will differ. The header is the useful constant. Dst is the destination prefix, Gateway the next hop where one exists, Prefsrc the preferred source address, and Protocol and Scope describe how the route was installed and where it applies. Dev is the outgoing interface and Table identifies the routing table.

Checkpoint: the no-argument form reads table 0, which is what the installed script passes to ip route list table. That is an implementation detail worth remembering, not a request to print every policy-routing table there is.

3. Separate IPv4 and IPv6 routes

Use -4 for IPv4 or -6 for IPv6; the long form is --family with inet or inet6:

$ routel -4 | head -5
Dst             Gateway         Prefsrc         Protocol Scope   Dev              Table
default         136.243.171.1                   static           enp0s31f6
172.17.0.0/16                   172.17.0.1      kernel   link    docker0
172.18.0.0/16                   172.18.0.1      kernel   link    br-64a4af5f7dec
$ routel --family inet6 | head -5
Dst                              Gateway                          Prefsrc                          Protocol Scope   Dev              Table
2a01:4f8:171:2009::/64                                                                             kernel           enp0s31f6
fe80::/64                                                                                          kernel           enp0s31f6

IPv6 gets wider columns so addresses stay readable. An empty gateway is normal for directly connected or local routes. Piping to head only shortens what you see on screen; it changes nothing in the kernel.

4. Inspect a particular routing table

Put a numeric table number first, then any raw route arguments. Table main is conventionally number 254 on Linux:

$ routel 254
Dst             Gateway         Prefsrc         Protocol Scope   Dev              Table
default         136.243.171.1                   static           enp0s31f6
172.17.0.0/16                   172.17.0.1      kernel   link    docker0

Reach for this when you are investigating policy routing and already know the table number: confirm it in your own routing policy first, do not assume. To pass route-list arguments after the table number, append them as ordinary ip route list arguments:

$ routel 254 match 192.0.2.10

The exact rows depend on the host. Need machine-readable data for automation? Call ip -j route list table 254 directly instead of parsing this presentation-oriented output.

5. Diagnose failures without changing routes

routel --help prints a short usage line and exits with status 64. That is expected for this script's help path, not a failure:

$ routel --help
Usage: /usr/bin/routel [tablenr [raw ip args...]]
$ printf 'exit status: %s\n' "$?"
exit status: 64

For a normal listing, check both the command status and the output:

$ routel -4 > /tmp/routel-ipv4.txt
$ status=$?
$ test "$status" -eq 0 && test -s /tmp/routel-ipv4.txt
$ printf 'status=%s, output=%s\n' "$status" "$(test -s /tmp/routel-ipv4.txt && echo present || echo empty)"
status=0, output=present

An invalid family gets passed straight to ip, so the error can be followed by a Python JSON parsing traceback. routel --family nope is not a valid way to inspect an unknown address family: stick to inet, inet6, -4 or -6.

6. Keep inspection separate from route changes

routel lists routes. It has no add, delete or replace operation of its own. The raw arguments go straight to ip route list, so every example here stays read-only. Do not turn an investigation into a change by swapping list for a write command in a copied pipeline: ip route del can interrupt connectivity and may be hard to recover remotely.

None of this needs sudo. If a restricted environment blocks route data, work out why first and use the least privilege your policy allows. Do not make a persistent routing change just to get a display command working.

Done means