High-performance Python bindings for the liblpm C library, providing fast longest prefix match (LPM) routing table operations for both IPv4 and IPv6.
- High Performance: Direct C bindings via Cython with minimal overhead
- IPv4 DIR-24-8: Optimized IPv4 lookups using DPDK-style algorithm
- IPv6 Wide Stride: Efficient IPv6 lookups with 16-bit first-level stride
- Batch Operations: Reduced overhead through batch lookup operations
- Pythonic API: Clean interface using
ipaddressmodule types - Type Hints: Full mypy support for IDE integration
- Context Manager: Automatic resource cleanup with
withstatement - Multiple Algorithms: Choose between performance and memory efficiency
Choose the installation method that best fits your environment:
pip install liblpmNative distribution packages are available for common Linux distributions:
Important: Distribution packages are built for specific Python versions. The package must match your system's Python version:
- Ubuntu 22.04: Python 3.10
- Ubuntu 24.04: Python 3.12
- Debian 12: Python 3.11
- Debian 13: Python 3.13
- Fedora 41: Python 3.13
If you have a different Python version, use PyPI installation instead (pip install liblpm).
# Download the appropriate package for your distribution:
# Ubuntu 22.04, Ubuntu 24.04, or Debian 12
# Install
sudo dpkg -i python3-liblpm_2.0.0-1_amd64.deb
# Or install with dependencies
sudo apt install ./python3-liblpm_2.0.0-1_amd64.debNote: Rocky Linux 9 ships with Python 3.9, which does not meet the minimum Python 3.10 requirement. Use Fedora or install from PyPI instead.
# Download the appropriate package for your distribution:
# Fedora 41 (Rocky Linux 9 not supported due to Python 3.9)
# Install
sudo rpm -ivh python3-liblpm-2.0.0-1.fc41.x86_64.rpm
# Or using dnf
sudo dnf install python3-liblpm-2.0.0-1.fc41.x86_64.rpmEnsure liblpm C library is installed first:
# Build and install liblpm C library
git clone --recursive https://github.com/MuriloChianfa/liblpm.git
cd liblpm
mkdir build && cd build
cmake ..
make -j$(nproc)
sudo make install
sudo ldconfig
# Install Python bindings
cd ../bindings/python
pip install .# Install with development dependencies
pip install -e ".[dev]"After installation, verify the package is working:
# Check if module can be imported
python3 -c "import liblpm; print('liblpm version:', liblpm.__version__)"
# Verify functionality
python3 -c "
from liblpm import LpmTableIPv4
from ipaddress import IPv4Network, IPv4Address
with LpmTableIPv4() as table:
table.insert(IPv4Network('10.0.0.0/8'), 1)
result = table.lookup(IPv4Address('10.1.2.3'))
print('Lookup result:', result)
assert result == 1, 'Lookup failed'
print('✓ Verification successful')
"You should see:
liblpm version: 2.0.0
Lookup result: 1
✓ Verification successful
from ipaddress import IPv4Address, IPv4Network
from liblpm import LpmTableIPv4
# Create IPv4 routing table using context manager
with LpmTableIPv4() as table:
# Insert routes
table.insert(IPv4Network('192.168.0.0/16'), 100)
table.insert(IPv4Network('192.168.1.0/24'), 101)
table.insert('10.0.0.0/8', 200) # String format also works
# Lookup - returns most specific match
next_hop = table.lookup(IPv4Address('192.168.1.1'))
print(f"Next hop: {next_hop}") # Output: Next hop: 101
# No match returns None
result = table.lookup(IPv4Address('8.8.8.8'))
print(f"Result: {result}") # Output: Result: Nonefrom liblpm import LpmTableIPv4, LpmTableIPv6, Algorithm
# IPv4 table with DIR-24-8 optimization (recommended)
table = LpmTableIPv4()
table = LpmTableIPv4(Algorithm.DIR24)
# IPv4 table with 8-bit stride (memory efficient)
table = LpmTableIPv4(Algorithm.STRIDE8)
# IPv6 table with wide stride optimization (recommended)
table = LpmTableIPv6()
table = LpmTableIPv6(Algorithm.WIDE16)
# IPv6 table with 8-bit stride (memory efficient)
table = LpmTableIPv6(Algorithm.STRIDE8)from ipaddress import IPv4Address, IPv4Network
with LpmTableIPv4() as table:
# Insert a route
table.insert(IPv4Network('192.168.0.0/16'), 100)
table.insert('10.0.0.0/8', 200) # String also works
# Delete a route
table.delete(IPv4Network('192.168.0.0/16'))
# Lookup an address
next_hop = table.lookup(IPv4Address('10.1.1.1'))
next_hop = table.lookup('10.1.1.1') # String also works
# Check if match found
if next_hop is not None:
print(f"Found: {next_hop}")
# Batch lookup (more efficient for multiple addresses)
addrs = [IPv4Address('10.1.1.1'), IPv4Address('10.2.2.2')]
results = table.lookup_batch(addrs)from ipaddress import IPv6Address, IPv6Network
from liblpm import LpmTableIPv6
with LpmTableIPv6() as table:
# Insert routes
table.insert(IPv6Network('2001:db8::/32'), 100)
table.insert(IPv6Network('2001:db8:1::/48'), 101)
# Lookup
next_hop = table.lookup(IPv6Address('2001:db8:1::1'))
print(f"Next hop: {next_hop}") # Output: Next hop: 101from liblpm import (
LpmTableIPv4,
LpmError,
LpmInsertError,
LpmDeleteError,
LpmClosedError,
LpmInvalidPrefixError,
)
try:
with LpmTableIPv4() as table:
table.insert('invalid', 100)
except LpmInvalidPrefixError as e:
print(f"Invalid prefix: {e}")
# Operations on closed table
table = LpmTableIPv4()
table.close()
try:
table.lookup('10.0.0.1')
except LpmClosedError:
print("Table is closed")with LpmTableIPv4() as table:
table.insert('192.168.0.0/16', 100)
table.insert('10.0.0.0/8', 200)
print(f"Algorithm: {table.algorithm}")
print(f"Prefixes: {table.num_prefixes}")
print(f"Nodes: {table.num_nodes}")
print(f"Closed: {table.closed}")For multiple lookups, use lookup_batch() to reduce Python/C boundary crossing overhead:
# More efficient than individual lookups
addrs = [IPv4Address(f'{i}.0.0.1') for i in range(10000)]
results = table.lookup_batch(addrs)| Algorithm | Best For | Memory | Lookup Speed |
|---|---|---|---|
| DIR24 (IPv4) | General IPv4 routing | ~64MB | Fastest |
| Stride8 (IPv4) | Memory-constrained | Low | Fast |
| Wide16 (IPv6) | /48 prefix allocations | Medium | Fastest |
| Stride8 (IPv6) | Sparse prefix sets | Low | Fast |
Run benchmarks with:
pytest benchmarks/ --benchmark-only -vExpected performance (varies by hardware):
- IPv4 single lookup: ~50-100ns
- IPv4 batch lookup: ~20-50ns per address
- IPv6 single lookup: ~80-150ns
- IPv6 batch lookup: ~40-80ns per address
LpmTable classes are not thread-safe. For concurrent access, use external synchronization:
import threading
from liblpm import LpmTableIPv4
lock = threading.RLock()
table = LpmTableIPv4()
def lookup_thread(addr):
with lock:
return table.lookup(addr)For read-heavy workloads, consider threading.RLock which allows multiple concurrent readers.
The package includes type stubs for full mypy support:
from ipaddress import IPv4Address, IPv4Network
from liblpm import LpmTableIPv4
def find_route(table: LpmTableIPv4, addr: IPv4Address) -> int | None:
return table.lookup(addr)Run mypy:
mypy your_code.pySee the examples/ directory:
basic_example.py- Fundamental operationsbatch_lookup.py- Efficient batch lookupscontext_manager.py- Resource management patternsalgorithms.py- Algorithm comparison
Run examples:
python examples/basic_example.py- Python 3.10+
- Cython 3.0+
- CMake 3.16+
- GCC or Clang
- liblpm installed
# From bindings/python directory
pip install build
python -m build
# Or using pip
pip install .# Install in development mode
pip install -e ".[dev]"
# Run tests
pytest tests/ -v
# Run benchmarks
pytest benchmarks/ --benchmark-only
# Type checking
mypy src/liblpm/
# Format code
black src/ tests/ examples/
ruff check src/ tests/ examples/| Platform | Status |
|---|---|
| Linux x86_64 | Fully supported |
| Linux aarch64 | Supported |
| macOS x86_64 | Supported |
| macOS arm64 | Supported |
| Windows | Not supported (liblpm is Unix-focused) |
- Python 3.10 or later
- liblpm 2.0.0 or later
- Linux or macOS
Contributions welcome! Please ensure:
- All tests pass:
pytest tests/ - Code is formatted:
black . && ruff check . - Type hints are correct:
mypy src/liblpm/ - Documentation is updated
Same as liblpm: Boost Software License 1.0
- liblpm by Murilo Chianfa
- Python bindings by Murilo Chianfa