Linux Routing Table Mystery: Why Table IDs Beyond 255 Work and Their Practical Limits


2 views

When working with Linux routing tables, the documentation suggests table IDs should be limited to 0-255 range with reserved values for special tables:

# Default table mappings
255 local
254 main
253 default
0   unspec

This would theoretically leave us 1-252 for custom tables. However, empirical testing reveals something curious - we can use much larger numbers.

Here's what happens when we push beyond the documented limits:

# Works despite being beyond 255
ip route add 192.168.1.0/24 dev eth0 table 1000

# Still works at very high numbers
ip route add 10.0.0.0/8 via 192.168.1.1 table 999999

# Breaks at 2^31 (2147483648)
ip route show table 2147483648 | grep -q "local table"

The behavior stems from how Linux kernel handles table IDs:

// Simplified from kernel source
struct fib_table *fib_get_table(struct net *net, u32 id)
{
    if (id == RT_TABLE_LOCAL)
        return local_table;
    if (id == RT_TABLE_MAIN)
        return main_table;
    
    // No range checking here!
    return fib_new_table(net, id);
}

The key insight: table IDs are stored as unsigned 32-bit integers, but when compared with reserved tables (like local=255), they're cast to signed integers.

While you can use very large table IDs, you should:

  • Stick to 1-252 for maximum compatibility
  • Avoid numbers near 2^31 (2147483648) due to integer overflow
  • Register custom names in /etc/iproute2/rt_tables for readability

Example of proper table registration:

# /etc/iproute2/rt_tables
100    vpn_tunnel
200    backup_route

There are valid use cases for high-numbered tables:

# Temporary tables for automated scripts
ip route flush table 5000
ip route add default via 10.0.0.1 table 5000
ip rule add fwmark 0x1 table 5000

Just remember these tables won't persist across reboots unless properly registered.


When working with custom routing tables in Linux, the documentation suggests that valid table IDs should be between 0-255, with reserved names defined in /etc/iproute2/rt_tables:

255 local
254 main
253 default
0   unspec

This would theoretically leave IDs 1-252 available for custom tables. However, empirical testing reveals more complex behavior.

While attempting to use undefined table names fails predictably:

$ ip route show table kermit
Error: argument "kermit" is wrong: table id value is invalid

The system actually accepts much higher numeric IDs:

$ ip route show table 1000
$ ip route add 10.10.10.0/24 dev eth0 table 1000
$ ip route show table 1000
10.10.10.0/24 dev eth0 scope link

At extreme values, we observe an interesting overflow behavior:

$ ip route show table 2147483647  # Works (2^31 - 1)
$ ip route show table 2147483648  # Falls back to table 255 (local)

This behavior stems from the kernel's table ID handling:

  1. Table IDs are stored as signed 32-bit integers
  2. The maximum positive value is 2^31 - 1 (2147483647)
  3. Higher values wrap around due to integer overflow
  4. The kernel uses a simple modulus operation internally

While you can use high-numbered tables:

  • Stick to 1-252 for maximum compatibility
  • Document any high-numbered tables thoroughly
  • Be aware of potential collision with future kernel versions
  • Consider using named tables in rt_tables for clarity

For production systems, the cleanest approach is:

# Add to /etc/iproute2/rt_tables
100 mycustom

# Then use normally
ip route add 192.168.1.0/24 via 10.0.0.1 table mycustom

This method is both readable and future-proof.