Linux – How to retrieve the routing tables in JSON format (linux)

ipiproute2jsonlinuxroute

The ip command is able to return some of its results in JSON format, but it seems to be unable to format the routing table(s).

Here are some examples of what I mean.

List of all addresses: ip --json address show

[{ ... },{
    "ifindex": 2,
    "ifname": "eth0",
    "flags": ["BROADCAST","MULTICAST","UP","LOWER_UP"],
    "mtu": 1500,
    "qdisc": "fq_codel",
    "master": "lan0",
    "operstate": "UP",
    "group": "default",
    "txqlen": 1000,
    "link_type": "ether",
    "address": "44:89:3f:e9:a8:08",
    "broadcast": "ff:ff:ff:ff:ff:ff",
    "addr_info": []
},{ ... }]

Interface list: ip --json link show

[{ ... },{
    "ifindex": 2,
    "ifname": "eth0",
    "flags": ["BROADCAST","MULTICAST","UP","LOWER_UP"],
    "mtu": 1500,
    "qdisc": "fq_codel",
    "master": "lan0",
    "operstate": "UP",
    "linkmode": "DEFAULT",
    "group": "default",
    "txqlen": 1000,
    "link_type": "ether",
    "address": "44:89:3f:e9:a8:08",
    "broadcast": "ff:ff:ff:ff:ff:ff"
},{ ... }]

