net_route_info.py

RTNetlink Routing Table Query Module with C Library via CFFI

Overview: A high-performance Python module for querying Linux routing tables using the RTNetlink protocol. This module provides comprehensive access to kernel routing information for both IPv4 and IPv6, including support for multiple routing tables, multipath routes, and detailed route metadata.

Table of Contents

Architecture & Design

Design Philosophy

The module is designed around a hybrid architecture that combines the performance of C with the convenience of Python:

Architecture Layers

Layer 1: Kernel Interface

Direct communication with Linux kernel via RTNetlink sockets using RTM_GETROUTE messages

Layer 2: C Implementation

Handles socket management, message construction, response parsing, and attribute extraction

Layer 3: CFFI Bridge

Runtime compilation and type conversion between C structures and Python objects

Layer 4: Python API

Context manager interface, type conversion, and user-friendly data structures

Data Flow

┌─────────────────────────────────────────────┐
│  Python Application Code                   │
└──────────────────┬──────────────────────────┘
                   │ get_routes()
                   ▼
┌─────────────────────────────────────────────┐
│  RoutingTableQuery Class                   │
│  - Socket management (context manager)     │
│  - Type conversion (Python ↔ C)            │
└──────────────────┬──────────────────────────┘
                   │ lib.nl_send_getroute()
                   ▼
┌─────────────────────────────────────────────┐
│  C Library (via CFFI)                      │
│  - nl_create_socket()                      │
│  - nl_send_getroute()                      │
│  - nl_receive_response()                   │
│  - nl_parse_routes()                       │
└──────────────────┬──────────────────────────┘
                   │ RTNetlink Protocol
                   ▼
┌─────────────────────────────────────────────┐
│  Linux Kernel Routing Subsystem            │
│  - Route lookup and filtering              │
│  - RTM_NEWROUTE messages                   │
└─────────────────────────────────────────────┘

Key Features

✓ Dual Stack Support

  • Full IPv4 routing tables
  • Full IPv6 routing tables
  • Mixed query support

✓ Multiple Tables

  • Main routing table
  • Local routing table
  • Custom routing tables (0-255+)

✓ Route Types

  • UNICAST (normal routes)
  • LOCAL (interface addresses)
  • BROADCAST/MULTICAST
  • UNREACHABLE/PROHIBIT/BLACKHOLE

✓ Advanced Features

  • Multipath routes (ECMP)
  • Route metrics and preferences
  • Routing protocols (DHCP, BIRD, etc.)
  • Cache information

Route Metadata Extracted

Category Fields Description
Addressing dst, src, gateway, prefsrc Destination network, source address, next-hop gateway, preferred source
Interface dev, dev_index Output interface name and index
Classification family, type, scope, protocol IPv4/IPv6, route type, scope (link/host/universe), protocol that installed route
Routing Policy table, metric, tos, flags Routing table ID, preference/metric, type of service, route flags
Multipath multipath[] Array of nexthop objects for ECMP routes
Cache Info cacheinfo{} Cache statistics (clntref, last_use, expires, error, used)

Requirements & Installation

System Requirements

Platform: Linux 2.6+

This module requires Linux with RTNetlink support. It will not work on Windows, macOS, or other Unix systems.

Python Requirements

Requirement Version Notes
Python ≥ 3.8 Required for type hints and modern syntax
cffi ≥ 1.0.0 Required for C library integration
setuptools Any version Required for Python 3.12+ due to distutils removal

Installation

# Install dependencies
pip install cffi setuptools

# For Python 3.12+, setuptools is mandatory
pip install setuptools  # if not already installed
⚠ Python 3.12+ Note: Python 3.12 removed the distutils module from the standard library. CFFI's ffi.verify() method requires setuptools on Python 3.12+ to provide the compilation infrastructure that distutils previously offered. The module will automatically check for setuptools availability at import time.

Permissions

⚠ Root/CAP_NET_ADMIN Required: Querying routing tables via RTNetlink requires either:

Usage Examples

Basic Usage: Query All Routes

from net_route_info import RoutingTableQuery

# Using context manager (recommended)
with RoutingTableQuery() as query:
    routes = query.get_routes()
    
    for route in routes:
        print(f"Destination: {route['dst']}")
        if 'gateway' in route:
            print(f"  via {route['gateway']}")
        if 'dev' in route:
            print(f"  dev {route['dev']}")
        print(f"  protocol: {route['protocol']}")
        print()

IPv4-Only Routes

with RoutingTableQuery() as query:
    ipv4_routes = query.get_routes(family='ipv4')
    
    # Filter for default route
    default_routes = [r for r in ipv4_routes if r['dst'] == '0.0.0.0/0']
    
    for route in default_routes:
        print(f"Default gateway: {route.get('gateway', 'N/A')}")

IPv6-Only Routes

