High-performance PHP extension for the liblpm C library, providing fast longest prefix match (LPM) routing table operations for both IPv4 and IPv6.
- High Performance: Native C extension with minimal overhead
- Dual API: Object-oriented classes and procedural functions
- IPv4 Optimizations: DIR-24-8 algorithm for 1-2 memory accesses per lookup
- IPv6 Support: Wide 16-bit stride optimized for common /48 allocations
- Batch Operations: Efficient batch lookups for high-throughput scenarios
- Algorithm Selection: Choose between DIR-24-8/Wide16 and 8-bit stride
- Memory Safe: Proper PHP reference counting and resource cleanup
- PECL Ready: Standard PHP extension packaging for easy distribution
- PHP 8.1 or later
- liblpm 1.2.0 or later
- Linux or macOS (tested on Linux)
- GCC or Clang compiler
- PHP development headers (
php-devpackage)
Choose the installation method that best fits your environment:
PECL automatically compiles the extension for your exact PHP version and platform:
pecl install liblpmThen add to your php.ini:
extension=liblpm.so# Ensure liblpm is installed first
cd /path/to/liblpm
mkdir build && cd build
cmake ..
make -j$(nproc)
sudo make install
sudo ldconfig
# Build PHP extension
cd bindings/php
phpize
./configure --with-liblpm=/usr/local
make -j$(nproc)
sudo make install
# Enable extension
echo "extension=liblpm.so" | sudo tee /etc/php/8.3/cli/conf.d/99-liblpm.ini# From liblpm root directory
mkdir build && cd build
cmake -DBUILD_PHP_WRAPPER=ON ..
make php_wrapper
sudo make php_installAfter installation, verify the extension is loaded:
php -m | grep liblpm
php --ri liblpmYou should see:
liblpm
liblpm support => enabled
liblpm version => 1.0.0
<?php
// Create IPv4 routing table
$table = new LpmTableIPv4();
// Insert routes
$table->insert("192.168.0.0/16", 100);
$table->insert("10.0.0.0/8", 200);
$table->insert("0.0.0.0/0", 1); // Default route
// Lookup addresses
$nextHop = $table->lookup("192.168.1.1"); // Returns 100
$nextHop = $table->lookup("10.1.2.3"); // Returns 200
$nextHop = $table->lookup("8.8.8.8"); // Returns 1 (default)
// Batch lookup for high throughput
$addresses = ["192.168.1.1", "10.1.2.3", "8.8.8.8"];
$results = $table->lookupBatch($addresses); // [100, 200, 1]
// Delete routes
$table->delete("192.168.0.0/16");
// Clean up (optional - destructor handles this)
$table->close();<?php
// Create table
$table = lpm_create_ipv4();
// Insert routes
lpm_insert($table, "192.168.0.0/16", 100);
lpm_insert($table, "10.0.0.0/8", 200);
// Lookup
$nextHop = lpm_lookup($table, "192.168.1.1"); // Returns 100
// Batch lookup
$results = lpm_lookup_batch($table, ["192.168.1.1", "10.1.2.3"]);
// Delete
lpm_delete($table, "192.168.0.0/16");
// Get library version
echo lpm_version();
// Clean up
lpm_close($table);<?php
// Create IPv6 routing table
$table = new LpmTableIPv6();
// Insert routes
$table->insert("2001:db8::/32", 1000);
$table->insert("2001:db8:1::/48", 2000);
$table->insert("::/0", 1); // Default route
// Lookup
$nextHop = $table->lookup("2001:db8:1::1"); // Returns 2000 (most specific)
$table->close();<?php
// IPv4 with DIR-24-8 (default, fastest for most cases)
$table = new LpmTableIPv4(LpmTableIPv4::ALGO_DIR24);
// IPv4 with 8-bit stride (lower memory for sparse tables)
$table = new LpmTableIPv4(LpmTableIPv4::ALGO_8STRIDE);
// IPv6 with Wide 16-bit stride (default, optimized for /48 allocations)
$table6 = new LpmTableIPv6(LpmTableIPv6::ALGO_WIDE16);
// IPv6 with 8-bit stride
$table6 = new LpmTableIPv6(LpmTableIPv6::ALGO_8STRIDE);
// Procedural API with algorithm selection
$table = lpm_create_ipv4(LPM_ALGO_IPV4_DIR24);
$table6 = lpm_create_ipv6(LPM_ALGO_IPV6_WIDE16);class LpmTableIPv4 {
const ALGO_DIR24 = 0; // DIR-24-8 algorithm (default)
const ALGO_8STRIDE = 1; // 8-bit stride algorithm
public function __construct(?int $algorithm = null);
public function insert(string $prefix, int $nextHop): bool;
public function delete(string $prefix): bool;
public function lookup(string $addr): int|false;
public function lookupBatch(array $addresses): array;
public function size(): int;
public function close(): void;
}class LpmTableIPv6 {
const ALGO_WIDE16 = 0; // Wide 16-bit stride (default)
const ALGO_8STRIDE = 1; // 8-bit stride algorithm
public function __construct(?int $algorithm = null);
public function insert(string $prefix, int $nextHop): bool;
public function delete(string $prefix): bool;
public function lookup(string $addr): int|false;
public function lookupBatch(array $addresses): array;
public function size(): int;
public function close(): void;
}class LpmException extends Exception {}
class LpmInvalidPrefixException extends LpmException {}
class LpmOperationException extends LpmException {}// Table creation
function lpm_create_ipv4(?int $algorithm = null): resource|false;
function lpm_create_ipv6(?int $algorithm = null): resource|false;
// Operations
function lpm_insert(resource $table, string $prefix, int $nextHop): bool;
function lpm_delete(resource $table, string $prefix): bool;
function lpm_lookup(resource $table, string $addr): int|false;
function lpm_lookup_batch(resource $table, array $addresses): array;
function lpm_size(resource $table): int;
function lpm_close(resource $table): void;
// Utility
function lpm_version(): string;const LPM_ALGO_IPV4_DIR24 = 0;
const LPM_ALGO_IPV4_8STRIDE = 1;
const LPM_ALGO_IPV6_WIDE16 = 0;
const LPM_ALGO_IPV6_8STRIDE = 1;For multiple lookups, batch operations are significantly faster:
// Slower: Individual lookups
foreach ($addresses as $addr) {
$results[] = $table->lookup($addr);
}
// Faster: Batch lookup
$results = $table->lookupBatch($addresses);- IPv4 DIR-24-8: Best for typical routing tables, 1-2 memory accesses
- IPv4 8-stride: Better memory efficiency for sparse tables
- IPv6 Wide16: Optimized for common /48 ISP allocations
- IPv6 8-stride: More memory efficient for sparse IPv6 tables
Creating tables has overhead. Reuse tables when possible:
// Less efficient: Create new table each time
function checkRoute($addr) {
$table = new LpmTableIPv4();
// ... insert routes ...
return $table->lookup($addr);
}
// More efficient: Reuse table
$table = new LpmTableIPv4();
// ... insert routes once ...
function checkRoute($addr) use ($table) {
return $table->lookup($addr);
}- Table objects are not thread-safe by default
- For multi-threaded PHP (rare), use external synchronization
- Read-only lookups may be safe for concurrent reads if no modifications occur
The extension throws exceptions for errors:
try {
$table = new LpmTableIPv4();
$table->insert("invalid-prefix", 100); // Throws LpmInvalidPrefixException
} catch (LpmInvalidPrefixException $e) {
echo "Invalid prefix: " . $e->getMessage();
} catch (LpmOperationException $e) {
echo "Operation failed: " . $e->getMessage();
}# Run tests
cd bindings/php
phpize
./configure --with-liblpm=/usr/local
make test
# Or with CMake
cd build
make php_testSee the examples/ directory:
basic_example.php- Basic OOP usagebatch_lookup.php- Batch operationsprocedural_example.php- Procedural APIipv6_example.php- IPv6 routing
Boost Software License 1.0 (same as liblpm)
- liblpm by Murilo Chianfa
- PHP bindings by Murilo Chianfa