I was expecting to retrieve the route list in JSON as well, but I could only retrieve its plaintext version:
Route list: ip --json route list (it's still in plaintext)

1.1.1.2 via 192.168.255.11 dev lan0 table hopper src 192.168.254.1 metric 10
default via 10.19.1.4 dev wg0 metric 5
default via 192.168.255.11 dev lan0 metric 6
10.19.1.0/24 dev wg0 scope link
124.214.110.113 via 192.168.255.11 dev lan0 metric 4
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
172.18.0.0/16 dev br-fea43fdf31f1 proto kernel scope link src 172.18.0.1
172.19.0.0/16 dev br-6f9e681d15b0 proto kernel scope link src 172.19.0.1 linkdown
192.168.0.0/16 dev lan0 proto kernel scope link src 192.168.254.1
192.168.1.0/24 dev wan0 proto kernel scope link src 192.168.1.9
192.168.1.0/24 via 192.168.255.11 dev lan0 metric 3
broadcast 127.0.0.0 dev lo table local proto kernel scope link src 127.0.0.1
local 127.0.0.1 dev lo table local proto kernel scope host src 127.0.0.1
multicast 239.255.255.250/32 from 192.168.1.109/32 table default proto 17 unresolved

Since I couldn't find a solution anywhere, I'll answer my own question and leave here what I did.
Who knows, maybe someday I'll need it again, or maybe someone else will be having the same problem! 🙂

Best Answer

So, here's what I did.
I used the command jq to parse the result of ip route list table all using a regex, transforming the output to JSON.
Feel free to play around with jq, and click here to use an online sandbox already loaded with this solution.
This is the complete command:

ip route list table all | jq --raw-input --slurp 'split("\n") | map(capture("^(?:(?<broadcast>broadcast) ?)?(?:(?<local>local) ?)?(?:(?<multicast>multicast) ?)?(?: ?(?<network>.*?) )(?:from (?<from>\\S+) ?)?(?:via (?<via>\\S+) ?)?(?:dev (?<dev>\\S+) ?)?(?:table (?<table>\\S+) ?)?(?:proto (?<proto>\\S+) ?)?(?:scope (?<scope>\\S+) ?)?(?:src (?<src>\\S+) ?)?(?:metric (?<metric>\\d+) ?)?(?<linkdown>linkdown)?(?<unresolved>unresolved)?"; "g"))'

And this is the output:

[
  {
    "broadcast": null,
    "local": null,
    "multicast": null,
    "network": "1.1.1.2",
    "from": null,
    "via": "192.168.255.11",
    "dev": "lan0",
    "table": "hopper",
    "proto": null,
    "scope": null,
    "src": "192.168.254.1",
    "metric": "10",
    "linkdown": null,
    "unresolved": null
  },
  {
    "broadcast": null,
    "local": null,
    "multicast": null,
    "network": "default",
    "from": null,
    "via": "10.19.1.4",
    "dev": "wg0",
    "table": null,
    "proto": null,
    "scope": null,
    "src": null,
    "metric": "5",
    "linkdown": null,
    "unresolved": null
  },
  {
    "broadcast": null,
    "local": null,
    "multicast": null,
    "network": "default",
    "from": null,
    "via": "192.168.255.11",
    "dev": "lan0",
    "table": null,
    "proto": null,
    "scope": null,
    "src": null,
    "metric": "6",
    "linkdown": null,
    "unresolved": null
  },
  {
    "broadcast": null,
    "local": null,
    "multicast": null,
    "network": "10.19.1.0/24",
    "from": null,
    "via": null,
    "dev": "wg0",
    "table": null,
    "proto": null,
    "scope": "link",
    "src": null,
    "metric": null,
    "linkdown": null,
    "unresolved": null
  },
  {
    "broadcast": null,
    "local": null,
    "multicast": null,
    "network": "124.214.110.113",
    "from": null,
    "via": "192.168.255.11",
    "dev": "lan0",
    "table": null,
    "proto": null,
    "scope": null,
    "src": null,
    "metric": "4",
    "linkdown": null,
    "unresolved": null
  },
  {
    "broadcast": null,
    "local": null,
    "multicast": null,
    "network": "172.17.0.0/16",
    "from": null,
    "via": null,
    "dev": "docker0",
    "table": null,
    "proto": "kernel",
    "scope": "link",
    "src": "172.17.0.1",
    "metric": null,
    "linkdown": "linkdown",
    "unresolved": null
  },
  {
    "broadcast": null,
    "local": null,
    "multicast": null,
    "network": "172.18.0.0/16",
    "from": null,
    "via": null,
    "dev": "br-fea43fdf31f1",
    "table": null,
    "proto": "kernel",
    "scope": "link",
    "src": "172.18.0.1",
    "metric": null,
    "linkdown": null,
    "unresolved": null
  },
  {
    "broadcast": null,
    "local": null,
    "multicast": null,
    "network": "172.19.0.0/16",
    "from": null,
    "via": null,
    "dev": "br-6f9e681d15b0",
    "table": null,
    "proto": "kernel",
    "scope": "link",
    "src": "172.19.0.1",
    "metric": null,
    "linkdown": "linkdown",
    "unresolved": null
  },
  {
    "broadcast": null,
    "local": null,
    "multicast": null,
    "network": "192.168.0.0/16",
    "from": null,
    "via": null,
    "dev": "lan0",
    "table": null,
    "proto": "kernel",
    "scope": "link",
    "src": "192.168.254.1",
    "metric": null,
    "linkdown": null,
    "unresolved": null
  },
  {
    "broadcast": null,
    "local": null,
    "multicast": null,
    "network": "192.168.1.0/24",
    "from": null,
    "via": null,
    "dev": "wan0",
    "table": null,
    "proto": "kernel",
    "scope": "link",
    "src": "192.168.1.9",
    "metric": null,
    "linkdown": null,
    "unresolved": null
  },
  {
    "broadcast": null,
    "local": null,
    "multicast": null,
    "network": "192.168.1.0/24",
    "from": null,
    "via": "192.168.255.11",
    "dev": "lan0",
    "table": null,
    "proto": null,
    "scope": null,
    "src": null,
    "metric": "3",
    "linkdown": null,
    "unresolved": null
  },
  {
    "broadcast": "broadcast",
    "local": null,
    "multicast": null,
    "network": "127.0.0.0",
    "from": null,
    "via": null,
    "dev": "lo",
    "table": "local",
    "proto": "kernel",
    "scope": "link",
    "src": "127.0.0.1",
    "metric": null,
    "linkdown": null,
    "unresolved": null
  },
  {
    "broadcast": null,
    "local": "local",
    "multicast": null,
    "network": "127.0.0.1",
    "from": null,
    "via": null,
    "dev": "lo",
    "table": "local",
    "proto": "kernel",
    "scope": "host",
    "src": "127.0.0.1",
    "metric": null,
    "linkdown": null,
    "unresolved": null
  },
  {
    "broadcast": null,
    "local": null,
    "multicast": "multicast",
    "network": "239.255.255.250/32",
    "from": "192.168.1.109/32",
    "via": null,
    "dev": null,
    "table": "default",
    "proto": "17",
    "scope": null,
    "src": null,
    "metric": null,
    "linkdown": null,
    "unresolved": "unresolved"
  }
]

That's already enough for what I needed but, if you have some time to spare, there's still room for improvement: all the null values could be removed, the "metric" values could be transformed to numbers, and "broadcast", "local", "multicast", "linkdown" and "unresolved" could be booleans.