with RoutingTableQuery() as query:
    ipv6_routes = query.get_routes(family='ipv6')
    
    # Filter for link-local routes
    link_local = [r for r in ipv6_routes 
                  if r['dst'].startswith('fe80:')]
    
    print(f"Found {len(link_local)} link-local routes")

Filtering by Routing Table

with RoutingTableQuery() as query:
    all_routes = query.get_routes()
    
    # Main table routes only
    main_routes = [r for r in all_routes if r['table'] == 'MAIN']
    
    # Local table routes only
    local_routes = [r for r in all_routes if r['table'] == 'LOCAL']
    
    print(f"Main table: {len(main_routes)} routes")
    print(f"Local table: {len(local_routes)} routes")

Analyzing Multipath Routes

with RoutingTableQuery() as query:
    routes = query.get_routes()
    
    # Find ECMP routes
    multipath_routes = [r for r in routes if 'multipath' in r]
    
    for route in multipath_routes:
        print(f"ECMP route to {route['dst']}:")
        for nexthop in route['multipath']:
            print(f"  - via {nexthop.get('gateway', 'direct')}")
            print(f"    dev {nexthop.get('dev', nexthop['dev_index'])}")
            print(f"    weight {nexthop['weight']}")

Disabling Unknown Attribute Tracking

# By default, unknown RTA_* attributes are tracked
# Disable for slightly better performance
with RoutingTableQuery(capture_unknown_attrs=False) as query:
    routes = query.get_routes()
    # 'unknown_rta_attrs' field will not be present

JSON Export

import json

with RoutingTableQuery() as query:
    routes = query.get_routes()
    
    # Pretty-print JSON
    print(json.dumps(routes, indent=2))
    
    # Save to file
    with open('routes.json', 'w') as f:
        json.dump(routes, f, indent=2)

API Reference

RoutingTableQuery Class

class RoutingTableQuery(capture_unknown_attrs: bool = True)

Main class for querying routing tables. Implements context manager protocol for safe socket management.

Constructor Parameters

Parameter Type Default Description
capture_unknown_attrs bool True Whether to track unknown RTA_* attributes in route entries

Methods

get_routes(family: Optional[str] = None) → List[Dict[str, Any]]

Query routing table entries with optional address family filtering.

Parameter Type Valid Values Description
family Optional[str] 'ipv4', 'ipv6', None Address family filter. None returns both IPv4 and IPv6

Returns: List of dictionaries, each representing a route entry with complete metadata.

Raises:

Context Manager Protocol

# Automatically manages socket lifecycle
with RoutingTableQuery() as query:
    routes = query.get_routes()
    # Socket is automatically closed on exit

# Manual socket management (not recommended)
query = RoutingTableQuery()
query.__enter__()  # Creates socket
try:
    routes = query.get_routes()
finally:
    query.__exit__(None, None, None)  # Closes socket

Route Information Structure

Each route entry is returned as a dictionary with the following structure:

Standard Fields

Field Type Always Present Description
family str 'ipv4' or 'ipv6'
type str Route type: UNICAST, LOCAL, BROADCAST, MULTICAST, UNREACHABLE, PROHIBIT, BLACKHOLE, etc.
protocol str Routing protocol: KERNEL, BOOT, STATIC, DHCP, BIRD, ZEBRA, BABEL, BGP, etc.
scope str Route scope: UNIVERSE, SITE, LINK, HOST, NOWHERE
table str/int Routing table: MAIN, LOCAL, DEFAULT, or numeric ID
dst str Destination network in CIDR notation (e.g., '192.168.1.0/24', '0.0.0.0/0', 'fe80::/64')
dst_len int Destination prefix length (0-32 for IPv4, 0-128 for IPv6)
flags int Route flags (bitfield)

Optional Fields

Field Type Description
gateway str Next-hop gateway IP address
dev str Output interface name (e.g., 'eth0', 'wlan0')
dev_index int Output interface index
metric int Route priority/preference (lower = preferred)
src str Source address/network for policy routing (CIDR notation)
prefsrc str Preferred source address for outgoing packets
table_id int Numeric routing table ID (for tables > 255)
multipath List[Dict] Array of nexthop objects for ECMP routes
cacheinfo Dict Cache statistics (clntref, last_use, expires, error, used)
unknown_rta_attrs List[int] List of unknown RTA_* attribute numbers (if capture_unknown_attrs=True)
unknown_rta_attrs_decoded List[Dict] Decoded information about unknown attributes

Multipath Nexthop Structure

When multipath field is present, each nexthop has the following structure:

Field Type Description
dev_index int Output interface index
dev str Output interface name (if resolvable)
gateway str Next-hop gateway IP (optional)
weight int Relative weight for load balancing
flags int Nexthop flags

Example Route Entry

{
  "family": "ipv4",
  "type": "UNICAST",
  "protocol": "DHCP",
  "scope": "UNIVERSE",
  "table": "MAIN",
  "dst": "0.0.0.0/0",
  "dst_len": 0,
  "gateway": "192.168.1.1",
  "dev": "eth0",
  "dev_index": 2,
  "metric": 100,
  "prefsrc": "192.168.1.100",
  "flags": 0
}

Command-Line Interface

The module can be run directly as a script with various options:

Basic Usage

# Full JSON output of all routes
sudo python3 net_route_info.py

# Human-readable summary
sudo python3 net_route_info.py --summary

# IPv4 routes only
sudo python3 net_route_info.py --ipv4

# IPv6 routes only
sudo python3 net_route_info.py --ipv6

# Filter by routing table
sudo python3 net_route_info.py --table main
sudo python3 net_route_info.py --table local

# Disable unknown attribute tracking
sudo python3 net_route_info.py --no-unknown-attrs

# Combine options
sudo python3 net_route_info.py --ipv4 --table main --summary

Command-Line Options

Option Description
--summary Display human-readable summary instead of JSON
--ipv4 Show only IPv4 routes
--ipv6 Show only IPv6 routes
--table TABLE Filter by routing table name (main, local, etc.)
--no-unknown-attrs Disable tracking of unknown RTA_* attributes

Example Output (--summary mode)

======================================================================
ROUTING TABLE QUERY
======================================================================

Total routes: 15

Route entries:

  0.0.0.0/0                                via 192.168.1.1          dev eth0       metric 100 [DHCP]
  192.168.1.0/24                           dev eth0       [KERNEL]
  192.168.1.100/32                         dev eth0       [KERNEL]
  fe80::/64                                dev eth0       [KERNEL]
  ::1/128                                  dev lo         [KERNEL]

Technical Details

RTNetlink Protocol

The module communicates with the Linux kernel using the RTNetlink protocol, part of the Netlink socket family. RTNetlink provides a mechanism for querying and modifying routing tables, IP addresses, and network interfaces.

Message Flow

  1. Socket Creation: Opens a NETLINK_ROUTE socket
  2. Request Construction: Builds RTM_GETROUTE request with optional family filter
  3. Kernel Query: Sends request to kernel and waits for response
  4. Response Processing: Parses RTM_NEWROUTE messages
  5. Attribute Extraction: Extracts route attributes (RTA_DST, RTA_GATEWAY, etc.)
  6. Data Conversion: Converts C structures to Python dictionaries

CFFI Integration

The module uses CFFI's ffi.verify() mode, which compiles C code at runtime during first import:

Performance Note: First import compiles C code (~1-2 seconds). Subsequent imports use cached library (~0.1 seconds). Route queries execute in microseconds after socket setup.

Memory Management

Interface Name Resolution

The module uses the C library function if_indextoname() to convert interface indices to names. This is wrapped in the custom nl_get_ifname() function that also tries reading from /sys/class/net/ as a fallback.

Supported Route Attributes (RTA_*)

The module explicitly handles these RTNetlink route attributes:

Attribute Value Description
RTA_DST1Destination network
RTA_SRC2Source network (policy routing)
RTA_OIF4Output interface index
RTA_GATEWAY5Next-hop gateway
RTA_PRIORITY6Route metric/preference
RTA_PREFSRC7Preferred source address
RTA_MULTIPATH9Multipath nexthops (ECMP)
RTA_CACHEINFO12Cache statistics
RTA_TABLE15Routing table ID (>255)

Unknown attributes are tracked if capture_unknown_attrs=True.

Limitations

Error Handling

The module raises clear exceptions for common error conditions:

Exception Cause Solution
RuntimeError (Python version) Python < 3.8 Upgrade to Python 3.8+
RuntimeError (setuptools) Python 3.12+ without setuptools Install setuptools: pip install setuptools
RuntimeError (socket) Failed to create netlink socket Check kernel support and permissions
RuntimeError (send/receive) Communication failure with kernel Check system logs; possible kernel issue
PermissionError Insufficient privileges Run with sudo or add CAP_NET_ADMIN capability
ValueError Invalid family parameter Use 'ipv4', 'ipv6', or None

Comparison with Alternatives

Approach Pros Cons
net_route_info.py Direct kernel access, complete metadata, high performance, type-safe Compilation required, Linux-only, requires privileges
ip route show No coding required, human-readable Text parsing needed, limited programmatic use, incomplete metadata
pyroute2 Pure Python, comprehensive netlink support Slower, more complex API, larger dependency
/proc/net/route No privileges, simple text format IPv4 only, incomplete information, deprecated interface

Conclusion

net_route_info.py provides a high-performance, type-safe interface to Linux routing tables through direct kernel communication via RTNetlink. Its hybrid C/Python architecture delivers native performance while maintaining Python's ease of use. The module is ideal for network monitoring tools, diagnostic utilities, SDN controllers, and any application requiring programmatic access to routing information.

Module: net_route_info.py

Documentation Generated: 2025

Python Version: 3.8+

Platform: Linux (2.6+)