From fcd6b3a7a0b7d60bab90a92f19f68f91a4405828 Mon Sep 17 00:00:00 2001 From: MuriloChianfa Date: Thu, 29 Jan 2026 00:20:48 -0300 Subject: [PATCH] add perl xs bindings with cpan packaging support --- .github/workflows/ci.yml | 35 +- CMakeLists.txt | 45 + bindings/perl/LPM.bs | 0 bindings/perl/LPM.c | 857 +++++++++++++ bindings/perl/LPM.xs | 474 ++++++++ bindings/perl/MYMETA.json | 69 ++ bindings/perl/MYMETA.yml | 38 + bindings/perl/Makefile | 1138 ++++++++++++++++++ bindings/perl/Makefile.PL | 201 ++++ bindings/perl/README.md | 288 +++++ bindings/perl/blib/arch/.exists | 0 bindings/perl/blib/arch/auto/Net/LPM/.exists | 0 bindings/perl/blib/bin/.exists | 0 bindings/perl/blib/lib/Net/.exists | 0 bindings/perl/blib/lib/Net/LPM.pm | 451 +++++++ bindings/perl/blib/lib/auto/Net/LPM/.exists | 0 bindings/perl/blib/man1/.exists | 0 bindings/perl/blib/man3/.exists | 0 bindings/perl/blib/man3/Net::LPM.3pm | 349 ++++++ bindings/perl/blib/script/.exists | 0 bindings/perl/examples/basic_example.pl | 183 +++ bindings/perl/lib/Net/LPM.pm | 451 +++++++ bindings/perl/pm_to_blib | 0 bindings/perl/t/00-load.t | 32 + bindings/perl/t/01-ipv4-basic.t | 71 ++ bindings/perl/t/02-ipv6-basic.t | 65 + bindings/perl/t/03-batch.t | 87 ++ bindings/perl/t/04-memory.t | 55 + bindings/perl/t/05-errors.t | 110 ++ bindings/perl/typemap | 30 + docker/Dockerfile.perl | 134 +++ docker/README.md | 33 + scripts/docker-build.sh | 7 +- 33 files changed, 5200 insertions(+), 3 deletions(-) create mode 100644 bindings/perl/LPM.bs create mode 100644 bindings/perl/LPM.c create mode 100644 bindings/perl/LPM.xs create mode 100644 bindings/perl/MYMETA.json create mode 100644 bindings/perl/MYMETA.yml create mode 100644 bindings/perl/Makefile create mode 100644 bindings/perl/Makefile.PL create mode 100644 bindings/perl/README.md create mode 100644 bindings/perl/blib/arch/.exists create mode 100644 bindings/perl/blib/arch/auto/Net/LPM/.exists create mode 100644 bindings/perl/blib/bin/.exists create mode 100644 bindings/perl/blib/lib/Net/.exists create mode 100644 bindings/perl/blib/lib/Net/LPM.pm create mode 100644 bindings/perl/blib/lib/auto/Net/LPM/.exists create mode 100644 bindings/perl/blib/man1/.exists create mode 100644 bindings/perl/blib/man3/.exists create mode 100644 bindings/perl/blib/man3/Net::LPM.3pm create mode 100644 bindings/perl/blib/script/.exists create mode 100644 bindings/perl/examples/basic_example.pl create mode 100644 bindings/perl/lib/Net/LPM.pm create mode 100644 bindings/perl/pm_to_blib create mode 100644 bindings/perl/t/00-load.t create mode 100644 bindings/perl/t/01-ipv4-basic.t create mode 100644 bindings/perl/t/02-ipv6-basic.t create mode 100644 bindings/perl/t/03-batch.t create mode 100644 bindings/perl/t/04-memory.t create mode 100644 bindings/perl/t/05-errors.t create mode 100644 bindings/perl/typemap create mode 100644 docker/Dockerfile.perl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88c03ae..5f5b768 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,6 +115,35 @@ jobs: run: | docker run --rm liblpm-go:ci + # Perl bindings test + test-perl-bindings: + name: Test Perl Bindings + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Perl container + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile.perl + push: false + load: true + tags: liblpm-perl:ci + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Run Perl tests + run: | + docker run --rm liblpm-perl:ci + # Code quality checks code-quality: name: Code Quality @@ -167,7 +196,7 @@ jobs: ci-summary: name: CI Summary runs-on: ubuntu-latest - needs: [build-and-test, test-cpp-bindings, test-go-bindings, code-quality] + needs: [build-and-test, test-cpp-bindings, test-go-bindings, test-perl-bindings, code-quality] if: always() steps: @@ -177,12 +206,14 @@ jobs: echo "Build and test: ${{ needs.build-and-test.result }}" echo "C++ bindings: ${{ needs.test-cpp-bindings.result }}" echo "Go bindings: ${{ needs.test-go-bindings.result }}" + echo "Perl bindings: ${{ needs.test-perl-bindings.result }}" echo "Code quality: ${{ needs.code-quality.result }}" # Fail if any required job failed if [[ "${{ needs.build-and-test.result }}" == "failure" ]] || \ [[ "${{ needs.test-cpp-bindings.result }}" == "failure" ]] || \ - [[ "${{ needs.test-go-bindings.result }}" == "failure" ]]; then + [[ "${{ needs.test-go-bindings.result }}" == "failure" ]] || \ + [[ "${{ needs.test-perl-bindings.result }}" == "failure" ]]; then echo "One or more required jobs failed" exit 1 fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c5b7da..c57483a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ option(WITH_DPDK_BENCHMARK "Build DPDK comparison benchmark (requires DPDK)" OFF option(WITH_EXTERNAL_LPM_BENCHMARK "Build benchmarks with external LPM libraries (Docker only)" OFF) option(BUILD_GO_WRAPPER "Build Go wrapper and bindings" OFF) option(BUILD_CPP_WRAPPER "Build C++ wrapper and bindings" OFF) +option(BUILD_PERL_WRAPPER "Build Perl wrapper and bindings" OFF) # External LPM libraries directory (set by Docker builds) set(EXTERNAL_LPM_DIR "" CACHE PATH "Directory containing external LPM libraries") @@ -390,6 +391,45 @@ if(BUILD_GO_WRAPPER) endif() endif() +# Perl wrapper +if(BUILD_PERL_WRAPPER) + find_program(PERL_EXECUTABLE perl) + if(PERL_EXECUTABLE) + message(STATUS "Found Perl: ${PERL_EXECUTABLE}") + + # Custom target to configure Perl wrapper + add_custom_target(perl_configure + COMMAND ${PERL_EXECUTABLE} Makefile.PL + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bindings/perl + DEPENDS lpm + COMMENT "Configuring Perl wrapper" + ) + + # Custom target to build Perl wrapper + add_custom_target(perl_wrapper ALL + COMMAND make + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bindings/perl + DEPENDS perl_configure + COMMENT "Building Perl wrapper and bindings" + ) + + # Custom target to test Perl wrapper + add_custom_target(perl_test + COMMAND ${CMAKE_COMMAND} -E env "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}:$ENV{LD_LIBRARY_PATH}" make test + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bindings/perl + DEPENDS perl_wrapper + COMMENT "Testing Perl wrapper" + ) + + message(STATUS "Perl wrapper targets added:") + message(STATUS " make perl_wrapper - Build Perl wrapper") + message(STATUS " make perl_test - Run Perl tests") + else() + message(WARNING "Perl not found. Perl wrapper will not be built.") + message(WARNING "Install Perl to build the wrapper: sudo apt install perl") + endif() +endif() + # Print configuration summary message(STATUS "liblpm configuration:") message(STATUS " Version: ${PROJECT_VERSION}") @@ -435,6 +475,11 @@ if(BUILD_CPP_WRAPPER) else() message(STATUS " Build C++ wrapper: OFF") endif() +if(BUILD_PERL_WRAPPER AND PERL_EXECUTABLE) + message(STATUS " Build Perl wrapper: ON") +else() + message(STATUS " Build Perl wrapper: OFF") +endif() # ============================================================================ # CPack Configuration for .deb and .rpm packages diff --git a/bindings/perl/LPM.bs b/bindings/perl/LPM.bs new file mode 100644 index 0000000..e69de29 diff --git a/bindings/perl/LPM.c b/bindings/perl/LPM.c new file mode 100644 index 0000000..f29c671 --- /dev/null +++ b/bindings/perl/LPM.c @@ -0,0 +1,857 @@ +/* + * This file was generated automatically by ExtUtils::ParseXS version 3.51 from the + * contents of LPM.xs. Do not edit this file, edit LPM.xs instead. + * + * ANY CHANGES MADE HERE WILL BE LOST! + * + */ + +#line 1 "LPM.xs" +/* + * LPM.xs - XS bindings for liblpm + * + * High-performance Perl bindings for the liblpm C library. + * Provides IPv4 and IPv6 longest prefix match operations. + * + * Copyright (c) Murilo Chianfa + * Licensed under the Boost Software License 1.0 + */ + +#define PERL_NO_GET_CONTEXT +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" + +#include +#include +#include + +/* Include liblpm header - path set in Makefile.PL */ +#include + +/* Type for our blessed object */ +typedef struct { + lpm_trie_t *trie; + int is_ipv6; + int destroyed; +} NetLPM; + +/* Helper: Parse IPv4 address string to network byte order uint32_t */ +static int +parse_ipv4_addr(const char *str, uint32_t *addr) +{ + struct in_addr in; + if (inet_pton(AF_INET, str, &in) != 1) { + return 0; + } + *addr = ntohl(in.s_addr); + return 1; +} + +/* Helper: Parse IPv4 address string to byte array */ +static int +parse_ipv4_addr_bytes(const char *str, uint8_t *bytes) +{ + struct in_addr in; + if (inet_pton(AF_INET, str, &in) != 1) { + return 0; + } + memcpy(bytes, &in.s_addr, 4); + return 1; +} + +/* Helper: Parse IPv6 address string to byte array */ +static int +parse_ipv6_addr(const char *str, uint8_t *bytes) +{ + struct in6_addr in6; + if (inet_pton(AF_INET6, str, &in6) != 1) { + return 0; + } + memcpy(bytes, &in6.s6_addr, 16); + return 1; +} + +/* Helper: Parse CIDR prefix notation (e.g., "192.168.0.0/16") */ +static int +parse_prefix(const char *str, uint8_t *addr_bytes, uint8_t *prefix_len, int is_ipv6) +{ + char addr_buf[64]; + const char *slash; + size_t addr_len; + long len; + char *endptr; + int max_len = is_ipv6 ? 128 : 32; + + /* Find the '/' separator */ + slash = strchr(str, '/'); + if (!slash) { + return 0; + } + + /* Extract address part */ + addr_len = slash - str; + if (addr_len >= sizeof(addr_buf)) { + return 0; + } + memcpy(addr_buf, str, addr_len); + addr_buf[addr_len] = '\0'; + + /* Check for empty prefix length */ + if (*(slash + 1) == '\0') { + return 0; + } + + /* Parse the prefix length */ + len = strtol(slash + 1, &endptr, 10); + if (*endptr != '\0' || len < 0 || len > max_len) { + return 0; + } + *prefix_len = (uint8_t)len; + + /* Parse the address */ + if (is_ipv6) { + if (!parse_ipv6_addr(addr_buf, addr_bytes)) { + return 0; + } + } else { + if (!parse_ipv4_addr_bytes(addr_buf, addr_bytes)) { + return 0; + } + } + + return 1; +} + +#line 127 "LPM.c" +#ifndef PERL_UNUSED_VAR +# define PERL_UNUSED_VAR(var) if (0) var = var +#endif + +#ifndef dVAR +# define dVAR dNOOP +#endif + + +/* This stuff is not part of the API! You have been warned. */ +#ifndef PERL_VERSION_DECIMAL +# define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s) +#endif +#ifndef PERL_DECIMAL_VERSION +# define PERL_DECIMAL_VERSION \ + PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION) +#endif +#ifndef PERL_VERSION_GE +# define PERL_VERSION_GE(r,v,s) \ + (PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s)) +#endif +#ifndef PERL_VERSION_LE +# define PERL_VERSION_LE(r,v,s) \ + (PERL_DECIMAL_VERSION <= PERL_VERSION_DECIMAL(r,v,s)) +#endif + +/* XS_INTERNAL is the explicit static-linkage variant of the default + * XS macro. + * + * XS_EXTERNAL is the same as XS_INTERNAL except it does not include + * "STATIC", ie. it exports XSUB symbols. You probably don't want that + * for anything but the BOOT XSUB. + * + * See XSUB.h in core! + */ + + +/* TODO: This might be compatible further back than 5.10.0. */ +#if PERL_VERSION_GE(5, 10, 0) && PERL_VERSION_LE(5, 15, 1) +# undef XS_EXTERNAL +# undef XS_INTERNAL +# if defined(__CYGWIN__) && defined(USE_DYNAMIC_LOADING) +# define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name) +# define XS_INTERNAL(name) STATIC XSPROTO(name) +# endif +# if defined(__SYMBIAN32__) +# define XS_EXTERNAL(name) EXPORT_C XSPROTO(name) +# define XS_INTERNAL(name) EXPORT_C STATIC XSPROTO(name) +# endif +# ifndef XS_EXTERNAL +# if defined(HASATTRIBUTE_UNUSED) && !defined(__cplusplus) +# define XS_EXTERNAL(name) void name(pTHX_ CV* cv __attribute__unused__) +# define XS_INTERNAL(name) STATIC void name(pTHX_ CV* cv __attribute__unused__) +# else +# ifdef __cplusplus +# define XS_EXTERNAL(name) extern "C" XSPROTO(name) +# define XS_INTERNAL(name) static XSPROTO(name) +# else +# define XS_EXTERNAL(name) XSPROTO(name) +# define XS_INTERNAL(name) STATIC XSPROTO(name) +# endif +# endif +# endif +#endif + +/* perl >= 5.10.0 && perl <= 5.15.1 */ + + +/* The XS_EXTERNAL macro is used for functions that must not be static + * like the boot XSUB of a module. If perl didn't have an XS_EXTERNAL + * macro defined, the best we can do is assume XS is the same. + * Dito for XS_INTERNAL. + */ +#ifndef XS_EXTERNAL +# define XS_EXTERNAL(name) XS(name) +#endif +#ifndef XS_INTERNAL +# define XS_INTERNAL(name) XS(name) +#endif + +/* Now, finally, after all this mess, we want an ExtUtils::ParseXS + * internal macro that we're free to redefine for varying linkage due + * to the EXPORT_XSUB_SYMBOLS XS keyword. This is internal, use + * XS_EXTERNAL(name) or XS_INTERNAL(name) in your code if you need to! + */ + +#undef XS_EUPXS +#if defined(PERL_EUPXS_ALWAYS_EXPORT) +# define XS_EUPXS(name) XS_EXTERNAL(name) +#else + /* default to internal */ +# define XS_EUPXS(name) XS_INTERNAL(name) +#endif + +#ifndef PERL_ARGS_ASSERT_CROAK_XS_USAGE +#define PERL_ARGS_ASSERT_CROAK_XS_USAGE assert(cv); assert(params) + +/* prototype to pass -Wmissing-prototypes */ +STATIC void +S_croak_xs_usage(const CV *const cv, const char *const params); + +STATIC void +S_croak_xs_usage(const CV *const cv, const char *const params) +{ + const GV *const gv = CvGV(cv); + + PERL_ARGS_ASSERT_CROAK_XS_USAGE; + + if (gv) { + const char *const gvname = GvNAME(gv); + const HV *const stash = GvSTASH(gv); + const char *const hvname = stash ? HvNAME(stash) : NULL; + + if (hvname) + Perl_croak_nocontext("Usage: %s::%s(%s)", hvname, gvname, params); + else + Perl_croak_nocontext("Usage: %s(%s)", gvname, params); + } else { + /* Pants. I don't think that it should be possible to get here. */ + Perl_croak_nocontext("Usage: CODE(0x%" UVxf ")(%s)", PTR2UV(cv), params); + } +} +#undef PERL_ARGS_ASSERT_CROAK_XS_USAGE + +#define croak_xs_usage S_croak_xs_usage + +#endif + +/* NOTE: the prototype of newXSproto() is different in versions of perls, + * so we define a portable version of newXSproto() + */ +#ifdef newXS_flags +#define newXSproto_portable(name, c_impl, file, proto) newXS_flags(name, c_impl, file, proto, 0) +#else +#define newXSproto_portable(name, c_impl, file, proto) (PL_Sv=(SV*)newXS(name, c_impl, file), sv_setpv(PL_Sv, proto), (CV*)PL_Sv) +#endif /* !defined(newXS_flags) */ + +#if PERL_VERSION_LE(5, 21, 5) +# define newXS_deffile(a,b) Perl_newXS(aTHX_ a,b,file) +#else +# define newXS_deffile(a,b) Perl_newXS_deffile(aTHX_ a,b) +#endif + +#line 271 "LPM.c" + +XS_EUPXS(XS_Net__LPM__xs_new_ipv4); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_new_ipv4) +{ + dVAR; dXSARGS; + if (items != 0) + croak_xs_usage(cv, ""); + { +#line 125 "LPM.xs" + NetLPM *self; +#line 282 "LPM.c" + SV * RETVAL; +#line 127 "LPM.xs" + Newxz(self, 1, NetLPM); + self->trie = lpm_create_ipv4(); + if (!self->trie) { + Safefree(self); + croak("Failed to create IPv4 LPM table"); + } + self->is_ipv6 = 0; + self->destroyed = 0; + RETVAL = newSViv(PTR2IV(self)); +#line 294 "LPM.c" + RETVAL = sv_2mortal(RETVAL); + ST(0) = RETVAL; + } + XSRETURN(1); +} + + +XS_EUPXS(XS_Net__LPM__xs_new_ipv6); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_new_ipv6) +{ + dVAR; dXSARGS; + if (items != 0) + croak_xs_usage(cv, ""); + { +#line 143 "LPM.xs" + NetLPM *self; +#line 311 "LPM.c" + SV * RETVAL; +#line 145 "LPM.xs" + Newxz(self, 1, NetLPM); + self->trie = lpm_create_ipv6(); + if (!self->trie) { + Safefree(self); + croak("Failed to create IPv6 LPM table"); + } + self->is_ipv6 = 1; + self->destroyed = 0; + RETVAL = newSViv(PTR2IV(self)); +#line 323 "LPM.c" + RETVAL = sv_2mortal(RETVAL); + ST(0) = RETVAL; + } + XSRETURN(1); +} + + +XS_EUPXS(XS_Net__LPM__xs_destroy); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_destroy) +{ + dVAR; dXSARGS; + if (items != 1) + croak_xs_usage(cv, "self_iv"); + { + IV self_iv = (IV)SvIV(ST(0)) +; +#line 162 "LPM.xs" + NetLPM *self; +#line 342 "LPM.c" +#line 164 "LPM.xs" + self = INT2PTR(NetLPM*, self_iv); + if (self && !self->destroyed) { + if (self->trie) { + lpm_destroy(self->trie); + self->trie = NULL; + } + self->destroyed = 1; + } +#line 352 "LPM.c" + } + XSRETURN_EMPTY; +} + + +XS_EUPXS(XS_Net__LPM__xs_free); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_free) +{ + dVAR; dXSARGS; + if (items != 1) + croak_xs_usage(cv, "self_iv"); + { + IV self_iv = (IV)SvIV(ST(0)) +; +#line 178 "LPM.xs" + NetLPM *self; +#line 369 "LPM.c" +#line 180 "LPM.xs" + self = INT2PTR(NetLPM*, self_iv); + if (self) { + if (!self->destroyed && self->trie) { + lpm_destroy(self->trie); + } + Safefree(self); + } +#line 378 "LPM.c" + } + XSRETURN_EMPTY; +} + + +XS_EUPXS(XS_Net__LPM__xs_is_ipv6); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_is_ipv6) +{ + dVAR; dXSARGS; + if (items != 1) + croak_xs_usage(cv, "self_iv"); + { + IV self_iv = (IV)SvIV(ST(0)) +; +#line 193 "LPM.xs" + NetLPM *self; +#line 395 "LPM.c" + int RETVAL; + dXSTARG; +#line 195 "LPM.xs" + self = INT2PTR(NetLPM*, self_iv); + RETVAL = self ? self->is_ipv6 : 0; +#line 401 "LPM.c" + XSprePUSH; + PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + + +XS_EUPXS(XS_Net__LPM__xs_is_destroyed); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_is_destroyed) +{ + dVAR; dXSARGS; + if (items != 1) + croak_xs_usage(cv, "self_iv"); + { + IV self_iv = (IV)SvIV(ST(0)) +; +#line 205 "LPM.xs" + NetLPM *self; +#line 420 "LPM.c" + int RETVAL; + dXSTARG; +#line 207 "LPM.xs" + self = INT2PTR(NetLPM*, self_iv); + RETVAL = self ? self->destroyed : 1; +#line 426 "LPM.c" + XSprePUSH; + PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + + +XS_EUPXS(XS_Net__LPM__xs_insert); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_insert) +{ + dVAR; dXSARGS; + if (items != 3) + croak_xs_usage(cv, "self_iv, prefix_str, next_hop"); + { + IV self_iv = (IV)SvIV(ST(0)) +; + const char * prefix_str = (const char *)SvPV_nolen(ST(1)) +; + unsigned int next_hop = (unsigned int)SvUV(ST(2)) +; +#line 219 "LPM.xs" + NetLPM *self; + uint8_t addr_bytes[16]; + uint8_t prefix_len; + int result; +#line 452 "LPM.c" + int RETVAL; + dXSTARG; +#line 224 "LPM.xs" + self = INT2PTR(NetLPM*, self_iv); + if (!self || self->destroyed || !self->trie) { + croak("Cannot insert into destroyed or invalid table"); + } + + if (!parse_prefix(prefix_str, addr_bytes, &prefix_len, self->is_ipv6)) { + croak("Invalid prefix format: %s", prefix_str); + } + + result = lpm_add(self->trie, addr_bytes, prefix_len, (uint32_t)next_hop); + if (result != 0) { + croak("Failed to insert prefix: %s", prefix_str); + } + + RETVAL = 1; +#line 471 "LPM.c" + XSprePUSH; + PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + + +XS_EUPXS(XS_Net__LPM__xs_delete); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_delete) +{ + dVAR; dXSARGS; + if (items != 2) + croak_xs_usage(cv, "self_iv, prefix_str"); + { + IV self_iv = (IV)SvIV(ST(0)) +; + const char * prefix_str = (const char *)SvPV_nolen(ST(1)) +; +#line 248 "LPM.xs" + NetLPM *self; + uint8_t addr_bytes[16]; + uint8_t prefix_len; + int result; +#line 495 "LPM.c" + int RETVAL; + dXSTARG; +#line 253 "LPM.xs" + self = INT2PTR(NetLPM*, self_iv); + if (!self || self->destroyed || !self->trie) { + croak("Cannot delete from destroyed or invalid table"); + } + + if (!parse_prefix(prefix_str, addr_bytes, &prefix_len, self->is_ipv6)) { + croak("Invalid prefix format: %s", prefix_str); + } + + result = lpm_delete(self->trie, addr_bytes, prefix_len); + if (result != 0) { + /* Prefix not found is not an error - just return 0 */ + RETVAL = 0; + } else { + RETVAL = 1; + } +#line 515 "LPM.c" + XSprePUSH; + PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + + +XS_EUPXS(XS_Net__LPM__xs_lookup); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_lookup) +{ + dVAR; dXSARGS; + if (items != 2) + croak_xs_usage(cv, "self_iv, addr_str"); + { + IV self_iv = (IV)SvIV(ST(0)) +; + const char * addr_str = (const char *)SvPV_nolen(ST(1)) +; +#line 278 "LPM.xs" + NetLPM *self; + uint32_t next_hop; + uint32_t ipv4_addr; + uint8_t ipv6_addr[16]; +#line 539 "LPM.c" + SV * RETVAL; +#line 283 "LPM.xs" + self = INT2PTR(NetLPM*, self_iv); + if (!self || self->destroyed || !self->trie) { + croak("Cannot lookup in destroyed or invalid table"); + } + + if (self->is_ipv6) { + if (!parse_ipv6_addr(addr_str, ipv6_addr)) { + croak("Invalid IPv6 address: %s", addr_str); + } + next_hop = lpm_lookup_ipv6(self->trie, ipv6_addr); + } else { + if (!parse_ipv4_addr(addr_str, &ipv4_addr)) { + croak("Invalid IPv4 address: %s", addr_str); + } + next_hop = lpm_lookup_ipv4(self->trie, ipv4_addr); + } + + if (next_hop == LPM_INVALID_NEXT_HOP) { + RETVAL = &PL_sv_undef; + } else { + RETVAL = newSVuv(next_hop); + } +#line 564 "LPM.c" + RETVAL = sv_2mortal(RETVAL); + ST(0) = RETVAL; + } + XSRETURN(1); +} + + +XS_EUPXS(XS_Net__LPM__xs_lookup_batch_ipv4); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_lookup_batch_ipv4) +{ + dVAR; dXSARGS; + if (items != 2) + croak_xs_usage(cv, "self_iv, addrs_av"); + PERL_UNUSED_VAR(ax); /* -Wall */ + SP -= items; + { + IV self_iv = (IV)SvIV(ST(0)) +; + SV * addrs_av = ST(1) +; +#line 314 "LPM.xs" + NetLPM *self; + AV *av; + SSize_t count; + SSize_t i; + uint32_t *addrs = NULL; + uint32_t *results = NULL; + SV **elem; + const char *addr_str; +#line 594 "LPM.c" +#line 323 "LPM.xs" + self = INT2PTR(NetLPM*, self_iv); + if (!self || self->destroyed || !self->trie) { + croak("Cannot lookup in destroyed or invalid table"); + } + + if (self->is_ipv6) { + croak("Use lookup_batch for IPv6, not lookup_batch_ipv4"); + } + + if (!SvROK(addrs_av) || SvTYPE(SvRV(addrs_av)) != SVt_PVAV) { + croak("lookup_batch_ipv4 requires an array reference"); + } + av = (AV*)SvRV(addrs_av); + + count = av_len(av) + 1; + if (count <= 0) { + XSRETURN_EMPTY; + } + + /* Allocate arrays */ + Newx(addrs, count, uint32_t); + Newx(results, count, uint32_t); + + /* Parse all addresses */ + for (i = 0; i < count; i++) { + elem = av_fetch(av, i, 0); + if (!elem || !SvOK(*elem)) { + Safefree(addrs); + Safefree(results); + croak("Invalid address at index %ld", (long)i); + } + addr_str = SvPV_nolen(*elem); + if (!parse_ipv4_addr(addr_str, &addrs[i])) { + Safefree(addrs); + Safefree(results); + croak("Invalid IPv4 address at index %ld: %s", (long)i, addr_str); + } + } + + /* Perform batch lookup */ + lpm_lookup_batch_ipv4(self->trie, addrs, results, (size_t)count); + + /* Push results onto the stack */ + EXTEND(SP, count); + for (i = 0; i < count; i++) { + if (results[i] == LPM_INVALID_NEXT_HOP) { + PUSHs(&PL_sv_undef); + } else { + PUSHs(sv_2mortal(newSVuv(results[i]))); + } + } + + Safefree(addrs); + Safefree(results); +#line 650 "LPM.c" + PUTBACK; + return; + } +} + + +XS_EUPXS(XS_Net__LPM__xs_lookup_batch_ipv6); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_lookup_batch_ipv6) +{ + dVAR; dXSARGS; + if (items != 2) + croak_xs_usage(cv, "self_iv, addrs_av"); + PERL_UNUSED_VAR(ax); /* -Wall */ + SP -= items; + { + IV self_iv = (IV)SvIV(ST(0)) +; + SV * addrs_av = ST(1) +; +#line 384 "LPM.xs" + NetLPM *self; + AV *av; + SSize_t count; + SSize_t i; + uint8_t *addrs = NULL; /* Flat array: count * 16 bytes */ + uint32_t *results = NULL; + SV **elem; + const char *addr_str; +#line 679 "LPM.c" +#line 393 "LPM.xs" + self = INT2PTR(NetLPM*, self_iv); + if (!self || self->destroyed || !self->trie) { + croak("Cannot lookup in destroyed or invalid table"); + } + + if (!self->is_ipv6) { + croak("Use lookup_batch for IPv4, not lookup_batch_ipv6"); + } + + if (!SvROK(addrs_av) || SvTYPE(SvRV(addrs_av)) != SVt_PVAV) { + croak("lookup_batch_ipv6 requires an array reference"); + } + av = (AV*)SvRV(addrs_av); + + count = av_len(av) + 1; + if (count <= 0) { + XSRETURN_EMPTY; + } + + /* Allocate arrays - use flat array for IPv6 addresses */ + Newx(addrs, count * 16, uint8_t); + Newx(results, count, uint32_t); + + /* Parse all addresses */ + for (i = 0; i < count; i++) { + elem = av_fetch(av, i, 0); + if (!elem || !SvOK(*elem)) { + Safefree(addrs); + Safefree(results); + croak("Invalid address at index %ld", (long)i); + } + addr_str = SvPV_nolen(*elem); + if (!parse_ipv6_addr(addr_str, &addrs[i * 16])) { + Safefree(addrs); + Safefree(results); + croak("Invalid IPv6 address at index %ld: %s", (long)i, addr_str); + } + } + + /* Perform batch lookup - cast to proper type */ + lpm_lookup_batch_ipv6(self->trie, (const uint8_t (*)[16])addrs, results, (size_t)count); + + /* Push results onto the stack */ + EXTEND(SP, count); + for (i = 0; i < count; i++) { + if (results[i] == LPM_INVALID_NEXT_HOP) { + PUSHs(&PL_sv_undef); + } else { + PUSHs(sv_2mortal(newSVuv(results[i]))); + } + } + + Safefree(addrs); + Safefree(results); +#line 735 "LPM.c" + PUTBACK; + return; + } +} + + +XS_EUPXS(XS_Net__LPM__xs_get_version); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_get_version) +{ + dVAR; dXSARGS; + if (items != 0) + croak_xs_usage(cv, ""); + { + const char * RETVAL; + dXSTARG; +#line 452 "LPM.xs" + RETVAL = lpm_get_version(); +#line 753 "LPM.c" + sv_setpv(TARG, RETVAL); + XSprePUSH; + PUSHTARG; + } + XSRETURN(1); +} + + +XS_EUPXS(XS_Net__LPM__xs_invalid_next_hop); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_invalid_next_hop) +{ + dVAR; dXSARGS; + if (items != 0) + croak_xs_usage(cv, ""); + { + unsigned int RETVAL; + dXSTARG; +#line 460 "LPM.xs" + RETVAL = LPM_INVALID_NEXT_HOP; +#line 773 "LPM.c" + XSprePUSH; + PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + + +XS_EUPXS(XS_Net__LPM__xs_print_stats); /* prototype to pass -Wmissing-prototypes */ +XS_EUPXS(XS_Net__LPM__xs_print_stats) +{ + dVAR; dXSARGS; + if (items != 1) + croak_xs_usage(cv, "self_iv"); + { + IV self_iv = (IV)SvIV(ST(0)) +; +#line 469 "LPM.xs" + NetLPM *self; +#line 792 "LPM.c" +#line 471 "LPM.xs" + self = INT2PTR(NetLPM*, self_iv); + if (self && !self->destroyed && self->trie) { + lpm_print_stats(self->trie); + } +#line 798 "LPM.c" + } + XSRETURN_EMPTY; +} + +#ifdef __cplusplus +extern "C" { +#endif +XS_EXTERNAL(boot_Net__LPM); /* prototype to pass -Wmissing-prototypes */ +XS_EXTERNAL(boot_Net__LPM) +{ +#if PERL_VERSION_LE(5, 21, 5) + dVAR; dXSARGS; +#else + dVAR; dXSBOOTARGSXSAPIVERCHK; +#endif +#if PERL_VERSION_LE(5, 8, 999) /* PERL_VERSION_LT is 5.33+ */ + char* file = __FILE__; +#else + const char* file = __FILE__; +#endif + + PERL_UNUSED_VAR(file); + + PERL_UNUSED_VAR(cv); /* -W */ + PERL_UNUSED_VAR(items); /* -W */ +#if PERL_VERSION_LE(5, 21, 5) + XS_VERSION_BOOTCHECK; +# ifdef XS_APIVERSION_BOOTCHECK + XS_APIVERSION_BOOTCHECK; +# endif +#endif + + newXS_deffile("Net::LPM::_xs_new_ipv4", XS_Net__LPM__xs_new_ipv4); + newXS_deffile("Net::LPM::_xs_new_ipv6", XS_Net__LPM__xs_new_ipv6); + newXS_deffile("Net::LPM::_xs_destroy", XS_Net__LPM__xs_destroy); + newXS_deffile("Net::LPM::_xs_free", XS_Net__LPM__xs_free); + newXS_deffile("Net::LPM::_xs_is_ipv6", XS_Net__LPM__xs_is_ipv6); + newXS_deffile("Net::LPM::_xs_is_destroyed", XS_Net__LPM__xs_is_destroyed); + newXS_deffile("Net::LPM::_xs_insert", XS_Net__LPM__xs_insert); + newXS_deffile("Net::LPM::_xs_delete", XS_Net__LPM__xs_delete); + newXS_deffile("Net::LPM::_xs_lookup", XS_Net__LPM__xs_lookup); + newXS_deffile("Net::LPM::_xs_lookup_batch_ipv4", XS_Net__LPM__xs_lookup_batch_ipv4); + newXS_deffile("Net::LPM::_xs_lookup_batch_ipv6", XS_Net__LPM__xs_lookup_batch_ipv6); + newXS_deffile("Net::LPM::_xs_get_version", XS_Net__LPM__xs_get_version); + newXS_deffile("Net::LPM::_xs_invalid_next_hop", XS_Net__LPM__xs_invalid_next_hop); + newXS_deffile("Net::LPM::_xs_print_stats", XS_Net__LPM__xs_print_stats); +#if PERL_VERSION_LE(5, 21, 5) +# if PERL_VERSION_GE(5, 9, 0) + if (PL_unitcheckav) + call_list(PL_scopestack_ix, PL_unitcheckav); +# endif + XSRETURN_YES; +#else + Perl_xs_boot_epilog(aTHX_ ax); +#endif +} + +#ifdef __cplusplus +} +#endif diff --git a/bindings/perl/LPM.xs b/bindings/perl/LPM.xs new file mode 100644 index 0000000..dd8c379 --- /dev/null +++ b/bindings/perl/LPM.xs @@ -0,0 +1,474 @@ +/* + * LPM.xs - XS bindings for liblpm + * + * High-performance Perl bindings for the liblpm C library. + * Provides IPv4 and IPv6 longest prefix match operations. + * + * Copyright (c) Murilo Chianfa + * Licensed under the Boost Software License 1.0 + */ + +#define PERL_NO_GET_CONTEXT +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" + +#include +#include +#include + +/* Include liblpm header - path set in Makefile.PL */ +#include + +/* Type for our blessed object */ +typedef struct { + lpm_trie_t *trie; + int is_ipv6; + int destroyed; +} NetLPM; + +/* Helper: Parse IPv4 address string to network byte order uint32_t */ +static int +parse_ipv4_addr(const char *str, uint32_t *addr) +{ + struct in_addr in; + if (inet_pton(AF_INET, str, &in) != 1) { + return 0; + } + *addr = ntohl(in.s_addr); + return 1; +} + +/* Helper: Parse IPv4 address string to byte array */ +static int +parse_ipv4_addr_bytes(const char *str, uint8_t *bytes) +{ + struct in_addr in; + if (inet_pton(AF_INET, str, &in) != 1) { + return 0; + } + memcpy(bytes, &in.s_addr, 4); + return 1; +} + +/* Helper: Parse IPv6 address string to byte array */ +static int +parse_ipv6_addr(const char *str, uint8_t *bytes) +{ + struct in6_addr in6; + if (inet_pton(AF_INET6, str, &in6) != 1) { + return 0; + } + memcpy(bytes, &in6.s6_addr, 16); + return 1; +} + +/* Helper: Parse CIDR prefix notation (e.g., "192.168.0.0/16") */ +static int +parse_prefix(const char *str, uint8_t *addr_bytes, uint8_t *prefix_len, int is_ipv6) +{ + char addr_buf[64]; + const char *slash; + size_t addr_len; + long len; + char *endptr; + int max_len = is_ipv6 ? 128 : 32; + + /* Find the '/' separator */ + slash = strchr(str, '/'); + if (!slash) { + return 0; + } + + /* Extract address part */ + addr_len = slash - str; + if (addr_len >= sizeof(addr_buf)) { + return 0; + } + memcpy(addr_buf, str, addr_len); + addr_buf[addr_len] = '\0'; + + /* Check for empty prefix length */ + if (*(slash + 1) == '\0') { + return 0; + } + + /* Parse the prefix length */ + len = strtol(slash + 1, &endptr, 10); + if (*endptr != '\0' || len < 0 || len > max_len) { + return 0; + } + *prefix_len = (uint8_t)len; + + /* Parse the address */ + if (is_ipv6) { + if (!parse_ipv6_addr(addr_buf, addr_bytes)) { + return 0; + } + } else { + if (!parse_ipv4_addr_bytes(addr_buf, addr_bytes)) { + return 0; + } + } + + return 1; +} + +MODULE = Net::LPM PACKAGE = Net::LPM + +PROTOTYPES: DISABLE + +# Create a new IPv4 LPM table +SV * +_xs_new_ipv4() + PREINIT: + NetLPM *self; + CODE: + Newxz(self, 1, NetLPM); + self->trie = lpm_create_ipv4(); + if (!self->trie) { + Safefree(self); + croak("Failed to create IPv4 LPM table"); + } + self->is_ipv6 = 0; + self->destroyed = 0; + RETVAL = newSViv(PTR2IV(self)); + OUTPUT: + RETVAL + +# Create a new IPv6 LPM table +SV * +_xs_new_ipv6() + PREINIT: + NetLPM *self; + CODE: + Newxz(self, 1, NetLPM); + self->trie = lpm_create_ipv6(); + if (!self->trie) { + Safefree(self); + croak("Failed to create IPv6 LPM table"); + } + self->is_ipv6 = 1; + self->destroyed = 0; + RETVAL = newSViv(PTR2IV(self)); + OUTPUT: + RETVAL + +# Destroy the LPM table +void +_xs_destroy(self_iv) + IV self_iv + PREINIT: + NetLPM *self; + CODE: + self = INT2PTR(NetLPM*, self_iv); + if (self && !self->destroyed) { + if (self->trie) { + lpm_destroy(self->trie); + self->trie = NULL; + } + self->destroyed = 1; + } + +# Free the NetLPM structure (called from Perl DESTROY) +void +_xs_free(self_iv) + IV self_iv + PREINIT: + NetLPM *self; + CODE: + self = INT2PTR(NetLPM*, self_iv); + if (self) { + if (!self->destroyed && self->trie) { + lpm_destroy(self->trie); + } + Safefree(self); + } + +# Check if table is IPv6 +int +_xs_is_ipv6(self_iv) + IV self_iv + PREINIT: + NetLPM *self; + CODE: + self = INT2PTR(NetLPM*, self_iv); + RETVAL = self ? self->is_ipv6 : 0; + OUTPUT: + RETVAL + +# Check if table is destroyed +int +_xs_is_destroyed(self_iv) + IV self_iv + PREINIT: + NetLPM *self; + CODE: + self = INT2PTR(NetLPM*, self_iv); + RETVAL = self ? self->destroyed : 1; + OUTPUT: + RETVAL + +# Insert a prefix into the table +int +_xs_insert(self_iv, prefix_str, next_hop) + IV self_iv + const char *prefix_str + unsigned int next_hop + PREINIT: + NetLPM *self; + uint8_t addr_bytes[16]; + uint8_t prefix_len; + int result; + CODE: + self = INT2PTR(NetLPM*, self_iv); + if (!self || self->destroyed || !self->trie) { + croak("Cannot insert into destroyed or invalid table"); + } + + if (!parse_prefix(prefix_str, addr_bytes, &prefix_len, self->is_ipv6)) { + croak("Invalid prefix format: %s", prefix_str); + } + + result = lpm_add(self->trie, addr_bytes, prefix_len, (uint32_t)next_hop); + if (result != 0) { + croak("Failed to insert prefix: %s", prefix_str); + } + + RETVAL = 1; + OUTPUT: + RETVAL + +# Delete a prefix from the table +int +_xs_delete(self_iv, prefix_str) + IV self_iv + const char *prefix_str + PREINIT: + NetLPM *self; + uint8_t addr_bytes[16]; + uint8_t prefix_len; + int result; + CODE: + self = INT2PTR(NetLPM*, self_iv); + if (!self || self->destroyed || !self->trie) { + croak("Cannot delete from destroyed or invalid table"); + } + + if (!parse_prefix(prefix_str, addr_bytes, &prefix_len, self->is_ipv6)) { + croak("Invalid prefix format: %s", prefix_str); + } + + result = lpm_delete(self->trie, addr_bytes, prefix_len); + if (result != 0) { + /* Prefix not found is not an error - just return 0 */ + RETVAL = 0; + } else { + RETVAL = 1; + } + OUTPUT: + RETVAL + +# Lookup a single address +SV * +_xs_lookup(self_iv, addr_str) + IV self_iv + const char *addr_str + PREINIT: + NetLPM *self; + uint32_t next_hop; + uint32_t ipv4_addr; + uint8_t ipv6_addr[16]; + CODE: + self = INT2PTR(NetLPM*, self_iv); + if (!self || self->destroyed || !self->trie) { + croak("Cannot lookup in destroyed or invalid table"); + } + + if (self->is_ipv6) { + if (!parse_ipv6_addr(addr_str, ipv6_addr)) { + croak("Invalid IPv6 address: %s", addr_str); + } + next_hop = lpm_lookup_ipv6(self->trie, ipv6_addr); + } else { + if (!parse_ipv4_addr(addr_str, &ipv4_addr)) { + croak("Invalid IPv4 address: %s", addr_str); + } + next_hop = lpm_lookup_ipv4(self->trie, ipv4_addr); + } + + if (next_hop == LPM_INVALID_NEXT_HOP) { + RETVAL = &PL_sv_undef; + } else { + RETVAL = newSVuv(next_hop); + } + OUTPUT: + RETVAL + +# Batch lookup for IPv4 addresses +void +_xs_lookup_batch_ipv4(self_iv, addrs_av) + IV self_iv + SV *addrs_av + PREINIT: + NetLPM *self; + AV *av; + SSize_t count; + SSize_t i; + uint32_t *addrs = NULL; + uint32_t *results = NULL; + SV **elem; + const char *addr_str; + PPCODE: + self = INT2PTR(NetLPM*, self_iv); + if (!self || self->destroyed || !self->trie) { + croak("Cannot lookup in destroyed or invalid table"); + } + + if (self->is_ipv6) { + croak("Use lookup_batch for IPv6, not lookup_batch_ipv4"); + } + + if (!SvROK(addrs_av) || SvTYPE(SvRV(addrs_av)) != SVt_PVAV) { + croak("lookup_batch_ipv4 requires an array reference"); + } + av = (AV*)SvRV(addrs_av); + + count = av_len(av) + 1; + if (count <= 0) { + XSRETURN_EMPTY; + } + + /* Allocate arrays */ + Newx(addrs, count, uint32_t); + Newx(results, count, uint32_t); + + /* Parse all addresses */ + for (i = 0; i < count; i++) { + elem = av_fetch(av, i, 0); + if (!elem || !SvOK(*elem)) { + Safefree(addrs); + Safefree(results); + croak("Invalid address at index %ld", (long)i); + } + addr_str = SvPV_nolen(*elem); + if (!parse_ipv4_addr(addr_str, &addrs[i])) { + Safefree(addrs); + Safefree(results); + croak("Invalid IPv4 address at index %ld: %s", (long)i, addr_str); + } + } + + /* Perform batch lookup */ + lpm_lookup_batch_ipv4(self->trie, addrs, results, (size_t)count); + + /* Push results onto the stack */ + EXTEND(SP, count); + for (i = 0; i < count; i++) { + if (results[i] == LPM_INVALID_NEXT_HOP) { + PUSHs(&PL_sv_undef); + } else { + PUSHs(sv_2mortal(newSVuv(results[i]))); + } + } + + Safefree(addrs); + Safefree(results); + +# Batch lookup for IPv6 addresses +void +_xs_lookup_batch_ipv6(self_iv, addrs_av) + IV self_iv + SV *addrs_av + PREINIT: + NetLPM *self; + AV *av; + SSize_t count; + SSize_t i; + uint8_t *addrs = NULL; /* Flat array: count * 16 bytes */ + uint32_t *results = NULL; + SV **elem; + const char *addr_str; + PPCODE: + self = INT2PTR(NetLPM*, self_iv); + if (!self || self->destroyed || !self->trie) { + croak("Cannot lookup in destroyed or invalid table"); + } + + if (!self->is_ipv6) { + croak("Use lookup_batch for IPv4, not lookup_batch_ipv6"); + } + + if (!SvROK(addrs_av) || SvTYPE(SvRV(addrs_av)) != SVt_PVAV) { + croak("lookup_batch_ipv6 requires an array reference"); + } + av = (AV*)SvRV(addrs_av); + + count = av_len(av) + 1; + if (count <= 0) { + XSRETURN_EMPTY; + } + + /* Allocate arrays - use flat array for IPv6 addresses */ + Newx(addrs, count * 16, uint8_t); + Newx(results, count, uint32_t); + + /* Parse all addresses */ + for (i = 0; i < count; i++) { + elem = av_fetch(av, i, 0); + if (!elem || !SvOK(*elem)) { + Safefree(addrs); + Safefree(results); + croak("Invalid address at index %ld", (long)i); + } + addr_str = SvPV_nolen(*elem); + if (!parse_ipv6_addr(addr_str, &addrs[i * 16])) { + Safefree(addrs); + Safefree(results); + croak("Invalid IPv6 address at index %ld: %s", (long)i, addr_str); + } + } + + /* Perform batch lookup - cast to proper type */ + lpm_lookup_batch_ipv6(self->trie, (const uint8_t (*)[16])addrs, results, (size_t)count); + + /* Push results onto the stack */ + EXTEND(SP, count); + for (i = 0; i < count; i++) { + if (results[i] == LPM_INVALID_NEXT_HOP) { + PUSHs(&PL_sv_undef); + } else { + PUSHs(sv_2mortal(newSVuv(results[i]))); + } + } + + Safefree(addrs); + Safefree(results); + +# Get the liblpm version string +const char * +_xs_get_version() + CODE: + RETVAL = lpm_get_version(); + OUTPUT: + RETVAL + +# Get the invalid next hop constant +unsigned int +_xs_invalid_next_hop() + CODE: + RETVAL = LPM_INVALID_NEXT_HOP; + OUTPUT: + RETVAL + +# Print stats (for debugging) +void +_xs_print_stats(self_iv) + IV self_iv + PREINIT: + NetLPM *self; + CODE: + self = INT2PTR(NetLPM*, self_iv); + if (self && !self->destroyed && self->trie) { + lpm_print_stats(self->trie); + } diff --git a/bindings/perl/MYMETA.json b/bindings/perl/MYMETA.json new file mode 100644 index 0000000..aa3198f --- /dev/null +++ b/bindings/perl/MYMETA.json @@ -0,0 +1,69 @@ +{ + "abstract" : "Perl bindings for high-performance liblpm Longest Prefix Match library", + "author" : [ + "Murilo Chianfa " + ], + "dynamic_config" : 0, + "generated_by" : "ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010", + "keywords" : [ + "lpm", + "longest-prefix-match", + "routing", + "ipv4", + "ipv6", + "networking" + ], + "license" : [ + "open_source" + ], + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : 2 + }, + "name" : "Net-LPM", + "no_index" : { + "directory" : [ + "t", + "inc" + ] + }, + "prereqs" : { + "build" : { + "requires" : { + "ExtUtils::MakeMaker" : "6.64" + } + }, + "configure" : { + "requires" : { + "ExtUtils::MakeMaker" : "6.64" + } + }, + "runtime" : { + "requires" : { + "Carp" : "0", + "Exporter" : "0", + "XSLoader" : "0", + "perl" : "5.010" + } + }, + "test" : { + "requires" : { + "Test::More" : "0.98" + } + } + }, + "release_status" : "stable", + "resources" : { + "bugtracker" : { + "web" : "https://github.com/MuriloChianfa/liblpm/issues" + }, + "homepage" : "https://github.com/MuriloChianfa/liblpm", + "repository" : { + "type" : "git", + "url" : "https://github.com/MuriloChianfa/liblpm.git", + "web" : "https://github.com/MuriloChianfa/liblpm" + } + }, + "version" : "v2.0.0", + "x_serialization_backend" : "JSON::PP version 4.16" +} diff --git a/bindings/perl/MYMETA.yml b/bindings/perl/MYMETA.yml new file mode 100644 index 0000000..ffd3ec6 --- /dev/null +++ b/bindings/perl/MYMETA.yml @@ -0,0 +1,38 @@ +--- +abstract: 'Perl bindings for high-performance liblpm Longest Prefix Match library' +author: + - 'Murilo Chianfa ' +build_requires: + ExtUtils::MakeMaker: '6.64' + Test::More: '0.98' +configure_requires: + ExtUtils::MakeMaker: '6.64' +dynamic_config: 0 +generated_by: 'ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010' +keywords: + - lpm + - longest-prefix-match + - routing + - ipv4 + - ipv6 + - networking +license: open_source +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html + version: '1.4' +name: Net-LPM +no_index: + directory: + - t + - inc +requires: + Carp: '0' + Exporter: '0' + XSLoader: '0' + perl: '5.010' +resources: + bugtracker: https://github.com/MuriloChianfa/liblpm/issues + homepage: https://github.com/MuriloChianfa/liblpm + repository: https://github.com/MuriloChianfa/liblpm.git +version: v2.0.0 +x_serialization_backend: 'CPAN::Meta::YAML version 0.018' diff --git a/bindings/perl/Makefile b/bindings/perl/Makefile new file mode 100644 index 0000000..19a7b6c --- /dev/null +++ b/bindings/perl/Makefile @@ -0,0 +1,1138 @@ +# This Makefile is for the Net::LPM extension to perl. +# +# It was generated automatically by MakeMaker version +# 7.70 (Revision: 77000) from the contents of +# Makefile.PL. Don't edit this file, edit Makefile.PL instead. +# +# ANY CHANGES MADE HERE WILL BE LOST! +# +# MakeMaker ARGV: () +# + +# MakeMaker Parameters: + +# ABSTRACT => q[Perl bindings for high-performance liblpm Longest Prefix Match library] +# AUTHOR => [q[Murilo Chianfa ]] +# BUILD_REQUIRES => { ExtUtils::MakeMaker=>q[6.64] } +# CCFLAGS => q[-D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fwrapv -fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -Wall] +# CONFIGURE_REQUIRES => { ExtUtils::MakeMaker=>q[6.64] } +# INC => q[-I/usr/local/include/lpm ] +# LIBS => [q[-L/usr/local/lib -llpm]] +# LICENSE => q[open_source] +# META_MERGE => { keywords=>[q[lpm], q[longest-prefix-match], q[routing], q[ipv4], q[ipv6], q[networking]], meta-spec=>{ version=>q[2] }, resources=>{ bugtracker=>{ web=>q[https://github.com/MuriloChianfa/liblpm/issues] }, homepage=>q[https://github.com/MuriloChianfa/liblpm], repository=>{ type=>q[git], url=>q[https://github.com/MuriloChianfa/liblpm.git], web=>q[https://github.com/MuriloChianfa/liblpm] } } } +# MIN_PERL_VERSION => q[5.010] +# NAME => q[Net::LPM] +# OBJECT => q[LPM$(OBJ_EXT)] +# PREREQ_PM => { Carp=>q[0], Exporter=>q[0], ExtUtils::MakeMaker=>q[6.64], Test::More=>q[0.98], XSLoader=>q[0] } +# TEST_REQUIRES => { Test::More=>q[0.98] } +# TYPEMAPS => [q[typemap]] +# VERSION_FROM => q[lib/Net/LPM.pm] +# XS => { LPM.xs=>q[LPM.c] } +# clean => { FILES=>q[Net-LPM-* *.o *.c] } + +# --- MakeMaker post_initialize section: + + +# --- MakeMaker const_config section: + +# These definitions are from config.sh (via /usr/lib/x86_64-linux-gnu/perl-base/Config.pm). +# They may have been overridden via Makefile.PL or on the command line. +AR = ar +CC = x86_64-linux-gnu-gcc +CCCDLFLAGS = -fPIC +CCDLFLAGS = -Wl,-E +CPPRUN = x86_64-linux-gnu-gcc -E +DLEXT = so +DLSRC = dl_dlopen.xs +EXE_EXT = +FULL_AR = /usr/bin/ar +LD = x86_64-linux-gnu-gcc +LDDLFLAGS = -shared -L/usr/local/lib -fstack-protector-strong +LDFLAGS = -fstack-protector-strong -L/usr/local/lib +LIBC = /lib/x86_64-linux-gnu/libc.so.6 +LIB_EXT = .a +OBJ_EXT = .o +OSNAME = linux +OSVERS = 6.1.0 +RANLIB = : +SITELIBEXP = /usr/local/share/perl/5.40.1 +SITEARCHEXP = /usr/local/lib/x86_64-linux-gnu/perl/5.40.1 +SO = so +VENDORARCHEXP = /usr/lib/x86_64-linux-gnu/perl5/5.40 +VENDORLIBEXP = /usr/share/perl5 + + +# --- MakeMaker constants section: +AR_STATIC_ARGS = cr +DIRFILESEP = / +DFSEP = $(DIRFILESEP) +NAME = Net::LPM +NAME_SYM = Net_LPM +VERSION = 2.0.0 +VERSION_MACRO = VERSION +VERSION_SYM = 2_0_0 +DEFINE_VERSION = -D$(VERSION_MACRO)=\"$(VERSION)\" +XS_VERSION = 2.0.0 +XS_VERSION_MACRO = XS_VERSION +XS_DEFINE_VERSION = -D$(XS_VERSION_MACRO)=\"$(XS_VERSION)\" +INST_ARCHLIB = blib/arch +INST_SCRIPT = blib/script +INST_BIN = blib/bin +INST_LIB = blib/lib +INST_MAN1DIR = blib/man1 +INST_MAN3DIR = blib/man3 +MAN1EXT = 1p +MAN3EXT = 3pm +MAN1SECTION = 1 +MAN3SECTION = 3 +INSTALLDIRS = site +DESTDIR = +PREFIX = $(SITEPREFIX) +PERLPREFIX = /usr +SITEPREFIX = /usr/local +VENDORPREFIX = /usr +INSTALLPRIVLIB = /usr/share/perl/5.40 +DESTINSTALLPRIVLIB = $(DESTDIR)$(INSTALLPRIVLIB) +INSTALLSITELIB = /usr/local/share/perl/5.40.1 +DESTINSTALLSITELIB = $(DESTDIR)$(INSTALLSITELIB) +INSTALLVENDORLIB = /usr/share/perl5 +DESTINSTALLVENDORLIB = $(DESTDIR)$(INSTALLVENDORLIB) +INSTALLARCHLIB = /usr/lib/x86_64-linux-gnu/perl/5.40 +DESTINSTALLARCHLIB = $(DESTDIR)$(INSTALLARCHLIB) +INSTALLSITEARCH = /usr/local/lib/x86_64-linux-gnu/perl/5.40.1 +DESTINSTALLSITEARCH = $(DESTDIR)$(INSTALLSITEARCH) +INSTALLVENDORARCH = /usr/lib/x86_64-linux-gnu/perl5/5.40 +DESTINSTALLVENDORARCH = $(DESTDIR)$(INSTALLVENDORARCH) +INSTALLBIN = /usr/bin +DESTINSTALLBIN = $(DESTDIR)$(INSTALLBIN) +INSTALLSITEBIN = /usr/local/bin +DESTINSTALLSITEBIN = $(DESTDIR)$(INSTALLSITEBIN) +INSTALLVENDORBIN = /usr/bin +DESTINSTALLVENDORBIN = $(DESTDIR)$(INSTALLVENDORBIN) +INSTALLSCRIPT = /usr/bin +DESTINSTALLSCRIPT = $(DESTDIR)$(INSTALLSCRIPT) +INSTALLSITESCRIPT = /usr/local/bin +DESTINSTALLSITESCRIPT = $(DESTDIR)$(INSTALLSITESCRIPT) +INSTALLVENDORSCRIPT = /usr/bin +DESTINSTALLVENDORSCRIPT = $(DESTDIR)$(INSTALLVENDORSCRIPT) +INSTALLMAN1DIR = /usr/share/man/man1 +DESTINSTALLMAN1DIR = $(DESTDIR)$(INSTALLMAN1DIR) +INSTALLSITEMAN1DIR = /usr/local/man/man1 +DESTINSTALLSITEMAN1DIR = $(DESTDIR)$(INSTALLSITEMAN1DIR) +INSTALLVENDORMAN1DIR = /usr/share/man/man1 +DESTINSTALLVENDORMAN1DIR = $(DESTDIR)$(INSTALLVENDORMAN1DIR) +INSTALLMAN3DIR = /usr/share/man/man3 +DESTINSTALLMAN3DIR = $(DESTDIR)$(INSTALLMAN3DIR) +INSTALLSITEMAN3DIR = /usr/local/man/man3 +DESTINSTALLSITEMAN3DIR = $(DESTDIR)$(INSTALLSITEMAN3DIR) +INSTALLVENDORMAN3DIR = /usr/share/man/man3 +DESTINSTALLVENDORMAN3DIR = $(DESTDIR)$(INSTALLVENDORMAN3DIR) +PERL_LIB = /usr/share/perl/5.40 +PERL_ARCHLIB = /usr/lib/x86_64-linux-gnu/perl/5.40 +PERL_ARCHLIBDEP = /usr/lib/x86_64-linux-gnu/perl/5.40 +LIBPERL_A = libperl.a +FIRST_MAKEFILE = Makefile +MAKEFILE_OLD = Makefile.old +MAKE_APERL_FILE = Makefile.aperl +PERLMAINCC = $(CC) +PERL_INC = /usr/lib/x86_64-linux-gnu/perl/5.40/CORE +PERL_INCDEP = /usr/lib/x86_64-linux-gnu/perl/5.40/CORE +PERL = "/usr/bin/perl" +FULLPERL = "/usr/bin/perl" +ABSPERL = $(PERL) +PERLRUN = $(PERL) +FULLPERLRUN = $(FULLPERL) +ABSPERLRUN = $(ABSPERL) +PERLRUNINST = $(PERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)" +FULLPERLRUNINST = $(FULLPERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)" +ABSPERLRUNINST = $(ABSPERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)" +PERL_CORE = 0 +PERM_DIR = 755 +PERM_RW = 644 +PERM_RWX = 755 + +MAKEMAKER = /usr/share/perl/5.40/ExtUtils/MakeMaker.pm +MM_VERSION = 7.70 +MM_REVISION = 77000 + +# FULLEXT = Pathname for extension directory (eg Foo/Bar/Oracle). +# BASEEXT = Basename part of FULLEXT. May be just equal FULLEXT. (eg Oracle) +# PARENT_NAME = NAME without BASEEXT and no trailing :: (eg Foo::Bar) +# DLBASE = Basename part of dynamic library. May be just equal BASEEXT. +MAKE = make +FULLEXT = Net/LPM +BASEEXT = LPM +PARENT_NAME = Net +DLBASE = $(BASEEXT) +VERSION_FROM = lib/Net/LPM.pm +INC = -I/usr/local/include/lpm +OBJECT = LPM$(OBJ_EXT) +LDFROM = $(OBJECT) +LINKTYPE = dynamic +BOOTDEP = + +# Handy lists of source code files: +XS_FILES = LPM.xs +C_FILES = LPM.c +O_FILES = LPM.o +H_FILES = +MAN1PODS = +MAN3PODS = lib/Net/LPM.pm + +# Where is the Config information that we are using/depend on +CONFIGDEP = $(PERL_ARCHLIBDEP)$(DFSEP)Config.pm $(PERL_INCDEP)$(DFSEP)config.h + +# Where to build things +INST_LIBDIR = $(INST_LIB)/Net +INST_ARCHLIBDIR = $(INST_ARCHLIB)/Net + +INST_AUTODIR = $(INST_LIB)/auto/$(FULLEXT) +INST_ARCHAUTODIR = $(INST_ARCHLIB)/auto/$(FULLEXT) + +INST_STATIC = $(INST_ARCHAUTODIR)/$(BASEEXT)$(LIB_EXT) +INST_DYNAMIC = $(INST_ARCHAUTODIR)/$(DLBASE).$(DLEXT) +INST_BOOT = $(INST_ARCHAUTODIR)/$(BASEEXT).bs + +# Extra linker info +EXPORT_LIST = +PERL_ARCHIVE = +PERL_ARCHIVEDEP = +PERL_ARCHIVE_AFTER = + + +TO_INST_PM = lib/Net/LPM.pm + + +# --- MakeMaker platform_constants section: +MM_Unix_VERSION = 7.70 +PERL_MALLOC_DEF = -DPERL_EXTMALLOC_DEF -Dmalloc=Perl_malloc -Dfree=Perl_mfree -Drealloc=Perl_realloc -Dcalloc=Perl_calloc + + +# --- MakeMaker tool_autosplit section: +# Usage: $(AUTOSPLITFILE) FileToSplit AutoDirToSplitInto +AUTOSPLITFILE = $(ABSPERLRUN) -e 'use AutoSplit; autosplit($$$$ARGV[0], $$$$ARGV[1], 0, 1, 1)' -- + + + +# --- MakeMaker tool_xsubpp section: + +XSUBPPDIR = /usr/share/perl/5.40/ExtUtils +XSUBPP = "$(XSUBPPDIR)$(DFSEP)xsubpp" +XSUBPPRUN = $(PERLRUN) $(XSUBPP) +XSPROTOARG = +XSUBPPDEPS = /usr/share/perl/5.40/ExtUtils/typemap typemap typemap /usr/share/perl/5.40/ExtUtils$(DFSEP)xsubpp +XSUBPPARGS = -typemap '/usr/share/perl/5.40/ExtUtils/typemap' -typemap '/home/murilo/Github/liblpm/bindings/perl/typemap' -typemap '/home/murilo/Github/liblpm/bindings/perl/typemap' +XSUBPP_EXTRA_ARGS = + + +# --- MakeMaker tools_other section: +SHELL = /bin/sh +CHMOD = chmod +CP = cp +MV = mv +NOOP = $(TRUE) +NOECHO = @ +RM_F = rm -f +RM_RF = rm -rf +TEST_F = test -f +TOUCH = touch +UMASK_NULL = umask 0 +DEV_NULL = > /dev/null 2>&1 +MKPATH = $(ABSPERLRUN) -MExtUtils::Command -e 'mkpath' -- +EQUALIZE_TIMESTAMP = $(ABSPERLRUN) -MExtUtils::Command -e 'eqtime' -- +FALSE = false +TRUE = true +ECHO = echo +ECHO_N = echo -n +UNINST = 0 +VERBINST = 0 +MOD_INSTALL = $(ABSPERLRUN) -MExtUtils::Install -e 'install([ from_to => {@ARGV}, verbose => '\''$(VERBINST)'\'', uninstall_shadows => '\''$(UNINST)'\'', dir_mode => '\''$(PERM_DIR)'\'' ]);' -- +DOC_INSTALL = $(ABSPERLRUN) -MExtUtils::Command::MM -e 'perllocal_install' -- +UNINSTALL = $(ABSPERLRUN) -MExtUtils::Command::MM -e 'uninstall' -- +WARN_IF_OLD_PACKLIST = $(ABSPERLRUN) -MExtUtils::Command::MM -e 'warn_if_old_packlist' -- +MACROSTART = +MACROEND = +USEMAKEFILE = -f +FIXIN = $(ABSPERLRUN) -MExtUtils::MY -e 'MY->fixin(shift)' -- +CP_NONEMPTY = $(ABSPERLRUN) -MExtUtils::Command::MM -e 'cp_nonempty' -- + + +# --- MakeMaker makemakerdflt section: +makemakerdflt : all + $(NOECHO) $(NOOP) + + +# --- MakeMaker dist section: +TAR = tar +TARFLAGS = cvf +ZIP = zip +ZIPFLAGS = -r +COMPRESS = gzip --best +SUFFIX = .gz +SHAR = shar +PREOP = $(NOECHO) $(NOOP) +POSTOP = $(NOECHO) $(NOOP) +TO_UNIX = $(NOECHO) $(NOOP) +CI = ci -u +RCS_LABEL = rcs -Nv$(VERSION_SYM): -q +DIST_CP = best +DIST_DEFAULT = tardist +DISTNAME = Net-LPM +DISTVNAME = Net-LPM-2.0.0 + + +# --- MakeMaker macro section: + + +# --- MakeMaker depend section: + + +# --- MakeMaker cflags section: + +CCFLAGS = -D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fwrapv -fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -Wall +OPTIMIZE = -O2 -g +PERLTYPE = +MPOLLUTE = + + +# --- MakeMaker const_loadlibs section: + +# Net::LPM might depend on some other libraries: +# See ExtUtils::Liblist for details +# +EXTRALIBS = -L/usr/local/lib -llpm +LDLOADLIBS = -L/usr/local/lib -llpm +BSLOADLIBS = + + +# --- MakeMaker const_cccmd section: +CCCMD = $(CC) -c $(PASTHRU_INC) $(INC) \ + $(CCFLAGS) $(OPTIMIZE) \ + $(PERLTYPE) $(MPOLLUTE) $(DEFINE_VERSION) \ + $(XS_DEFINE_VERSION) + +# --- MakeMaker post_constants section: + + +# --- MakeMaker pasthru section: + +PASTHRU = LIBPERL_A="$(LIBPERL_A)"\ + LINKTYPE="$(LINKTYPE)"\ + OPTIMIZE="$(OPTIMIZE)"\ + LD="$(LD)"\ + PREFIX="$(PREFIX)"\ + PASTHRU_DEFINE='$(DEFINE) $(PASTHRU_DEFINE)'\ + PASTHRU_INC='-I/usr/local/include/lpm $(PASTHRU_INC)' + + +# --- MakeMaker special_targets section: +.SUFFIXES : .xs .c .C .cpp .i .s .cxx .cc $(OBJ_EXT) + +.PHONY: all config static dynamic test linkext manifest blibdirs clean realclean disttest distdir pure_all subdirs clean_subdirs makemakerdflt manifypods realclean_subdirs subdirs_dynamic subdirs_pure_nolink subdirs_static subdirs-test_dynamic subdirs-test_static test_dynamic test_static + + + +# --- MakeMaker c_o section: + +.c.i: + $(CPPRUN) -c $(PASTHRU_INC) $(INC) \ + $(CCFLAGS) $(OPTIMIZE) \ + $(PERLTYPE) $(MPOLLUTE) $(DEFINE_VERSION) \ + $(XS_DEFINE_VERSION) $(CCCDLFLAGS) "-I$(PERL_INC)" $(PASTHRU_DEFINE) $(DEFINE) $*.c > $*.i + +.c.s : + $(CCCMD) -S $(CCCDLFLAGS) "-I$(PERL_INC)" $(PASTHRU_DEFINE) $(DEFINE) $*.c + +.c$(OBJ_EXT) : + $(CCCMD) $(CCCDLFLAGS) "-I$(PERL_INC)" $(PASTHRU_DEFINE) $(DEFINE) $*.c + +.cpp$(OBJ_EXT) : + $(CCCMD) $(CCCDLFLAGS) "-I$(PERL_INC)" $(PASTHRU_DEFINE) $(DEFINE) $*.cpp + +.cxx$(OBJ_EXT) : + $(CCCMD) $(CCCDLFLAGS) "-I$(PERL_INC)" $(PASTHRU_DEFINE) $(DEFINE) $*.cxx + +.cc$(OBJ_EXT) : + $(CCCMD) $(CCCDLFLAGS) "-I$(PERL_INC)" $(PASTHRU_DEFINE) $(DEFINE) $*.cc + +.C$(OBJ_EXT) : + $(CCCMD) $(CCCDLFLAGS) "-I$(PERL_INC)" $(PASTHRU_DEFINE) $(DEFINE) $*.C + + +# --- MakeMaker xs_c section: + +.xs.c: + $(XSUBPPRUN) $(XSPROTOARG) $(XSUBPPARGS) $(XSUBPP_EXTRA_ARGS) $*.xs > $*.xsc + $(MV) $*.xsc $*.c + + +# --- MakeMaker xs_o section: +.xs$(OBJ_EXT) : + $(XSUBPPRUN) $(XSPROTOARG) $(XSUBPPARGS) $*.xs > $*.xsc + $(MV) $*.xsc $*.c + $(CCCMD) $(CCCDLFLAGS) "-I$(PERL_INC)" $(PASTHRU_DEFINE) $(DEFINE) $*.c + + +# --- MakeMaker top_targets section: +all :: pure_all manifypods + $(NOECHO) $(NOOP) + +pure_all :: config pm_to_blib subdirs linkext + $(NOECHO) $(NOOP) + +subdirs :: $(MYEXTLIB) + $(NOECHO) $(NOOP) + +config :: $(FIRST_MAKEFILE) blibdirs + $(NOECHO) $(NOOP) + +help : + perldoc ExtUtils::MakeMaker + + +# --- MakeMaker blibdirs section: +blibdirs : $(INST_LIBDIR)$(DFSEP).exists $(INST_ARCHLIB)$(DFSEP).exists $(INST_AUTODIR)$(DFSEP).exists $(INST_ARCHAUTODIR)$(DFSEP).exists $(INST_BIN)$(DFSEP).exists $(INST_SCRIPT)$(DFSEP).exists $(INST_MAN1DIR)$(DFSEP).exists $(INST_MAN3DIR)$(DFSEP).exists + $(NOECHO) $(NOOP) + +# Backwards compat with 6.18 through 6.25 +blibdirs.ts : blibdirs + $(NOECHO) $(NOOP) + +$(INST_LIBDIR)$(DFSEP).exists :: Makefile.PL + $(NOECHO) $(MKPATH) $(INST_LIBDIR) + $(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_LIBDIR) + $(NOECHO) $(TOUCH) $(INST_LIBDIR)$(DFSEP).exists + +$(INST_ARCHLIB)$(DFSEP).exists :: Makefile.PL + $(NOECHO) $(MKPATH) $(INST_ARCHLIB) + $(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_ARCHLIB) + $(NOECHO) $(TOUCH) $(INST_ARCHLIB)$(DFSEP).exists + +$(INST_AUTODIR)$(DFSEP).exists :: Makefile.PL + $(NOECHO) $(MKPATH) $(INST_AUTODIR) + $(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_AUTODIR) + $(NOECHO) $(TOUCH) $(INST_AUTODIR)$(DFSEP).exists + +$(INST_ARCHAUTODIR)$(DFSEP).exists :: Makefile.PL + $(NOECHO) $(MKPATH) $(INST_ARCHAUTODIR) + $(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_ARCHAUTODIR) + $(NOECHO) $(TOUCH) $(INST_ARCHAUTODIR)$(DFSEP).exists + +$(INST_BIN)$(DFSEP).exists :: Makefile.PL + $(NOECHO) $(MKPATH) $(INST_BIN) + $(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_BIN) + $(NOECHO) $(TOUCH) $(INST_BIN)$(DFSEP).exists + +$(INST_SCRIPT)$(DFSEP).exists :: Makefile.PL + $(NOECHO) $(MKPATH) $(INST_SCRIPT) + $(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_SCRIPT) + $(NOECHO) $(TOUCH) $(INST_SCRIPT)$(DFSEP).exists + +$(INST_MAN1DIR)$(DFSEP).exists :: Makefile.PL + $(NOECHO) $(MKPATH) $(INST_MAN1DIR) + $(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_MAN1DIR) + $(NOECHO) $(TOUCH) $(INST_MAN1DIR)$(DFSEP).exists + +$(INST_MAN3DIR)$(DFSEP).exists :: Makefile.PL + $(NOECHO) $(MKPATH) $(INST_MAN3DIR) + $(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_MAN3DIR) + $(NOECHO) $(TOUCH) $(INST_MAN3DIR)$(DFSEP).exists + + + +# --- MakeMaker linkext section: + +linkext :: dynamic + $(NOECHO) $(NOOP) + + +# --- MakeMaker dlsyms section: + + +# --- MakeMaker dynamic_bs section: +BOOTSTRAP = $(BASEEXT).bs + +# As Mkbootstrap might not write a file (if none is required) +# we use touch to prevent make continually trying to remake it. +# The DynaLoader only reads a non-empty file. +$(BASEEXT).bs : $(FIRST_MAKEFILE) $(BOOTDEP) + $(NOECHO) $(ECHO) "Running Mkbootstrap for $(BASEEXT) ($(BSLOADLIBS))" + $(NOECHO) $(PERLRUN) \ + "-MExtUtils::Mkbootstrap" \ + -e "Mkbootstrap('$(BASEEXT)','$(BSLOADLIBS)');" + $(NOECHO) $(TOUCH) "$(BASEEXT).bs" + $(CHMOD) $(PERM_RW) "$(BASEEXT).bs" + +$(INST_ARCHAUTODIR)/$(BASEEXT).bs : $(BASEEXT).bs $(INST_ARCHAUTODIR)$(DFSEP).exists + $(NOECHO) $(RM_RF) $(INST_ARCHAUTODIR)/$(BASEEXT).bs + - $(CP_NONEMPTY) $(BASEEXT).bs $(INST_ARCHAUTODIR)/$(BASEEXT).bs $(PERM_RW) + + +# --- MakeMaker dynamic section: + +dynamic :: $(FIRST_MAKEFILE) config $(INST_BOOT) $(INST_DYNAMIC) + $(NOECHO) $(NOOP) + + +# --- MakeMaker dynamic_lib section: +# This section creates the dynamically loadable objects from relevant +# objects and possibly $(MYEXTLIB). +ARMAYBE = : +OTHERLDFLAGS = +INST_DYNAMIC_DEP = +INST_DYNAMIC_FIX = + +$(INST_DYNAMIC) : $(OBJECT) $(MYEXTLIB) $(INST_ARCHAUTODIR)$(DFSEP).exists $(EXPORT_LIST) $(PERL_ARCHIVEDEP) $(PERL_ARCHIVE_AFTER) $(INST_DYNAMIC_DEP) + $(RM_F) $@ + $(LD) $(LDDLFLAGS) $(LDFROM) $(OTHERLDFLAGS) -o $@ $(MYEXTLIB) \ + $(PERL_ARCHIVE) $(LDLOADLIBS) $(PERL_ARCHIVE_AFTER) $(EXPORT_LIST) \ + $(INST_DYNAMIC_FIX) + $(CHMOD) $(PERM_RWX) $@ + + +# --- MakeMaker static section: + +## $(INST_PM) has been moved to the all: target. +## It remains here for awhile to allow for old usage: "make static" +static :: $(FIRST_MAKEFILE) $(INST_STATIC) + $(NOECHO) $(NOOP) + + +# --- MakeMaker static_lib section: +$(INST_STATIC): $(OBJECT) $(MYEXTLIB) $(INST_ARCHAUTODIR)$(DFSEP).exists + $(RM_F) "$@" + $(FULL_AR) $(AR_STATIC_ARGS) "$@" $(OBJECT) + $(RANLIB) "$@" + $(CHMOD) $(PERM_RWX) $@ + $(NOECHO) $(ECHO) "$(EXTRALIBS)" > $(INST_ARCHAUTODIR)$(DFSEP)extralibs.ld + + +# --- MakeMaker manifypods section: + +POD2MAN_EXE = $(PERLRUN) "-MExtUtils::Command::MM" -e pod2man "--" +POD2MAN = $(POD2MAN_EXE) + + +manifypods : pure_all config \ + lib/Net/LPM.pm + $(NOECHO) $(POD2MAN) --section=$(MAN3EXT) --perm_rw=$(PERM_RW) -u \ + lib/Net/LPM.pm $(INST_MAN3DIR)/Net::LPM.$(MAN3EXT) + + + + +# --- MakeMaker processPL section: + + +# --- MakeMaker installbin section: + + +# --- MakeMaker subdirs section: + +# none + +# --- MakeMaker clean_subdirs section: +clean_subdirs : + $(NOECHO) $(NOOP) + + +# --- MakeMaker clean section: + +# Delete temporary files but do not touch installed files. We don't delete +# the Makefile here so a later make realclean still has a makefile to use. + +clean :: clean_subdirs + - $(RM_F) \ + $(BASEEXT).bso $(BASEEXT).def \ + $(BASEEXT).exp $(BASEEXT).x \ + $(BOOTSTRAP) $(INST_ARCHAUTODIR)/extralibs.all \ + $(INST_ARCHAUTODIR)/extralibs.ld $(MAKE_APERL_FILE) \ + *$(LIB_EXT) *$(OBJ_EXT) \ + *perl.core LPM.base \ + LPM.bs LPM.bso \ + LPM.c LPM.def \ + LPM.exp LPM.o \ + LPM_def.old MYMETA.json \ + MYMETA.yml blibdirs.ts \ + core core.*perl.*.? \ + core.[0-9] core.[0-9][0-9] \ + core.[0-9][0-9][0-9] core.[0-9][0-9][0-9][0-9] \ + core.[0-9][0-9][0-9][0-9][0-9] lib$(BASEEXT).def \ + mon.out perl \ + perl$(EXE_EXT) perl.exe \ + perlmain.c pm_to_blib \ + pm_to_blib.ts so_locations \ + tmon.out + - $(RM_RF) \ + *.c *.o \ + Net-LPM-* blib + $(NOECHO) $(RM_F) $(MAKEFILE_OLD) + - $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD) $(DEV_NULL) + + +# --- MakeMaker realclean_subdirs section: +# so clean is forced to complete before realclean_subdirs runs +realclean_subdirs : clean + $(NOECHO) $(NOOP) + + +# --- MakeMaker realclean section: +# Delete temporary files (via clean) and also delete dist files +realclean purge :: realclean_subdirs + - $(RM_F) \ + $(FIRST_MAKEFILE) $(MAKEFILE_OLD) \ + $(OBJECT) + - $(RM_RF) \ + $(DISTVNAME) + + +# --- MakeMaker metafile section: +metafile : create_distdir + $(NOECHO) $(ECHO) Generating META.yml + $(NOECHO) $(ECHO) '---' > META_new.yml + $(NOECHO) $(ECHO) 'abstract: '\''Perl bindings for high-performance liblpm Longest Prefix Match library'\''' >> META_new.yml + $(NOECHO) $(ECHO) 'author:' >> META_new.yml + $(NOECHO) $(ECHO) ' - '\''Murilo Chianfa '\''' >> META_new.yml + $(NOECHO) $(ECHO) 'build_requires:' >> META_new.yml + $(NOECHO) $(ECHO) ' ExtUtils::MakeMaker: '\''6.64'\''' >> META_new.yml + $(NOECHO) $(ECHO) ' Test::More: '\''0.98'\''' >> META_new.yml + $(NOECHO) $(ECHO) 'configure_requires:' >> META_new.yml + $(NOECHO) $(ECHO) ' ExtUtils::MakeMaker: '\''6.64'\''' >> META_new.yml + $(NOECHO) $(ECHO) 'dynamic_config: 1' >> META_new.yml + $(NOECHO) $(ECHO) 'generated_by: '\''ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010'\''' >> META_new.yml + $(NOECHO) $(ECHO) 'keywords:' >> META_new.yml + $(NOECHO) $(ECHO) ' - lpm' >> META_new.yml + $(NOECHO) $(ECHO) ' - longest-prefix-match' >> META_new.yml + $(NOECHO) $(ECHO) ' - routing' >> META_new.yml + $(NOECHO) $(ECHO) ' - ipv4' >> META_new.yml + $(NOECHO) $(ECHO) ' - ipv6' >> META_new.yml + $(NOECHO) $(ECHO) ' - networking' >> META_new.yml + $(NOECHO) $(ECHO) 'license: open_source' >> META_new.yml + $(NOECHO) $(ECHO) 'meta-spec:' >> META_new.yml + $(NOECHO) $(ECHO) ' url: http://module-build.sourceforge.net/META-spec-v1.4.html' >> META_new.yml + $(NOECHO) $(ECHO) ' version: '\''1.4'\''' >> META_new.yml + $(NOECHO) $(ECHO) 'name: Net-LPM' >> META_new.yml + $(NOECHO) $(ECHO) 'no_index:' >> META_new.yml + $(NOECHO) $(ECHO) ' directory:' >> META_new.yml + $(NOECHO) $(ECHO) ' - t' >> META_new.yml + $(NOECHO) $(ECHO) ' - inc' >> META_new.yml + $(NOECHO) $(ECHO) 'requires:' >> META_new.yml + $(NOECHO) $(ECHO) ' Carp: '\''0'\''' >> META_new.yml + $(NOECHO) $(ECHO) ' Exporter: '\''0'\''' >> META_new.yml + $(NOECHO) $(ECHO) ' XSLoader: '\''0'\''' >> META_new.yml + $(NOECHO) $(ECHO) ' perl: '\''5.010'\''' >> META_new.yml + $(NOECHO) $(ECHO) 'resources:' >> META_new.yml + $(NOECHO) $(ECHO) ' bugtracker: https://github.com/MuriloChianfa/liblpm/issues' >> META_new.yml + $(NOECHO) $(ECHO) ' homepage: https://github.com/MuriloChianfa/liblpm' >> META_new.yml + $(NOECHO) $(ECHO) ' repository: https://github.com/MuriloChianfa/liblpm.git' >> META_new.yml + $(NOECHO) $(ECHO) 'version: v2.0.0' >> META_new.yml + $(NOECHO) $(ECHO) 'x_serialization_backend: '\''CPAN::Meta::YAML version 0.018'\''' >> META_new.yml + -$(NOECHO) $(MV) META_new.yml $(DISTVNAME)/META.yml + $(NOECHO) $(ECHO) Generating META.json + $(NOECHO) $(ECHO) '{' > META_new.json + $(NOECHO) $(ECHO) ' "abstract" : "Perl bindings for high-performance liblpm Longest Prefix Match library",' >> META_new.json + $(NOECHO) $(ECHO) ' "author" : [' >> META_new.json + $(NOECHO) $(ECHO) ' "Murilo Chianfa "' >> META_new.json + $(NOECHO) $(ECHO) ' ],' >> META_new.json + $(NOECHO) $(ECHO) ' "dynamic_config" : 1,' >> META_new.json + $(NOECHO) $(ECHO) ' "generated_by" : "ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010",' >> META_new.json + $(NOECHO) $(ECHO) ' "keywords" : [' >> META_new.json + $(NOECHO) $(ECHO) ' "lpm",' >> META_new.json + $(NOECHO) $(ECHO) ' "longest-prefix-match",' >> META_new.json + $(NOECHO) $(ECHO) ' "routing",' >> META_new.json + $(NOECHO) $(ECHO) ' "ipv4",' >> META_new.json + $(NOECHO) $(ECHO) ' "ipv6",' >> META_new.json + $(NOECHO) $(ECHO) ' "networking"' >> META_new.json + $(NOECHO) $(ECHO) ' ],' >> META_new.json + $(NOECHO) $(ECHO) ' "license" : [' >> META_new.json + $(NOECHO) $(ECHO) ' "open_source"' >> META_new.json + $(NOECHO) $(ECHO) ' ],' >> META_new.json + $(NOECHO) $(ECHO) ' "meta-spec" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",' >> META_new.json + $(NOECHO) $(ECHO) ' "version" : 2' >> META_new.json + $(NOECHO) $(ECHO) ' },' >> META_new.json + $(NOECHO) $(ECHO) ' "name" : "Net-LPM",' >> META_new.json + $(NOECHO) $(ECHO) ' "no_index" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "directory" : [' >> META_new.json + $(NOECHO) $(ECHO) ' "t",' >> META_new.json + $(NOECHO) $(ECHO) ' "inc"' >> META_new.json + $(NOECHO) $(ECHO) ' ]' >> META_new.json + $(NOECHO) $(ECHO) ' },' >> META_new.json + $(NOECHO) $(ECHO) ' "prereqs" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "build" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "requires" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "ExtUtils::MakeMaker" : "6.64"' >> META_new.json + $(NOECHO) $(ECHO) ' }' >> META_new.json + $(NOECHO) $(ECHO) ' },' >> META_new.json + $(NOECHO) $(ECHO) ' "configure" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "requires" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "ExtUtils::MakeMaker" : "6.64"' >> META_new.json + $(NOECHO) $(ECHO) ' }' >> META_new.json + $(NOECHO) $(ECHO) ' },' >> META_new.json + $(NOECHO) $(ECHO) ' "runtime" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "requires" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "Carp" : "0",' >> META_new.json + $(NOECHO) $(ECHO) ' "Exporter" : "0",' >> META_new.json + $(NOECHO) $(ECHO) ' "XSLoader" : "0",' >> META_new.json + $(NOECHO) $(ECHO) ' "perl" : "5.010"' >> META_new.json + $(NOECHO) $(ECHO) ' }' >> META_new.json + $(NOECHO) $(ECHO) ' },' >> META_new.json + $(NOECHO) $(ECHO) ' "test" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "requires" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "Test::More" : "0.98"' >> META_new.json + $(NOECHO) $(ECHO) ' }' >> META_new.json + $(NOECHO) $(ECHO) ' }' >> META_new.json + $(NOECHO) $(ECHO) ' },' >> META_new.json + $(NOECHO) $(ECHO) ' "release_status" : "stable",' >> META_new.json + $(NOECHO) $(ECHO) ' "resources" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "bugtracker" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "web" : "https://github.com/MuriloChianfa/liblpm/issues"' >> META_new.json + $(NOECHO) $(ECHO) ' },' >> META_new.json + $(NOECHO) $(ECHO) ' "homepage" : "https://github.com/MuriloChianfa/liblpm",' >> META_new.json + $(NOECHO) $(ECHO) ' "repository" : {' >> META_new.json + $(NOECHO) $(ECHO) ' "type" : "git",' >> META_new.json + $(NOECHO) $(ECHO) ' "url" : "https://github.com/MuriloChianfa/liblpm.git",' >> META_new.json + $(NOECHO) $(ECHO) ' "web" : "https://github.com/MuriloChianfa/liblpm"' >> META_new.json + $(NOECHO) $(ECHO) ' }' >> META_new.json + $(NOECHO) $(ECHO) ' },' >> META_new.json + $(NOECHO) $(ECHO) ' "version" : "v2.0.0",' >> META_new.json + $(NOECHO) $(ECHO) ' "x_serialization_backend" : "JSON::PP version 4.16"' >> META_new.json + $(NOECHO) $(ECHO) '}' >> META_new.json + -$(NOECHO) $(MV) META_new.json $(DISTVNAME)/META.json + + +# --- MakeMaker signature section: +signature : + cpansign -s + + +# --- MakeMaker dist_basics section: +distclean :: realclean distcheck + $(NOECHO) $(NOOP) + +distcheck : + $(PERLRUN) "-MExtUtils::Manifest=fullcheck" -e fullcheck + +skipcheck : + $(PERLRUN) "-MExtUtils::Manifest=skipcheck" -e skipcheck + +manifest : + $(PERLRUN) "-MExtUtils::Manifest=mkmanifest" -e mkmanifest + +veryclean : realclean + $(RM_F) *~ */*~ *.orig */*.orig *.bak */*.bak *.old */*.old + + + +# --- MakeMaker dist_core section: + +dist : $(DIST_DEFAULT) $(FIRST_MAKEFILE) + $(NOECHO) $(ABSPERLRUN) -l -e 'print '\''Warning: Makefile possibly out of date with $(VERSION_FROM)'\''' \ + -e ' if -e '\''$(VERSION_FROM)'\'' and -M '\''$(VERSION_FROM)'\'' < -M '\''$(FIRST_MAKEFILE)'\'';' -- + +tardist : $(DISTVNAME).tar$(SUFFIX) + $(NOECHO) $(NOOP) + +uutardist : $(DISTVNAME).tar$(SUFFIX) + uuencode $(DISTVNAME).tar$(SUFFIX) $(DISTVNAME).tar$(SUFFIX) > $(DISTVNAME).tar$(SUFFIX)_uu + $(NOECHO) $(ECHO) 'Created $(DISTVNAME).tar$(SUFFIX)_uu' + +$(DISTVNAME).tar$(SUFFIX) : distdir + $(PREOP) + $(TO_UNIX) + $(TAR) $(TARFLAGS) $(DISTVNAME).tar $(DISTVNAME) + $(RM_RF) $(DISTVNAME) + $(COMPRESS) $(DISTVNAME).tar + $(NOECHO) $(ECHO) 'Created $(DISTVNAME).tar$(SUFFIX)' + $(POSTOP) + +zipdist : $(DISTVNAME).zip + $(NOECHO) $(NOOP) + +$(DISTVNAME).zip : distdir + $(PREOP) + $(ZIP) $(ZIPFLAGS) $(DISTVNAME).zip $(DISTVNAME) + $(RM_RF) $(DISTVNAME) + $(NOECHO) $(ECHO) 'Created $(DISTVNAME).zip' + $(POSTOP) + +shdist : distdir + $(PREOP) + $(SHAR) $(DISTVNAME) > $(DISTVNAME).shar + $(RM_RF) $(DISTVNAME) + $(NOECHO) $(ECHO) 'Created $(DISTVNAME).shar' + $(POSTOP) + + +# --- MakeMaker distdir section: +create_distdir : + $(RM_RF) $(DISTVNAME) + $(PERLRUN) "-MExtUtils::Manifest=manicopy,maniread" \ + -e "manicopy(maniread(),'$(DISTVNAME)', '$(DIST_CP)');" + +distdir : create_distdir distmeta + $(NOECHO) $(NOOP) + + + +# --- MakeMaker dist_test section: +disttest : distdir + cd $(DISTVNAME) && $(ABSPERLRUN) Makefile.PL + cd $(DISTVNAME) && $(MAKE) $(PASTHRU) + cd $(DISTVNAME) && $(MAKE) test $(PASTHRU) + + + +# --- MakeMaker dist_ci section: +ci : + $(ABSPERLRUN) -MExtUtils::Manifest=maniread -e '@all = sort keys %{ maniread() };' \ + -e 'print(qq{Executing $(CI) @all\n});' \ + -e 'system(qq{$(CI) @all}) == 0 or die $$!;' \ + -e 'print(qq{Executing $(RCS_LABEL) ...\n});' \ + -e 'system(qq{$(RCS_LABEL) @all}) == 0 or die $$!;' -- + + +# --- MakeMaker distmeta section: +distmeta : create_distdir metafile + $(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e 'exit unless -e q{META.yml};' \ + -e 'eval { maniadd({q{META.yml} => q{Module YAML meta-data (added by MakeMaker)}}) }' \ + -e ' or die "Could not add META.yml to MANIFEST: $${'\''@'\''}"' -- + $(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e 'exit unless -f q{META.json};' \ + -e 'eval { maniadd({q{META.json} => q{Module JSON meta-data (added by MakeMaker)}}) }' \ + -e ' or die "Could not add META.json to MANIFEST: $${'\''@'\''}"' -- + + + +# --- MakeMaker distsignature section: +distsignature : distmeta + $(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e 'eval { maniadd({q{SIGNATURE} => q{Public-key signature (added by MakeMaker)}}) }' \ + -e ' or die "Could not add SIGNATURE to MANIFEST: $${'\''@'\''}"' -- + $(NOECHO) cd $(DISTVNAME) && $(TOUCH) SIGNATURE + cd $(DISTVNAME) && cpansign -s + + + +# --- MakeMaker install section: + +install :: pure_install doc_install + $(NOECHO) $(NOOP) + +install_perl :: pure_perl_install doc_perl_install + $(NOECHO) $(NOOP) + +install_site :: pure_site_install doc_site_install + $(NOECHO) $(NOOP) + +install_vendor :: pure_vendor_install doc_vendor_install + $(NOECHO) $(NOOP) + +pure_install :: pure_$(INSTALLDIRS)_install + $(NOECHO) $(NOOP) + +doc_install :: doc_$(INSTALLDIRS)_install + $(NOECHO) $(NOOP) + +pure__install : pure_site_install + $(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site + +doc__install : doc_site_install + $(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site + +pure_perl_install :: all + $(NOECHO) umask 022; $(MOD_INSTALL) \ + "$(INST_LIB)" "$(DESTINSTALLPRIVLIB)" \ + "$(INST_ARCHLIB)" "$(DESTINSTALLARCHLIB)" \ + "$(INST_BIN)" "$(DESTINSTALLBIN)" \ + "$(INST_SCRIPT)" "$(DESTINSTALLSCRIPT)" \ + "$(INST_MAN1DIR)" "$(DESTINSTALLMAN1DIR)" \ + "$(INST_MAN3DIR)" "$(DESTINSTALLMAN3DIR)" + $(NOECHO) $(WARN_IF_OLD_PACKLIST) \ + "$(SITEARCHEXP)/auto/$(FULLEXT)" + + +pure_site_install :: all + $(NOECHO) umask 02; $(MOD_INSTALL) \ + read "$(SITEARCHEXP)/auto/$(FULLEXT)/.packlist" \ + write "$(DESTINSTALLSITEARCH)/auto/$(FULLEXT)/.packlist" \ + "$(INST_LIB)" "$(DESTINSTALLSITELIB)" \ + "$(INST_ARCHLIB)" "$(DESTINSTALLSITEARCH)" \ + "$(INST_BIN)" "$(DESTINSTALLSITEBIN)" \ + "$(INST_SCRIPT)" "$(DESTINSTALLSITESCRIPT)" \ + "$(INST_MAN1DIR)" "$(DESTINSTALLSITEMAN1DIR)" \ + "$(INST_MAN3DIR)" "$(DESTINSTALLSITEMAN3DIR)" + $(NOECHO) $(WARN_IF_OLD_PACKLIST) \ + "$(PERL_ARCHLIB)/auto/$(FULLEXT)" + +pure_vendor_install :: all + $(NOECHO) umask 022; $(MOD_INSTALL) \ + "$(INST_LIB)" "$(DESTINSTALLVENDORLIB)" \ + "$(INST_ARCHLIB)" "$(DESTINSTALLVENDORARCH)" \ + "$(INST_BIN)" "$(DESTINSTALLVENDORBIN)" \ + "$(INST_SCRIPT)" "$(DESTINSTALLVENDORSCRIPT)" \ + "$(INST_MAN1DIR)" "$(DESTINSTALLVENDORMAN1DIR)" \ + "$(INST_MAN3DIR)" "$(DESTINSTALLVENDORMAN3DIR)" + + +doc_perl_install :: all + +doc_site_install :: all + $(NOECHO) $(ECHO) Appending installation info to "$(DESTINSTALLSITEARCH)/perllocal.pod" + -$(NOECHO) umask 02; $(MKPATH) "$(DESTINSTALLSITEARCH)" + -$(NOECHO) umask 02; $(DOC_INSTALL) \ + "Module" "$(NAME)" \ + "installed into" "$(INSTALLSITELIB)" \ + LINKTYPE "$(LINKTYPE)" \ + VERSION "$(VERSION)" \ + EXE_FILES "$(EXE_FILES)" \ + >> "$(DESTINSTALLSITEARCH)/perllocal.pod" + +doc_vendor_install :: all + + +uninstall :: uninstall_from_$(INSTALLDIRS)dirs + $(NOECHO) $(NOOP) + +uninstall_from_perldirs :: + +uninstall_from_sitedirs :: + $(NOECHO) $(UNINSTALL) "$(SITEARCHEXP)/auto/$(FULLEXT)/.packlist" + +uninstall_from_vendordirs :: + + +# --- MakeMaker force section: +# Phony target to force checking subdirectories. +FORCE : + $(NOECHO) $(NOOP) + + +# --- MakeMaker perldepend section: +PERL_HDRS = \ + $(PERL_INCDEP)/EXTERN.h \ + $(PERL_INCDEP)/INTERN.h \ + $(PERL_INCDEP)/XSUB.h \ + $(PERL_INCDEP)/av.h \ + $(PERL_INCDEP)/bitcount.h \ + $(PERL_INCDEP)/charclass_invlists.h \ + $(PERL_INCDEP)/config.h \ + $(PERL_INCDEP)/cop.h \ + $(PERL_INCDEP)/cv.h \ + $(PERL_INCDEP)/dosish.h \ + $(PERL_INCDEP)/ebcdic_tables.h \ + $(PERL_INCDEP)/embed.h \ + $(PERL_INCDEP)/embedvar.h \ + $(PERL_INCDEP)/fakesdio.h \ + $(PERL_INCDEP)/feature.h \ + $(PERL_INCDEP)/form.h \ + $(PERL_INCDEP)/git_version.h \ + $(PERL_INCDEP)/gv.h \ + $(PERL_INCDEP)/handy.h \ + $(PERL_INCDEP)/hv.h \ + $(PERL_INCDEP)/hv_func.h \ + $(PERL_INCDEP)/hv_macro.h \ + $(PERL_INCDEP)/inline.h \ + $(PERL_INCDEP)/intrpvar.h \ + $(PERL_INCDEP)/invlist_inline.h \ + $(PERL_INCDEP)/iperlsys.h \ + $(PERL_INCDEP)/keywords.h \ + $(PERL_INCDEP)/l1_char_class_tab.h \ + $(PERL_INCDEP)/locale_table.h \ + $(PERL_INCDEP)/malloc_ctl.h \ + $(PERL_INCDEP)/metaconfig.h \ + $(PERL_INCDEP)/mg.h \ + $(PERL_INCDEP)/mg_data.h \ + $(PERL_INCDEP)/mg_raw.h \ + $(PERL_INCDEP)/mg_vtable.h \ + $(PERL_INCDEP)/mydtrace.h \ + $(PERL_INCDEP)/nostdio.h \ + $(PERL_INCDEP)/op.h \ + $(PERL_INCDEP)/op_reg_common.h \ + $(PERL_INCDEP)/opcode.h \ + $(PERL_INCDEP)/opnames.h \ + $(PERL_INCDEP)/overload.h \ + $(PERL_INCDEP)/pad.h \ + $(PERL_INCDEP)/parser.h \ + $(PERL_INCDEP)/patchlevel-debian.h \ + $(PERL_INCDEP)/patchlevel.h \ + $(PERL_INCDEP)/perl.h \ + $(PERL_INCDEP)/perl_inc_macro.h \ + $(PERL_INCDEP)/perl_langinfo.h \ + $(PERL_INCDEP)/perl_siphash.h \ + $(PERL_INCDEP)/perlapi.h \ + $(PERL_INCDEP)/perlio.h \ + $(PERL_INCDEP)/perliol.h \ + $(PERL_INCDEP)/perlsdio.h \ + $(PERL_INCDEP)/perlstatic.h \ + $(PERL_INCDEP)/perlvars.h \ + $(PERL_INCDEP)/perly.h \ + $(PERL_INCDEP)/pp.h \ + $(PERL_INCDEP)/pp_proto.h \ + $(PERL_INCDEP)/proto.h \ + $(PERL_INCDEP)/reentr.h \ + $(PERL_INCDEP)/regcharclass.h \ + $(PERL_INCDEP)/regcomp.h \ + $(PERL_INCDEP)/regcomp_internal.h \ + $(PERL_INCDEP)/regexp.h \ + $(PERL_INCDEP)/reginline.h \ + $(PERL_INCDEP)/regnodes.h \ + $(PERL_INCDEP)/sbox32_hash.h \ + $(PERL_INCDEP)/scope.h \ + $(PERL_INCDEP)/scope_types.h \ + $(PERL_INCDEP)/sv.h \ + $(PERL_INCDEP)/sv_inline.h \ + $(PERL_INCDEP)/thread.h \ + $(PERL_INCDEP)/time64.h \ + $(PERL_INCDEP)/time64_config.h \ + $(PERL_INCDEP)/uni_keywords.h \ + $(PERL_INCDEP)/unicode_constants.h \ + $(PERL_INCDEP)/unixish.h \ + $(PERL_INCDEP)/utf8.h \ + $(PERL_INCDEP)/utfebcdic.h \ + $(PERL_INCDEP)/util.h \ + $(PERL_INCDEP)/uudmap.h \ + $(PERL_INCDEP)/vutil.h \ + $(PERL_INCDEP)/warnings.h \ + $(PERL_INCDEP)/zaphod32_hash.h + +$(OBJECT) : $(PERL_HDRS) + +LPM.c : $(XSUBPPDEPS) + + +# --- MakeMaker makefile section: + +$(OBJECT) : $(FIRST_MAKEFILE) + +# We take a very conservative approach here, but it's worth it. +# We move Makefile to Makefile.old here to avoid gnu make looping. +$(FIRST_MAKEFILE) : Makefile.PL $(CONFIGDEP) + $(NOECHO) $(ECHO) "Makefile out-of-date with respect to $?" + $(NOECHO) $(ECHO) "Cleaning current config before rebuilding Makefile..." + -$(NOECHO) $(RM_F) $(MAKEFILE_OLD) + -$(NOECHO) $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD) + - $(MAKE) $(USEMAKEFILE) $(MAKEFILE_OLD) clean $(DEV_NULL) + $(PERLRUN) Makefile.PL + $(NOECHO) $(ECHO) "==> Your Makefile has been rebuilt. <==" + $(NOECHO) $(ECHO) "==> Please rerun the $(MAKE) command. <==" + $(FALSE) + + + +# --- MakeMaker staticmake section: + +# --- MakeMaker makeaperl section --- +MAP_TARGET = perl +FULLPERL = "/usr/bin/perl" +MAP_PERLINC = "-Iblib/arch" "-Iblib/lib" "-I/usr/lib/x86_64-linux-gnu/perl/5.40" "-I/usr/share/perl/5.40" + +$(MAP_TARGET) :: $(MAKE_APERL_FILE) + $(MAKE) $(USEMAKEFILE) $(MAKE_APERL_FILE) $@ + +$(MAKE_APERL_FILE) : static $(FIRST_MAKEFILE) pm_to_blib + $(NOECHO) $(ECHO) Writing \"$(MAKE_APERL_FILE)\" for this $(MAP_TARGET) + $(NOECHO) $(PERLRUNINST) \ + Makefile.PL DIR="" \ + MAKEFILE=$(MAKE_APERL_FILE) LINKTYPE=static \ + MAKEAPERL=1 NORECURS=1 CCCDLFLAGS= + + +# --- MakeMaker test section: +TEST_VERBOSE=0 +TEST_TYPE=test_$(LINKTYPE) +TEST_FILE = test.pl +TEST_FILES = t/*.t +TESTDB_SW = -d + +testdb :: testdb_$(LINKTYPE) + $(NOECHO) $(NOOP) + +test :: $(TEST_TYPE) + $(NOECHO) $(NOOP) + +# Occasionally we may face this degenerate target: +test_ : test_dynamic + $(NOECHO) $(NOOP) + +subdirs-test_dynamic :: dynamic pure_all + +test_dynamic :: subdirs-test_dynamic + LD_LIBRARY_PATH=../../build:$(LD_LIBRARY_PATH) \ + PERL_DL_NONLAZY=1 $(FULLPERLRUN) "-MExtUtils::Command::MM" "-MTest::Harness" "-e" "undef *Test::Harness::Switches; test_harness($(TEST_VERBOSE), '$(INST_LIB)', '$(INST_ARCHLIB)')" $(TEST_FILES) + +testdb_dynamic :: dynamic pure_all + PERL_DL_NONLAZY=1 $(FULLPERLRUN) $(TESTDB_SW) "-I$(INST_LIB)" "-I$(INST_ARCHLIB)" $(TEST_FILE) + +subdirs-test_static :: static pure_all + +test_static :: subdirs-test_static $(MAP_TARGET) + PERL_DL_NONLAZY=1 "/home/murilo/Github/liblpm/bindings/perl/$(MAP_TARGET)" $(MAP_PERLINC) "-MExtUtils::Command::MM" "-MTest::Harness" "-e" "undef *Test::Harness::Switches; test_harness($(TEST_VERBOSE), '$(INST_LIB)', '$(INST_ARCHLIB)')" $(TEST_FILES) + +testdb_static :: static pure_all $(MAP_TARGET) + PERL_DL_NONLAZY=1 "/home/murilo/Github/liblpm/bindings/perl/$(MAP_TARGET)" $(MAP_PERLINC) "-I$(INST_LIB)" "-I$(INST_ARCHLIB)" $(TEST_FILE) + + + +# --- MakeMaker ppd section: +# Creates a PPD (Perl Package Description) for a binary distribution. +ppd : + $(NOECHO) $(ECHO) '' > Net-LPM.ppd + $(NOECHO) $(ECHO) ' Perl bindings for high-performance liblpm Longest Prefix Match library' >> Net-LPM.ppd + $(NOECHO) $(ECHO) ' Murilo Chianfa <murilo.chianfa@outlook.com>' >> Net-LPM.ppd + $(NOECHO) $(ECHO) ' ' >> Net-LPM.ppd + $(NOECHO) $(ECHO) ' ' >> Net-LPM.ppd + $(NOECHO) $(ECHO) ' ' >> Net-LPM.ppd + $(NOECHO) $(ECHO) ' ' >> Net-LPM.ppd + $(NOECHO) $(ECHO) ' ' >> Net-LPM.ppd + $(NOECHO) $(ECHO) ' ' >> Net-LPM.ppd + $(NOECHO) $(ECHO) ' ' >> Net-LPM.ppd + $(NOECHO) $(ECHO) ' ' >> Net-LPM.ppd + $(NOECHO) $(ECHO) '' >> Net-LPM.ppd + + +# --- MakeMaker pm_to_blib section: + +pm_to_blib : $(FIRST_MAKEFILE) $(TO_INST_PM) + $(NOECHO) $(ABSPERLRUN) -MExtUtils::Install -e 'pm_to_blib({@ARGV}, '\''$(INST_LIB)/auto'\'', q[$(PM_FILTER)], '\''$(PERM_DIR)'\'')' -- \ + 'lib/Net/LPM.pm' 'blib/lib/Net/LPM.pm' + $(NOECHO) $(TOUCH) pm_to_blib + + +# --- MakeMaker selfdocument section: + +# here so even if top_targets is overridden, these will still be defined +# gmake will silently still work if any are .PHONY-ed but nmake won't + +static :: + $(NOECHO) $(NOOP) + +dynamic :: + $(NOECHO) $(NOOP) + +config :: + $(NOECHO) $(NOOP) + + +# --- MakeMaker postamble section: + +# Additional targets + +# Run tests with verbose output +testv: all + $(FULLPERLRUN) "-MExtUtils::Command::MM" "-MTest::Harness" "-e" "undef *Test::Harness::Switches; test_harness(1, '$(INST_LIB)', '$(INST_ARCHLIB)')" $(TEST_FILES) + +# Run tests under valgrind for memory leak detection +valgrind: all + PERL_DESTRUCT_LEVEL=2 valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes $(FULLPERLRUN) -Iblib/lib -Iblib/arch -e 'use Test::Harness qw(runtests); runtests(@ARGV)' t/*.t + +# Generate documentation +docs: + pod2html lib/Net/LPM.pm > LPM.html + +# Development: rebuild and test +dev: realclean all test + +.PHONY: testv valgrind docs dev + + + +# End. diff --git a/bindings/perl/Makefile.PL b/bindings/perl/Makefile.PL new file mode 100644 index 0000000..f94e102 --- /dev/null +++ b/bindings/perl/Makefile.PL @@ -0,0 +1,201 @@ +#!/usr/bin/env perl +# +# Makefile.PL - Build configuration for Net::LPM +# +# This script generates the Makefile for building the Net::LPM Perl module +# which provides XS bindings to the liblpm C library. +# + +use strict; +use warnings; +use ExtUtils::MakeMaker; +use Config; + +# Minimum Perl version +my $MIN_PERL_VERSION = '5.010'; + +# Check Perl version +if ($] < 5.010) { + die "Net::LPM requires Perl $MIN_PERL_VERSION or later (you have $])\n"; +} + +# Try to find liblpm +my ($lpm_inc, $lpm_lib) = find_liblpm(); + +print "Configuration:\n"; +print " Include path: $lpm_inc\n"; +print " Library path: $lpm_lib\n"; +print " Perl version: $]\n"; +print "\n"; + +WriteMakefile( + NAME => 'Net::LPM', + VERSION_FROM => 'lib/Net/LPM.pm', + ABSTRACT => 'Perl bindings for high-performance liblpm Longest Prefix Match library', + AUTHOR => 'Murilo Chianfa ', + LICENSE => 'open_source', # Boost Software License 1.0 + MIN_PERL_VERSION => $MIN_PERL_VERSION, + + # Dependencies + CONFIGURE_REQUIRES => { + 'ExtUtils::MakeMaker' => '6.64', + }, + BUILD_REQUIRES => { + 'ExtUtils::MakeMaker' => '6.64', + }, + PREREQ_PM => { + 'Carp' => 0, + 'Exporter' => 0, + 'XSLoader' => 0, + }, + TEST_REQUIRES => { + 'Test::More' => '0.98', + }, + + # XS configuration + XS => { 'LPM.xs' => 'LPM.c' }, + OBJECT => 'LPM$(OBJ_EXT)', + TYPEMAPS => ['typemap'], + + # Compiler/Linker flags + INC => $lpm_inc, + LIBS => [$lpm_lib], + CCFLAGS => "$Config{ccflags} -Wall", + + # Clean files + clean => { FILES => 'Net-LPM-* *.o *.c' }, + + # Metadata + META_MERGE => { + 'meta-spec' => { version => 2 }, + resources => { + repository => { + type => 'git', + url => 'https://github.com/MuriloChianfa/liblpm.git', + web => 'https://github.com/MuriloChianfa/liblpm', + }, + bugtracker => { + web => 'https://github.com/MuriloChianfa/liblpm/issues', + }, + homepage => 'https://github.com/MuriloChianfa/liblpm', + }, + keywords => ['lpm', 'longest-prefix-match', 'routing', 'ipv4', 'ipv6', 'networking'], + }, +); + +# Find liblpm installation +sub find_liblpm { + my @inc_paths = (); + my @lib_paths = (); + + # Check environment variables first + if ($ENV{LIBLPM_INC}) { + push @inc_paths, $ENV{LIBLPM_INC}; + } + if ($ENV{LIBLPM_LIB}) { + push @lib_paths, $ENV{LIBLPM_LIB}; + } + + # Check relative path to parent project (development) + push @inc_paths, '../../include'; + push @lib_paths, '../../build'; + + # Check pkg-config + my $pkg_config_inc = `pkg-config --cflags liblpm 2>/dev/null`; + my $pkg_config_lib = `pkg-config --libs liblpm 2>/dev/null`; + if ($? == 0) { + chomp($pkg_config_inc); + chomp($pkg_config_lib); + if ($pkg_config_inc || $pkg_config_lib) { + return ($pkg_config_inc, $pkg_config_lib); + } + } + + # Check standard system paths + push @inc_paths, '/usr/local/include', '/usr/include'; + push @lib_paths, '/usr/local/lib', '/usr/local/lib64', '/usr/lib', '/usr/lib64'; + + # Find header + my $inc_path = ''; + for my $path (@inc_paths) { + if (-f "$path/lpm/lpm.h") { + $inc_path = "-I$path"; + last; + } elsif (-f "$path/lpm.h") { + $inc_path = "-I$path"; + last; + } + } + + # Find library + my $lib_path = '-llpm -lm'; + for my $path (@lib_paths) { + if (-f "$path/liblpm.so" || -f "$path/liblpm.a") { + $lib_path = "-L$path -llpm -lm"; + last; + } + } + + # Warn if not found + unless ($inc_path) { + warn <<"END"; +WARNING: liblpm headers not found! + +You need to install liblpm before building Net::LPM. + +Option 1: Build from source (development): + cd ../.. + mkdir build && cd build + cmake .. + make -j\$(nproc) + sudo make install + +Option 2: Install from package: + See https://github.com/MuriloChianfa/liblpm#installation + +Option 3: Set environment variables: + LIBLPM_INC=/path/to/include LIBLPM_LIB=/path/to/lib perl Makefile.PL + +END + } + + return ($inc_path, $lib_path); +} + +# Custom MY methods +package MY; + +sub postamble { + return <<'MAKE'; + +# Additional targets + +# Run tests with verbose output +testv: all + $(FULLPERLRUN) "-MExtUtils::Command::MM" "-MTest::Harness" "-e" "undef *Test::Harness::Switches; test_harness(1, '$(INST_LIB)', '$(INST_ARCHLIB)')" $(TEST_FILES) + +# Run tests under valgrind for memory leak detection +valgrind: all + PERL_DESTRUCT_LEVEL=2 valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes $(FULLPERLRUN) -Iblib/lib -Iblib/arch -e 'use Test::Harness qw(runtests); runtests(@ARGV)' t/*.t + +# Generate documentation +docs: + pod2html lib/Net/LPM.pm > LPM.html + +# Development: rebuild and test +dev: realclean all test + +.PHONY: testv valgrind docs dev + +MAKE +} + +sub test { + my $self = shift; + my $test = $self->SUPER::test(@_); + + # Set LD_LIBRARY_PATH for tests if using development build + $test =~ s/^(test_dynamic\s*:.*\n)/$1\tLD_LIBRARY_PATH=..\/..\/build:\$(LD_LIBRARY_PATH) \\\n/m; + + return $test; +} diff --git a/bindings/perl/README.md b/bindings/perl/README.md new file mode 100644 index 0000000..6f43a56 --- /dev/null +++ b/bindings/perl/README.md @@ -0,0 +1,288 @@ +# Perl Bindings for liblpm + +High-performance Perl bindings for the [liblpm](https://github.com/MuriloChianfa/liblpm) C library, providing fast longest prefix match (LPM) routing table operations for both IPv4 and IPv6. + +## Features + +- **High Performance**: Direct XS bindings to optimized C library +- **Dual Stack**: Full support for both IPv4 and IPv6 +- **Multiple Algorithms**: DIR-24-8 for IPv4, Wide 16-bit stride for IPv6 +- **Batch Operations**: Efficient batch lookups to reduce XS overhead +- **SIMD Optimized**: Runtime CPU feature detection (SSE2, AVX, AVX2, AVX512) +- **Memory Safe**: Automatic cleanup via Perl's reference counting +- **Idiomatic API**: Clean object-oriented Perl interface + +## Installation + +### Prerequisites + +First, ensure liblpm is installed: + +```bash +# Build and install liblpm from source +cd /path/to/liblpm +mkdir -p build && cd build +cmake .. +make -j$(nproc) +sudo make install +sudo ldconfig +``` + +Or install from packages (see main liblpm README). + +### Build Perl Module + +```bash +cd bindings/perl +perl Makefile.PL +make +make test +sudo make install +``` + +### Development Build + +For development, set the library path: + +```bash +cd bindings/perl +perl Makefile.PL +make +LD_LIBRARY_PATH=../../build make test +``` + +## Quick Start + +```perl +use Net::LPM; + +# Create IPv4 routing table +my $table = Net::LPM->new_ipv4(); + +# Add routes (CIDR notation) +$table->insert("192.168.0.0/16", 100); +$table->insert("10.0.0.0/8", 200); +$table->insert("0.0.0.0/0", 999); # Default route + +# Lookup +my $next_hop = $table->lookup("192.168.1.1"); +print "Next hop: $next_hop\n"; # Prints: Next hop: 100 + +# Batch lookup (more efficient for multiple addresses) +my @addresses = qw(192.168.1.1 10.1.1.1 8.8.8.8); +my @next_hops = $table->lookup_batch(\@addresses); + +# Table automatically destroyed when out of scope +``` + +## API Reference + +### Creating Tables + +```perl +# IPv4 table (uses DIR-24-8 algorithm) +my $ipv4_table = Net::LPM->new_ipv4(); + +# IPv6 table (uses Wide 16-bit stride algorithm) +my $ipv6_table = Net::LPM->new_ipv6(); +``` + +### Basic Operations + +```perl +# Insert a route +$table->insert("192.168.0.0/16", 100); +$table->insert("2001:db8::/32", 200); + +# Delete a route +my $deleted = $table->delete("192.168.0.0/16"); + +# Lookup a single address +my $next_hop = $table->lookup("192.168.1.1"); +if (defined $next_hop) { + print "Found: $next_hop\n"; +} else { + print "No match\n"; +} + +# Batch lookup (more efficient for multiple addresses) +my @addresses = qw(192.168.1.1 192.168.1.2 10.0.0.1); +my @next_hops = $table->lookup_batch(\@addresses); +``` + +### Error Handling + +```perl +use Try::Tiny; + +try { + $table->insert("invalid-prefix", 100); +} catch { + warn "Insert failed: $_"; +}; +``` + +### Utility Methods + +```perl +# Check table type +if ($table->is_ipv4) { ... } +if ($table->is_ipv6) { ... } + +# Get liblpm C library version +my $version = Net::LPM->version(); + +# Print internal statistics (for debugging) +$table->print_stats(); +``` + +### Constants + +```perl +use Net::LPM qw(LPM_INVALID_NEXT_HOP); + +# LPM_INVALID_NEXT_HOP is 0xFFFFFFFF +# In the Perl API, this is converted to undef +``` + +## Performance + +### XS Overhead + +While the C library provides nanosecond-scale lookups, the XS binding adds some overhead (~5-20ns per call). To maximize performance: + +1. **Use Batch Operations**: For multiple lookups, use `lookup_batch()` to amortize overhead + + ```perl + # Slower: N XS calls + my @results = map { $table->lookup($_) } @addresses; + + # Faster: 1 XS call + my @results = $table->lookup_batch(\@addresses); + ``` + +2. **Reuse Table Objects**: Avoid frequent create/destroy cycles + +3. **Keep Tables in Scope**: Don't let tables go out of scope during lookup-intensive operations + +### Benchmark + +Run benchmarks: + +```bash +cd bindings/perl +make +LD_LIBRARY_PATH=../../build perl -Iblib/lib -Iblib/arch examples/basic_example.pl +``` + +## Examples + +See the [examples](examples/) directory: + +- [basic_example.pl](examples/basic_example.pl) - Complete API demonstration + +Run the example: + +```bash +cd bindings/perl +make +LD_LIBRARY_PATH=../../build perl -Iblib/lib -Iblib/arch examples/basic_example.pl +``` + +## Thread Safety + +**Net::LPM objects are NOT thread-safe.** + +Each table should only be accessed from a single thread. For multi-threaded applications: + +- Use separate tables per thread, or +- Add external synchronization (e.g., using `threads::shared`) + +Read-only lookups can be done concurrently if no modifications occur. + +## Memory Management + +The module uses Perl's reference counting for automatic cleanup: + +```perl +my $table = Net::LPM->new_ipv4(); +# ... use table ... +# Table automatically freed when $table goes out of scope + +# Or explicitly: +undef $table; # Immediately free resources +``` + +## Testing + +```bash +# Run all tests +make test + +# Verbose test output +make testv + +# Memory leak detection with valgrind +make valgrind +``` + +## Requirements + +- Perl 5.10 or later +- liblpm 2.0.0 or later +- C compiler with C99 support +- Linux or macOS + +## Troubleshooting + +### Library not found + +If you get "liblpm not found" errors: + +```bash +# Option 1: Set environment variables +LIBLPM_INC=/path/to/include LIBLPM_LIB=/path/to/lib perl Makefile.PL + +# Option 2: Set LD_LIBRARY_PATH at runtime +LD_LIBRARY_PATH=/path/to/lib perl your_script.pl + +# Option 3: Install liblpm system-wide +sudo make install +sudo ldconfig +``` + +### Test failures + +Ensure liblpm is properly built: + +```bash +cd ../../build +make -j$(nproc) +cd ../bindings/perl +LD_LIBRARY_PATH=../../build make test +``` + +## Contributing + +Contributions welcome! Please ensure: + +- All tests pass: `make test` +- Code follows Perl best practices +- Documentation is updated +- Memory safety verified: `make valgrind` + +## License + +Same as liblpm: Boost Software License 1.0 + +## Credits + +- [liblpm](https://github.com/MuriloChianfa/liblpm) by Murilo Chianfa +- Perl bindings by Murilo Chianfa + +## See Also + +- [liblpm main documentation](../../README.md) +- [C++ bindings](../cpp/README.md) +- [Go bindings](../go/README.md) +- [perldoc Net::LPM](lib/Net/LPM.pm) - Full API documentation diff --git a/bindings/perl/blib/arch/.exists b/bindings/perl/blib/arch/.exists new file mode 100644 index 0000000..e69de29 diff --git a/bindings/perl/blib/arch/auto/Net/LPM/.exists b/bindings/perl/blib/arch/auto/Net/LPM/.exists new file mode 100644 index 0000000..e69de29 diff --git a/bindings/perl/blib/bin/.exists b/bindings/perl/blib/bin/.exists new file mode 100644 index 0000000..e69de29 diff --git a/bindings/perl/blib/lib/Net/.exists b/bindings/perl/blib/lib/Net/.exists new file mode 100644 index 0000000..e69de29 diff --git a/bindings/perl/blib/lib/Net/LPM.pm b/bindings/perl/blib/lib/Net/LPM.pm new file mode 100644 index 0000000..fc05905 --- /dev/null +++ b/bindings/perl/blib/lib/Net/LPM.pm @@ -0,0 +1,451 @@ +package Net::LPM; + +use strict; +use warnings; +use Carp qw(croak); + +our $VERSION = '2.0.0'; + +# Load XS code first +require XSLoader; +XSLoader::load('Net::LPM', $VERSION); + +# Export constants - must be after XSLoader +use Exporter 'import'; +our @EXPORT_OK = qw(LPM_INVALID_NEXT_HOP); +our %EXPORT_TAGS = ( + constants => [qw(LPM_INVALID_NEXT_HOP)], + all => \@EXPORT_OK, +); + +# Constant for invalid next hop (0xFFFFFFFF) +# Define as literal to avoid load-order issues +use constant LPM_INVALID_NEXT_HOP => 0xFFFFFFFF; + +=head1 NAME + +Net::LPM - High-performance Perl bindings for liblpm Longest Prefix Match library + +=head1 VERSION + +Version 2.0.0 + +=head1 SYNOPSIS + + use Net::LPM; + + # Create an IPv4 routing table + my $table = Net::LPM->new_ipv4(); + + # Insert routes (CIDR notation) + $table->insert("192.168.0.0/16", 100); + $table->insert("10.0.0.0/8", 200); + $table->insert("0.0.0.0/0", 999); # Default route + + # Lookup an address + my $next_hop = $table->lookup("192.168.1.1"); + if (defined $next_hop) { + print "Next hop: $next_hop\n"; # Prints: Next hop: 100 + } + + # Batch lookup (more efficient for multiple addresses) + my @addresses = qw(192.168.1.1 10.1.1.1 172.16.0.1); + my @next_hops = $table->lookup_batch(\@addresses); + + # Delete a route + $table->delete("10.0.0.0/8"); + + # Table is automatically destroyed when $table goes out of scope + +=head1 DESCRIPTION + +Net::LPM provides high-performance Perl bindings to the liblpm C library +for Longest Prefix Match (LPM) routing table operations. + +LPM is a fundamental operation in IP routing where, given an IP address, +you need to find the most specific (longest) matching prefix in a routing +table. For example, if a table contains both 192.168.0.0/16 and +192.168.1.0/24, a lookup for 192.168.1.1 would return the /24 route. + +=head2 Features + +=over 4 + +=item * B: Direct XS bindings to optimized C library + +=item * B: Full support for both IPv4 and IPv6 + +=item * B: DIR-24-8 for IPv4, Wide 16-bit stride for IPv6 + +=item * B: Efficient batch lookups to reduce overhead + +=item * B: Runtime CPU feature detection for optimal performance + +=item * B: Automatic cleanup via Perl's reference counting + +=back + +=head1 CONSTRUCTOR METHODS + +=head2 new_ipv4 + + my $table = Net::LPM->new_ipv4(); + +Creates a new IPv4 routing table using the DIR-24-8 algorithm. +This provides optimal lookup performance with typically 1-2 memory +accesses per lookup. + +Returns a new Net::LPM object. + +Dies on failure (e.g., out of memory). + +=head2 new_ipv6 + + my $table = Net::LPM->new_ipv6(); + +Creates a new IPv6 routing table using the Wide 16-bit stride algorithm. +This is optimized for common /48 prefix allocations. + +Returns a new Net::LPM object. + +Dies on failure. + +=cut + +sub new_ipv4 { + my $class = shift; + my $ptr = _xs_new_ipv4(); + my $self = bless { + _ptr => $ptr, + _type => 'ipv4', + }, $class; + return $self; +} + +sub new_ipv6 { + my $class = shift; + my $ptr = _xs_new_ipv6(); + my $self = bless { + _ptr => $ptr, + _type => 'ipv6', + }, $class; + return $self; +} + +=head1 INSTANCE METHODS + +=head2 insert + + $table->insert($prefix, $next_hop); + $table->insert("192.168.0.0/16", 100); + $table->insert("2001:db8::/32", 200); + +Inserts a route into the table. + +B + +=over 4 + +=item * C<$prefix> - CIDR notation prefix (e.g., "192.168.0.0/16" or "2001:db8::/32") + +=item * C<$next_hop> - Unsigned 32-bit integer representing the next hop + +=back + +Returns true on success. + +Dies on error (invalid prefix format, prefix length out of range, etc.). + +=cut + +sub insert { + my ($self, $prefix, $next_hop) = @_; + + croak "Missing prefix argument" unless defined $prefix; + croak "Missing next_hop argument" unless defined $next_hop; + croak "next_hop must be a non-negative integer" + if $next_hop < 0 || $next_hop != int($next_hop); + croak "next_hop exceeds 32-bit unsigned range" + if $next_hop > 0xFFFFFFFF; + + return _xs_insert($self->{_ptr}, $prefix, $next_hop); +} + +=head2 delete + + my $deleted = $table->delete($prefix); + $table->delete("192.168.0.0/16"); + +Deletes a route from the table. + +B + +=over 4 + +=item * C<$prefix> - CIDR notation prefix to delete + +=back + +Returns true if the prefix was found and deleted, false if not found. + +Dies on error (invalid prefix format). + +=cut + +sub delete { + my ($self, $prefix) = @_; + + croak "Missing prefix argument" unless defined $prefix; + + return _xs_delete($self->{_ptr}, $prefix); +} + +=head2 lookup + + my $next_hop = $table->lookup($address); + my $nh = $table->lookup("192.168.1.1"); + my $nh = $table->lookup("2001:db8::1"); + +Looks up a single IP address in the table. + +B + +=over 4 + +=item * C<$address> - IP address to lookup (dotted-quad for IPv4, colon notation for IPv6) + +=back + +Returns the next hop value if a matching prefix is found, C otherwise. + +Dies on error (invalid address format, wrong IP version). + +=cut + +sub lookup { + my ($self, $address) = @_; + + croak "Missing address argument" unless defined $address; + + return _xs_lookup($self->{_ptr}, $address); +} + +=head2 lookup_batch + + my @next_hops = $table->lookup_batch(\@addresses); + my @results = $table->lookup_batch([qw(192.168.1.1 10.0.0.1 172.16.0.1)]); + +Performs batch lookup for multiple addresses. This is more efficient than +calling lookup() multiple times because it amortizes the Perl/C call overhead. + +B + +=over 4 + +=item * C<\@addresses> - Array reference of IP addresses to lookup + +=back + +Returns a list of next hop values (or C for addresses with no match), +in the same order as the input addresses. + +Dies on error (invalid address, wrong IP version). + +=cut + +sub lookup_batch { + my ($self, $addrs_ref) = @_; + + croak "lookup_batch requires an array reference" + unless ref($addrs_ref) eq 'ARRAY'; + + return () unless @$addrs_ref; + + if ($self->{_type} eq 'ipv6') { + return _xs_lookup_batch_ipv6($self->{_ptr}, $addrs_ref); + } else { + return _xs_lookup_batch_ipv4($self->{_ptr}, $addrs_ref); + } +} + +=head2 is_ipv4 + + if ($table->is_ipv4) { ... } + +Returns true if this is an IPv4 table. + +=cut + +sub is_ipv4 { + my $self = shift; + return $self->{_type} eq 'ipv4'; +} + +=head2 is_ipv6 + + if ($table->is_ipv6) { ... } + +Returns true if this is an IPv6 table. + +=cut + +sub is_ipv6 { + my $self = shift; + return $self->{_type} eq 'ipv6'; +} + +=head2 print_stats + + $table->print_stats(); + +Prints internal statistics about the routing table to STDOUT. +Useful for debugging and performance analysis. + +=cut + +sub print_stats { + my $self = shift; + _xs_print_stats($self->{_ptr}); +} + +=head1 CLASS METHODS + +=head2 version + + my $version = Net::LPM->version(); + +Returns the version string of the underlying liblpm C library. + +=cut + +sub version { + return _xs_get_version(); +} + +=head1 DESTRUCTOR + +The destructor is automatically called when the object goes out of scope +or is explicitly undefined. It frees all C resources associated with +the routing table. + + undef $table; # Explicitly destroy + # Or just let it go out of scope + +=cut + +sub DESTROY { + my $self = shift; + if ($self->{_ptr}) { + _xs_free($self->{_ptr}); + $self->{_ptr} = undef; + } +} + +=head1 EXPORTS + +The following constants can be exported: + + use Net::LPM qw(LPM_INVALID_NEXT_HOP); + use Net::LPM qw(:constants); + use Net::LPM qw(:all); + +=head2 LPM_INVALID_NEXT_HOP + +The value returned internally when no matching prefix is found. +In the Perl API, this is converted to C, but the constant +is available if needed for comparison. + +=head1 PERFORMANCE + +=head2 XS Overhead + +While the C library provides nanosecond-scale lookups, the XS binding +adds some overhead (~5-20ns per call). For high-performance applications: + +=over 4 + +=item * Use C instead of multiple C calls + +=item * Reuse table objects (avoid create/destroy cycles) + +=item * Keep tables in scope during lookup-intensive operations + +=back + +=head2 Batch Operations + +Batch operations amortize the Perl/XS overhead across multiple lookups: + + # Slower: N XS calls + my @results = map { $table->lookup($_) } @addresses; + + # Faster: 1 XS call + my @results = $table->lookup_batch(\@addresses); + +=head1 THREAD SAFETY + +B + +Each table should only be accessed from a single thread. For multi-threaded +applications, either: + +=over 4 + +=item * Use separate tables per thread + +=item * Add external synchronization (e.g., using L) + +=back + +Read-only lookups can be done concurrently if no modifications occur. + +=head1 MEMORY MANAGEMENT + +The module uses Perl's reference counting for automatic memory management. +When a Net::LPM object goes out of scope or is undefined, the underlying +C resources are automatically freed. + +For explicit control: + + undef $table; # Immediately free resources + +=head1 ERROR HANDLING + +Methods die on error with descriptive messages. Use eval or Try::Tiny +for error handling: + + use Try::Tiny; + + try { + $table->insert("invalid-prefix", 100); + } catch { + warn "Insert failed: $_"; + }; + +=head1 SEE ALSO + +=over 4 + +=item * L - liblpm project + +=item * L - Pure Perl IP address manipulation + +=item * L - IP address manipulation with prefix support + +=back + +=head1 AUTHOR + +Murilo Chianfa Emurilo.chianfa@outlook.comE + +=head1 LICENSE + +This library is licensed under the Boost Software License 1.0. + +=head1 COPYRIGHT + +Copyright (c) Murilo Chianfa. All rights reserved. + +=cut + +1; diff --git a/bindings/perl/blib/lib/auto/Net/LPM/.exists b/bindings/perl/blib/lib/auto/Net/LPM/.exists new file mode 100644 index 0000000..e69de29 diff --git a/bindings/perl/blib/man1/.exists b/bindings/perl/blib/man1/.exists new file mode 100644 index 0000000..e69de29 diff --git a/bindings/perl/blib/man3/.exists b/bindings/perl/blib/man3/.exists new file mode 100644 index 0000000..e69de29 diff --git a/bindings/perl/blib/man3/Net::LPM.3pm b/bindings/perl/blib/man3/Net::LPM.3pm new file mode 100644 index 0000000..d006a93 --- /dev/null +++ b/bindings/perl/blib/man3/Net::LPM.3pm @@ -0,0 +1,349 @@ +.\" -*- mode: troff; coding: utf-8 -*- +.\" Automatically generated by Pod::Man 5.0102 (Pod::Simple 3.45) +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" \*(C` and \*(C' are quotes in nroff, nothing in troff, for use with C<>. +.ie n \{\ +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds C` +. ds C' +'br\} +.\" +.\" Escape single quotes in literal strings from groff's Unicode transform. +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" +.\" If the F register is >0, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.\" +.\" Avoid warning from groff about undefined register 'F'. +.de IX +.. +.nr rF 0 +.if \n(.g .if rF .nr rF 1 +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. if !\nF==2 \{\ +. nr % 0 +. nr F 2 +. \} +. \} +.\} +.rr rF +.\" ======================================================================== +.\" +.IX Title "Net::LPM 3pm" +.TH Net::LPM 3pm 2026-01-29 "perl v5.40.1" "User Contributed Perl Documentation" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.if n .ad l +.nh +.SH NAME +Net::LPM \- High\-performance Perl bindings for liblpm Longest Prefix Match library +.SH VERSION +.IX Header "VERSION" +Version 2.0.0 +.SH SYNOPSIS +.IX Header "SYNOPSIS" +.Vb 1 +\& use Net::LPM; +\& +\& # Create an IPv4 routing table +\& my $table = Net::LPM\->new_ipv4(); +\& +\& # Insert routes (CIDR notation) +\& $table\->insert("192.168.0.0/16", 100); +\& $table\->insert("10.0.0.0/8", 200); +\& $table\->insert("0.0.0.0/0", 999); # Default route +\& +\& # Lookup an address +\& my $next_hop = $table\->lookup("192.168.1.1"); +\& if (defined $next_hop) { +\& print "Next hop: $next_hop\en"; # Prints: Next hop: 100 +\& } +\& +\& # Batch lookup (more efficient for multiple addresses) +\& my @addresses = qw(192.168.1.1 10.1.1.1 172.16.0.1); +\& my @next_hops = $table\->lookup_batch(\e@addresses); +\& +\& # Delete a route +\& $table\->delete("10.0.0.0/8"); +\& +\& # Table is automatically destroyed when $table goes out of scope +.Ve +.SH DESCRIPTION +.IX Header "DESCRIPTION" +Net::LPM provides high-performance Perl bindings to the liblpm C library +for Longest Prefix Match (LPM) routing table operations. +.PP +LPM is a fundamental operation in IP routing where, given an IP address, +you need to find the most specific (longest) matching prefix in a routing +table. For example, if a table contains both 192.168.0.0/16 and +192.168.1.0/24, a lookup for 192.168.1.1 would return the /24 route. +.SS Features +.IX Subsection "Features" +.IP \(bu 4 +\&\fBHigh Performance\fR: Direct XS bindings to optimized C library +.IP \(bu 4 +\&\fBDual Stack\fR: Full support for both IPv4 and IPv6 +.IP \(bu 4 +\&\fBMultiple Algorithms\fR: DIR\-24\-8 for IPv4, Wide 16\-bit stride for IPv6 +.IP \(bu 4 +\&\fBBatch Operations\fR: Efficient batch lookups to reduce overhead +.IP \(bu 4 +\&\fBSIMD Optimized\fR: Runtime CPU feature detection for optimal performance +.IP \(bu 4 +\&\fBMemory Safe\fR: Automatic cleanup via Perl's reference counting +.SH "CONSTRUCTOR METHODS" +.IX Header "CONSTRUCTOR METHODS" +.SS new_ipv4 +.IX Subsection "new_ipv4" +.Vb 1 +\& my $table = Net::LPM\->new_ipv4(); +.Ve +.PP +Creates a new IPv4 routing table using the DIR\-24\-8 algorithm. +This provides optimal lookup performance with typically 1\-2 memory +accesses per lookup. +.PP +Returns a new Net::LPM object. +.PP +Dies on failure (e.g., out of memory). +.SS new_ipv6 +.IX Subsection "new_ipv6" +.Vb 1 +\& my $table = Net::LPM\->new_ipv6(); +.Ve +.PP +Creates a new IPv6 routing table using the Wide 16\-bit stride algorithm. +This is optimized for common /48 prefix allocations. +.PP +Returns a new Net::LPM object. +.PP +Dies on failure. +.SH "INSTANCE METHODS" +.IX Header "INSTANCE METHODS" +.SS insert +.IX Subsection "insert" +.Vb 3 +\& $table\->insert($prefix, $next_hop); +\& $table\->insert("192.168.0.0/16", 100); +\& $table\->insert("2001:db8::/32", 200); +.Ve +.PP +Inserts a route into the table. +.PP +\&\fBParameters:\fR +.IP \(bu 4 +\&\f(CW$prefix\fR \- CIDR notation prefix (e.g., "192.168.0.0/16" or "2001:db8::/32") +.IP \(bu 4 +\&\f(CW$next_hop\fR \- Unsigned 32\-bit integer representing the next hop +.PP +Returns true on success. +.PP +Dies on error (invalid prefix format, prefix length out of range, etc.). +.SS delete +.IX Subsection "delete" +.Vb 2 +\& my $deleted = $table\->delete($prefix); +\& $table\->delete("192.168.0.0/16"); +.Ve +.PP +Deletes a route from the table. +.PP +\&\fBParameters:\fR +.IP \(bu 4 +\&\f(CW$prefix\fR \- CIDR notation prefix to delete +.PP +Returns true if the prefix was found and deleted, false if not found. +.PP +Dies on error (invalid prefix format). +.SS lookup +.IX Subsection "lookup" +.Vb 3 +\& my $next_hop = $table\->lookup($address); +\& my $nh = $table\->lookup("192.168.1.1"); +\& my $nh = $table\->lookup("2001:db8::1"); +.Ve +.PP +Looks up a single IP address in the table. +.PP +\&\fBParameters:\fR +.IP \(bu 4 +\&\f(CW$address\fR \- IP address to lookup (dotted-quad for IPv4, colon notation for IPv6) +.PP +Returns the next hop value if a matching prefix is found, \f(CW\*(C`undef\*(C'\fR otherwise. +.PP +Dies on error (invalid address format, wrong IP version). +.SS lookup_batch +.IX Subsection "lookup_batch" +.Vb 2 +\& my @next_hops = $table\->lookup_batch(\e@addresses); +\& my @results = $table\->lookup_batch([qw(192.168.1.1 10.0.0.1 172.16.0.1)]); +.Ve +.PP +Performs batch lookup for multiple addresses. This is more efficient than +calling \fBlookup()\fR multiple times because it amortizes the Perl/C call overhead. +.PP +\&\fBParameters:\fR +.IP \(bu 4 +\&\f(CW\*(C`\e@addresses\*(C'\fR \- Array reference of IP addresses to lookup +.PP +Returns a list of next hop values (or \f(CW\*(C`undef\*(C'\fR for addresses with no match), +in the same order as the input addresses. +.PP +Dies on error (invalid address, wrong IP version). +.SS is_ipv4 +.IX Subsection "is_ipv4" +.Vb 1 +\& if ($table\->is_ipv4) { ... } +.Ve +.PP +Returns true if this is an IPv4 table. +.SS is_ipv6 +.IX Subsection "is_ipv6" +.Vb 1 +\& if ($table\->is_ipv6) { ... } +.Ve +.PP +Returns true if this is an IPv6 table. +.SS print_stats +.IX Subsection "print_stats" +.Vb 1 +\& $table\->print_stats(); +.Ve +.PP +Prints internal statistics about the routing table to STDOUT. +Useful for debugging and performance analysis. +.SH "CLASS METHODS" +.IX Header "CLASS METHODS" +.SS version +.IX Subsection "version" +.Vb 1 +\& my $version = Net::LPM\->version(); +.Ve +.PP +Returns the version string of the underlying liblpm C library. +.SH DESTRUCTOR +.IX Header "DESTRUCTOR" +The destructor is automatically called when the object goes out of scope +or is explicitly undefined. It frees all C resources associated with +the routing table. +.PP +.Vb 2 +\& undef $table; # Explicitly destroy +\& # Or just let it go out of scope +.Ve +.SH EXPORTS +.IX Header "EXPORTS" +The following constants can be exported: +.PP +.Vb 3 +\& use Net::LPM qw(LPM_INVALID_NEXT_HOP); +\& use Net::LPM qw(:constants); +\& use Net::LPM qw(:all); +.Ve +.SS LPM_INVALID_NEXT_HOP +.IX Subsection "LPM_INVALID_NEXT_HOP" +The value returned internally when no matching prefix is found. +In the Perl API, this is converted to \f(CW\*(C`undef\*(C'\fR, but the constant +is available if needed for comparison. +.SH PERFORMANCE +.IX Header "PERFORMANCE" +.SS "XS Overhead" +.IX Subsection "XS Overhead" +While the C library provides nanosecond-scale lookups, the XS binding +adds some overhead (~5\-20ns per call). For high-performance applications: +.IP \(bu 4 +Use \f(CWlookup_batch()\fR instead of multiple \f(CWlookup()\fR calls +.IP \(bu 4 +Reuse table objects (avoid create/destroy cycles) +.IP \(bu 4 +Keep tables in scope during lookup-intensive operations +.SS "Batch Operations" +.IX Subsection "Batch Operations" +Batch operations amortize the Perl/XS overhead across multiple lookups: +.PP +.Vb 2 +\& # Slower: N XS calls +\& my @results = map { $table\->lookup($_) } @addresses; +\& +\& # Faster: 1 XS call +\& my @results = $table\->lookup_batch(\e@addresses); +.Ve +.SH "THREAD SAFETY" +.IX Header "THREAD SAFETY" +\&\fBNet::LPM objects are NOT thread-safe.\fR +.PP +Each table should only be accessed from a single thread. For multi-threaded +applications, either: +.IP \(bu 4 +Use separate tables per thread +.IP \(bu 4 +Add external synchronization (e.g., using threads::shared) +.PP +Read-only lookups can be done concurrently if no modifications occur. +.SH "MEMORY MANAGEMENT" +.IX Header "MEMORY MANAGEMENT" +The module uses Perl's reference counting for automatic memory management. +When a Net::LPM object goes out of scope or is undefined, the underlying +C resources are automatically freed. +.PP +For explicit control: +.PP +.Vb 1 +\& undef $table; # Immediately free resources +.Ve +.SH "ERROR HANDLING" +.IX Header "ERROR HANDLING" +Methods die on error with descriptive messages. Use eval or Try::Tiny +for error handling: +.PP +.Vb 1 +\& use Try::Tiny; +\& +\& try { +\& $table\->insert("invalid\-prefix", 100); +\& } catch { +\& warn "Insert failed: $_"; +\& }; +.Ve +.SH "SEE ALSO" +.IX Header "SEE ALSO" +.IP \(bu 4 + \- liblpm project +.IP \(bu 4 +Net::IP \- Pure Perl IP address manipulation +.IP \(bu 4 +NetAddr::IP \- IP address manipulation with prefix support +.SH AUTHOR +.IX Header "AUTHOR" +Murilo Chianfa +.SH LICENSE +.IX Header "LICENSE" +This library is licensed under the Boost Software License 1.0. +.SH COPYRIGHT +.IX Header "COPYRIGHT" +Copyright (c) Murilo Chianfa. All rights reserved. diff --git a/bindings/perl/blib/script/.exists b/bindings/perl/blib/script/.exists new file mode 100644 index 0000000..e69de29 diff --git a/bindings/perl/examples/basic_example.pl b/bindings/perl/examples/basic_example.pl new file mode 100644 index 0000000..758838b --- /dev/null +++ b/bindings/perl/examples/basic_example.pl @@ -0,0 +1,183 @@ +#!/usr/bin/env perl +# +# basic_example.pl - Demonstrates Net::LPM usage for IPv4 and IPv6 +# +# This example shows how to: +# - Create IPv4 and IPv6 routing tables +# - Insert routes with different prefix lengths +# - Perform single and batch lookups +# - Handle longest prefix matching +# - Delete routes +# +# Usage: perl basic_example.pl +# + +use strict; +use warnings; +use FindBin qw($Bin); +use lib "$Bin/../lib", "$Bin/../blib/lib", "$Bin/../blib/arch"; + +use Net::LPM; + +print "=" x 60, "\n"; +print "Net::LPM Basic Example\n"; +print "liblpm version: ", Net::LPM->version(), "\n"; +print "=" x 60, "\n\n"; + +# ============================================================================ +# IPv4 Example +# ============================================================================ + +print "IPv4 Routing Table Example\n"; +print "-" x 40, "\n\n"; + +# Create IPv4 routing table +my $ipv4_table = Net::LPM->new_ipv4(); + +# Add some routes +print "Adding routes:\n"; +my @ipv4_routes = ( + ["0.0.0.0/0", 999, "Default route"], + ["10.0.0.0/8", 100, "Private Class A"], + ["10.1.0.0/16", 101, "More specific /16"], + ["10.1.1.0/24", 102, "Even more specific /24"], + ["192.168.0.0/16", 200, "Private Class C"], + ["172.16.0.0/12", 300, "Private Class B"], + ["8.8.8.0/24", 400, "Google DNS subnet"], +); + +for my $route (@ipv4_routes) { + my ($prefix, $nh, $desc) = @$route; + $ipv4_table->insert($prefix, $nh); + printf " %-18s -> %3d (%s)\n", $prefix, $nh, $desc; +} +print "\n"; + +# Single lookups demonstrating longest prefix match +print "Single lookups (demonstrating longest prefix match):\n"; +my @ipv4_lookups = ( + ["10.1.1.5", "Matches /24 (102)"], + ["10.1.2.5", "Matches /16 (101)"], + ["10.2.3.4", "Matches /8 (100)"], + ["192.168.1.1", "Matches /16 (200)"], + ["172.20.0.1", "Matches /12 (300)"], + ["8.8.8.8", "Google DNS (400)"], + ["1.1.1.1", "Default route (999)"], +); + +for my $lookup (@ipv4_lookups) { + my ($addr, $desc) = @$lookup; + my $nh = $ipv4_table->lookup($addr); + printf " %-15s -> %s (%s)\n", $addr, + defined($nh) ? $nh : "no match", $desc; +} +print "\n"; + +# Batch lookup (more efficient for multiple addresses) +print "Batch lookup:\n"; +my @batch_addrs = qw(10.1.1.1 10.1.2.2 10.2.3.4 192.168.1.1 8.8.8.8); +my @batch_results = $ipv4_table->lookup_batch(\@batch_addrs); + +for my $i (0 .. $#batch_addrs) { + my $nh = $batch_results[$i]; + printf " %-15s -> %s\n", $batch_addrs[$i], + defined($nh) ? $nh : "no match"; +} +print "\n"; + +# Delete a route and see the fallback +print "Deleting 10.1.1.0/24 and looking up 10.1.1.5 again:\n"; +$ipv4_table->delete("10.1.1.0/24"); +my $nh = $ipv4_table->lookup("10.1.1.5"); +printf " 10.1.1.5 now -> %d (fell back to /16)\n\n", $nh; + +# Clean up +undef $ipv4_table; + +# ============================================================================ +# IPv6 Example +# ============================================================================ + +print "IPv6 Routing Table Example\n"; +print "-" x 40, "\n\n"; + +# Create IPv6 routing table +my $ipv6_table = Net::LPM->new_ipv6(); + +# Add some routes +print "Adding routes:\n"; +my @ipv6_routes = ( + ["::/0", 999, "Default route"], + ["2001:db8::/32", 100, "Documentation prefix"], + ["2001:db8:1::/48", 101, "More specific /48"], + ["2001:db8:1:1::/64", 102, "Even more specific /64"], + ["fe80::/10", 200, "Link-local"], + ["fc00::/7", 300, "Unique local addresses"], + ["2607:f8b0:4000::/36", 400, "Google prefix"], +); + +for my $route (@ipv6_routes) { + my ($prefix, $nh, $desc) = @$route; + $ipv6_table->insert($prefix, $nh); + printf " %-24s -> %3d (%s)\n", $prefix, $nh, $desc; +} +print "\n"; + +# Single lookups +print "Single lookups:\n"; +my @ipv6_lookups = ( + ["2001:db8:1:1::1", "Matches /64 (102)"], + ["2001:db8:1:2::1", "Matches /48 (101)"], + ["2001:db8:ffff::1", "Matches /32 (100)"], + ["fe80::1", "Link-local (200)"], + ["fd00::1", "Unique local (300)"], + ["2607:f8b0:4004:800::200e", "Google (400)"], + ["2620:fe::fe", "Default route (999)"], +); + +for my $lookup (@ipv6_lookups) { + my ($addr, $desc) = @$lookup; + my $nh = $ipv6_table->lookup($addr); + printf " %-28s -> %s (%s)\n", $addr, + defined($nh) ? $nh : "no match", $desc; +} +print "\n"; + +# Batch lookup +print "Batch lookup:\n"; +my @ipv6_batch = qw(2001:db8:1:1::1 2001:db8:1:2::1 fe80::1 2620:fe::fe); +my @ipv6_results = $ipv6_table->lookup_batch(\@ipv6_batch); + +for my $i (0 .. $#ipv6_batch) { + my $nh = $ipv6_results[$i]; + printf " %-24s -> %s\n", $ipv6_batch[$i], + defined($nh) ? $nh : "no match"; +} +print "\n"; + +# Clean up +undef $ipv6_table; + +# ============================================================================ +# Performance tips +# ============================================================================ + +print "Performance Tips\n"; +print "-" x 40, "\n"; +print <<'TIPS'; + +1. Use batch lookups for multiple addresses: + my @results = $table->lookup_batch(\@addresses); + +2. Reuse table objects - avoid frequent create/destroy cycles + +3. For highest performance, keep tables in scope during + lookup-intensive operations + +4. The underlying C library uses SIMD optimizations automatically + +TIPS + +print "=" x 60, "\n"; +print "Example completed successfully!\n"; +print "=" x 60, "\n"; diff --git a/bindings/perl/lib/Net/LPM.pm b/bindings/perl/lib/Net/LPM.pm new file mode 100644 index 0000000..fc05905 --- /dev/null +++ b/bindings/perl/lib/Net/LPM.pm @@ -0,0 +1,451 @@ +package Net::LPM; + +use strict; +use warnings; +use Carp qw(croak); + +our $VERSION = '2.0.0'; + +# Load XS code first +require XSLoader; +XSLoader::load('Net::LPM', $VERSION); + +# Export constants - must be after XSLoader +use Exporter 'import'; +our @EXPORT_OK = qw(LPM_INVALID_NEXT_HOP); +our %EXPORT_TAGS = ( + constants => [qw(LPM_INVALID_NEXT_HOP)], + all => \@EXPORT_OK, +); + +# Constant for invalid next hop (0xFFFFFFFF) +# Define as literal to avoid load-order issues +use constant LPM_INVALID_NEXT_HOP => 0xFFFFFFFF; + +=head1 NAME + +Net::LPM - High-performance Perl bindings for liblpm Longest Prefix Match library + +=head1 VERSION + +Version 2.0.0 + +=head1 SYNOPSIS + + use Net::LPM; + + # Create an IPv4 routing table + my $table = Net::LPM->new_ipv4(); + + # Insert routes (CIDR notation) + $table->insert("192.168.0.0/16", 100); + $table->insert("10.0.0.0/8", 200); + $table->insert("0.0.0.0/0", 999); # Default route + + # Lookup an address + my $next_hop = $table->lookup("192.168.1.1"); + if (defined $next_hop) { + print "Next hop: $next_hop\n"; # Prints: Next hop: 100 + } + + # Batch lookup (more efficient for multiple addresses) + my @addresses = qw(192.168.1.1 10.1.1.1 172.16.0.1); + my @next_hops = $table->lookup_batch(\@addresses); + + # Delete a route + $table->delete("10.0.0.0/8"); + + # Table is automatically destroyed when $table goes out of scope + +=head1 DESCRIPTION + +Net::LPM provides high-performance Perl bindings to the liblpm C library +for Longest Prefix Match (LPM) routing table operations. + +LPM is a fundamental operation in IP routing where, given an IP address, +you need to find the most specific (longest) matching prefix in a routing +table. For example, if a table contains both 192.168.0.0/16 and +192.168.1.0/24, a lookup for 192.168.1.1 would return the /24 route. + +=head2 Features + +=over 4 + +=item * B: Direct XS bindings to optimized C library + +=item * B: Full support for both IPv4 and IPv6 + +=item * B: DIR-24-8 for IPv4, Wide 16-bit stride for IPv6 + +=item * B: Efficient batch lookups to reduce overhead + +=item * B: Runtime CPU feature detection for optimal performance + +=item * B: Automatic cleanup via Perl's reference counting + +=back + +=head1 CONSTRUCTOR METHODS + +=head2 new_ipv4 + + my $table = Net::LPM->new_ipv4(); + +Creates a new IPv4 routing table using the DIR-24-8 algorithm. +This provides optimal lookup performance with typically 1-2 memory +accesses per lookup. + +Returns a new Net::LPM object. + +Dies on failure (e.g., out of memory). + +=head2 new_ipv6 + + my $table = Net::LPM->new_ipv6(); + +Creates a new IPv6 routing table using the Wide 16-bit stride algorithm. +This is optimized for common /48 prefix allocations. + +Returns a new Net::LPM object. + +Dies on failure. + +=cut + +sub new_ipv4 { + my $class = shift; + my $ptr = _xs_new_ipv4(); + my $self = bless { + _ptr => $ptr, + _type => 'ipv4', + }, $class; + return $self; +} + +sub new_ipv6 { + my $class = shift; + my $ptr = _xs_new_ipv6(); + my $self = bless { + _ptr => $ptr, + _type => 'ipv6', + }, $class; + return $self; +} + +=head1 INSTANCE METHODS + +=head2 insert + + $table->insert($prefix, $next_hop); + $table->insert("192.168.0.0/16", 100); + $table->insert("2001:db8::/32", 200); + +Inserts a route into the table. + +B + +=over 4 + +=item * C<$prefix> - CIDR notation prefix (e.g., "192.168.0.0/16" or "2001:db8::/32") + +=item * C<$next_hop> - Unsigned 32-bit integer representing the next hop + +=back + +Returns true on success. + +Dies on error (invalid prefix format, prefix length out of range, etc.). + +=cut + +sub insert { + my ($self, $prefix, $next_hop) = @_; + + croak "Missing prefix argument" unless defined $prefix; + croak "Missing next_hop argument" unless defined $next_hop; + croak "next_hop must be a non-negative integer" + if $next_hop < 0 || $next_hop != int($next_hop); + croak "next_hop exceeds 32-bit unsigned range" + if $next_hop > 0xFFFFFFFF; + + return _xs_insert($self->{_ptr}, $prefix, $next_hop); +} + +=head2 delete + + my $deleted = $table->delete($prefix); + $table->delete("192.168.0.0/16"); + +Deletes a route from the table. + +B + +=over 4 + +=item * C<$prefix> - CIDR notation prefix to delete + +=back + +Returns true if the prefix was found and deleted, false if not found. + +Dies on error (invalid prefix format). + +=cut + +sub delete { + my ($self, $prefix) = @_; + + croak "Missing prefix argument" unless defined $prefix; + + return _xs_delete($self->{_ptr}, $prefix); +} + +=head2 lookup + + my $next_hop = $table->lookup($address); + my $nh = $table->lookup("192.168.1.1"); + my $nh = $table->lookup("2001:db8::1"); + +Looks up a single IP address in the table. + +B + +=over 4 + +=item * C<$address> - IP address to lookup (dotted-quad for IPv4, colon notation for IPv6) + +=back + +Returns the next hop value if a matching prefix is found, C otherwise. + +Dies on error (invalid address format, wrong IP version). + +=cut + +sub lookup { + my ($self, $address) = @_; + + croak "Missing address argument" unless defined $address; + + return _xs_lookup($self->{_ptr}, $address); +} + +=head2 lookup_batch + + my @next_hops = $table->lookup_batch(\@addresses); + my @results = $table->lookup_batch([qw(192.168.1.1 10.0.0.1 172.16.0.1)]); + +Performs batch lookup for multiple addresses. This is more efficient than +calling lookup() multiple times because it amortizes the Perl/C call overhead. + +B + +=over 4 + +=item * C<\@addresses> - Array reference of IP addresses to lookup + +=back + +Returns a list of next hop values (or C for addresses with no match), +in the same order as the input addresses. + +Dies on error (invalid address, wrong IP version). + +=cut + +sub lookup_batch { + my ($self, $addrs_ref) = @_; + + croak "lookup_batch requires an array reference" + unless ref($addrs_ref) eq 'ARRAY'; + + return () unless @$addrs_ref; + + if ($self->{_type} eq 'ipv6') { + return _xs_lookup_batch_ipv6($self->{_ptr}, $addrs_ref); + } else { + return _xs_lookup_batch_ipv4($self->{_ptr}, $addrs_ref); + } +} + +=head2 is_ipv4 + + if ($table->is_ipv4) { ... } + +Returns true if this is an IPv4 table. + +=cut + +sub is_ipv4 { + my $self = shift; + return $self->{_type} eq 'ipv4'; +} + +=head2 is_ipv6 + + if ($table->is_ipv6) { ... } + +Returns true if this is an IPv6 table. + +=cut + +sub is_ipv6 { + my $self = shift; + return $self->{_type} eq 'ipv6'; +} + +=head2 print_stats + + $table->print_stats(); + +Prints internal statistics about the routing table to STDOUT. +Useful for debugging and performance analysis. + +=cut + +sub print_stats { + my $self = shift; + _xs_print_stats($self->{_ptr}); +} + +=head1 CLASS METHODS + +=head2 version + + my $version = Net::LPM->version(); + +Returns the version string of the underlying liblpm C library. + +=cut + +sub version { + return _xs_get_version(); +} + +=head1 DESTRUCTOR + +The destructor is automatically called when the object goes out of scope +or is explicitly undefined. It frees all C resources associated with +the routing table. + + undef $table; # Explicitly destroy + # Or just let it go out of scope + +=cut + +sub DESTROY { + my $self = shift; + if ($self->{_ptr}) { + _xs_free($self->{_ptr}); + $self->{_ptr} = undef; + } +} + +=head1 EXPORTS + +The following constants can be exported: + + use Net::LPM qw(LPM_INVALID_NEXT_HOP); + use Net::LPM qw(:constants); + use Net::LPM qw(:all); + +=head2 LPM_INVALID_NEXT_HOP + +The value returned internally when no matching prefix is found. +In the Perl API, this is converted to C, but the constant +is available if needed for comparison. + +=head1 PERFORMANCE + +=head2 XS Overhead + +While the C library provides nanosecond-scale lookups, the XS binding +adds some overhead (~5-20ns per call). For high-performance applications: + +=over 4 + +=item * Use C instead of multiple C calls + +=item * Reuse table objects (avoid create/destroy cycles) + +=item * Keep tables in scope during lookup-intensive operations + +=back + +=head2 Batch Operations + +Batch operations amortize the Perl/XS overhead across multiple lookups: + + # Slower: N XS calls + my @results = map { $table->lookup($_) } @addresses; + + # Faster: 1 XS call + my @results = $table->lookup_batch(\@addresses); + +=head1 THREAD SAFETY + +B + +Each table should only be accessed from a single thread. For multi-threaded +applications, either: + +=over 4 + +=item * Use separate tables per thread + +=item * Add external synchronization (e.g., using L) + +=back + +Read-only lookups can be done concurrently if no modifications occur. + +=head1 MEMORY MANAGEMENT + +The module uses Perl's reference counting for automatic memory management. +When a Net::LPM object goes out of scope or is undefined, the underlying +C resources are automatically freed. + +For explicit control: + + undef $table; # Immediately free resources + +=head1 ERROR HANDLING + +Methods die on error with descriptive messages. Use eval or Try::Tiny +for error handling: + + use Try::Tiny; + + try { + $table->insert("invalid-prefix", 100); + } catch { + warn "Insert failed: $_"; + }; + +=head1 SEE ALSO + +=over 4 + +=item * L - liblpm project + +=item * L - Pure Perl IP address manipulation + +=item * L - IP address manipulation with prefix support + +=back + +=head1 AUTHOR + +Murilo Chianfa Emurilo.chianfa@outlook.comE + +=head1 LICENSE + +This library is licensed under the Boost Software License 1.0. + +=head1 COPYRIGHT + +Copyright (c) Murilo Chianfa. All rights reserved. + +=cut + +1; diff --git a/bindings/perl/pm_to_blib b/bindings/perl/pm_to_blib new file mode 100644 index 0000000..e69de29 diff --git a/bindings/perl/t/00-load.t b/bindings/perl/t/00-load.t new file mode 100644 index 0000000..817e0b4 --- /dev/null +++ b/bindings/perl/t/00-load.t @@ -0,0 +1,32 @@ +#!/usr/bin/env perl +# +# 00-load.t - Test module loading and basic sanity +# + +use strict; +use warnings; +use Test::More tests => 8; + +# Test 1: Module loads +BEGIN { + use_ok('Net::LPM', qw(LPM_INVALID_NEXT_HOP)) or BAIL_OUT("Cannot load Net::LPM"); +} + +# Test 2: Module version is defined +ok(defined $Net::LPM::VERSION, 'Module version is defined'); +like($Net::LPM::VERSION, qr/^\d+\.\d+\.\d+$/, 'Version format is X.Y.Z'); + +# Test 3: C library version +my $c_version = Net::LPM->version(); +ok(defined $c_version, 'C library version is defined'); +like($c_version, qr/\d+\.\d+/, 'C library version looks valid'); +diag("C library version: $c_version"); + +# Test 4: Constants +ok(defined &LPM_INVALID_NEXT_HOP, 'LPM_INVALID_NEXT_HOP constant is defined'); +is(LPM_INVALID_NEXT_HOP(), 0xFFFFFFFF, 'LPM_INVALID_NEXT_HOP has expected value'); + +# Test 5: Can create objects +can_ok('Net::LPM', qw(new_ipv4 new_ipv6 insert delete lookup lookup_batch version)); + +diag("Module loaded successfully"); diff --git a/bindings/perl/t/01-ipv4-basic.t b/bindings/perl/t/01-ipv4-basic.t new file mode 100644 index 0000000..8aed05d --- /dev/null +++ b/bindings/perl/t/01-ipv4-basic.t @@ -0,0 +1,71 @@ +#!/usr/bin/env perl +# +# 01-ipv4-basic.t - Test IPv4 basic operations +# + +use strict; +use warnings; +use Test::More tests => 32; + +use Net::LPM; + +# Test table creation +my $table = Net::LPM->new_ipv4(); +ok(defined $table, 'IPv4 table created'); +isa_ok($table, 'Net::LPM'); +ok($table->is_ipv4, 'Table is IPv4'); +ok(!$table->is_ipv6, 'Table is not IPv6'); + +# Test basic insert and lookup +ok($table->insert("192.168.0.0/16", 100), 'Insert 192.168.0.0/16'); +is($table->lookup("192.168.1.1"), 100, 'Lookup 192.168.1.1 returns 100'); +is($table->lookup("192.168.255.255"), 100, 'Lookup 192.168.255.255 returns 100'); +is($table->lookup("192.169.0.1"), undef, 'Lookup 192.169.0.1 returns undef'); + +# Test multiple prefixes +ok($table->insert("10.0.0.0/8", 200), 'Insert 10.0.0.0/8'); +ok($table->insert("172.16.0.0/12", 300), 'Insert 172.16.0.0/12'); + +is($table->lookup("10.1.2.3"), 200, 'Lookup in 10.0.0.0/8'); +is($table->lookup("172.20.0.1"), 300, 'Lookup in 172.16.0.0/12'); +is($table->lookup("8.8.8.8"), undef, 'No match for 8.8.8.8'); + +# Test longest prefix match +ok($table->insert("192.168.1.0/24", 101), 'Insert more specific prefix'); +is($table->lookup("192.168.1.1"), 101, 'Longest prefix match returns 101'); +is($table->lookup("192.168.2.1"), 100, 'Less specific match returns 100'); + +# Test even more specific prefix +ok($table->insert("192.168.1.128/25", 102), 'Insert /25 prefix'); +is($table->lookup("192.168.1.129"), 102, 'Lookup in /25 returns 102'); +is($table->lookup("192.168.1.1"), 101, 'Lookup outside /25 returns 101'); + +# Test default route +ok($table->insert("0.0.0.0/0", 999), 'Insert default route'); +is($table->lookup("8.8.8.8"), 999, 'Previously no-match now hits default'); + +# Test host routes (/32) +ok($table->insert("1.2.3.4/32", 444), 'Insert host route /32'); +is($table->lookup("1.2.3.4"), 444, 'Host route lookup works'); +is($table->lookup("1.2.3.5"), 999, 'Adjacent IP uses default'); + +# Test prefix deletion +ok($table->delete("192.168.1.128/25"), 'Delete /25 prefix'); +is($table->lookup("192.168.1.129"), 101, 'After delete, falls back to /24'); + +# Test delete non-existent +ok(!$table->delete("203.0.113.0/24"), 'Delete non-existent returns false'); + +# Test boundary addresses +ok($table->insert("255.255.255.0/24", 555), 'Insert high address prefix'); +is($table->lookup("255.255.255.255"), 555, 'Lookup 255.255.255.255'); + +# Test zero next hop +ok($table->insert("100.0.0.0/8", 0), 'Insert with next_hop=0'); +is($table->lookup("100.1.1.1"), 0, 'Lookup returns 0 (not undef)'); + +# Clean up +undef $table; +pass('Table destroyed cleanly'); + +diag("IPv4 basic operations tests completed"); diff --git a/bindings/perl/t/02-ipv6-basic.t b/bindings/perl/t/02-ipv6-basic.t new file mode 100644 index 0000000..68d1d63 --- /dev/null +++ b/bindings/perl/t/02-ipv6-basic.t @@ -0,0 +1,65 @@ +#!/usr/bin/env perl +# +# 02-ipv6-basic.t - Test IPv6 basic operations +# + +use strict; +use warnings; +use Test::More tests => 28; + +use Net::LPM; + +# Test table creation +my $table = Net::LPM->new_ipv6(); +ok(defined $table, 'IPv6 table created'); +isa_ok($table, 'Net::LPM'); +ok($table->is_ipv6, 'Table is IPv6'); +ok(!$table->is_ipv4, 'Table is not IPv4'); + +# Test basic insert and lookup +ok($table->insert("2001:db8::/32", 100), 'Insert 2001:db8::/32'); +is($table->lookup("2001:db8::1"), 100, 'Lookup 2001:db8::1 returns 100'); +is($table->lookup("2001:db8:ffff::1"), 100, 'Lookup 2001:db8:ffff::1 returns 100'); +is($table->lookup("2001:db9::1"), undef, 'Lookup 2001:db9::1 returns undef'); + +# Test multiple prefixes +ok($table->insert("2001:db8:1::/48", 200), 'Insert 2001:db8:1::/48'); +ok($table->insert("2001:db8:2::/48", 300), 'Insert 2001:db8:2::/48'); + +is($table->lookup("2001:db8:1::1"), 200, 'Lookup in 2001:db8:1::/48'); +is($table->lookup("2001:db8:2::ffff"), 300, 'Lookup in 2001:db8:2::/48'); + +# Test longest prefix match +ok($table->insert("2001:db8:1:1::/64", 201), 'Insert more specific /64'); +is($table->lookup("2001:db8:1:1::1"), 201, 'Longest prefix match returns 201'); +is($table->lookup("2001:db8:1:2::1"), 200, 'Less specific match returns 200'); + +# Test default route +ok($table->insert("::/0", 999), 'Insert default route'); +is($table->lookup("2607:f8b0:4004:800::200e"), 999, 'Google DNS hits default route'); + +# Test link-local range +ok($table->insert("fe80::/10", 500), 'Insert link-local prefix'); +is($table->lookup("fe80::1"), 500, 'Link-local lookup works'); + +# Test host route (/128) +ok($table->insert("2001:db8:1:1::dead:beef/128", 666), 'Insert host route /128'); +is($table->lookup("2001:db8:1:1::dead:beef"), 666, 'Host route lookup works'); +is($table->lookup("2001:db8:1:1::dead:beee"), 201, 'Adjacent IP uses /64'); + +# Test delete +ok($table->delete("2001:db8:1::/48"), 'Delete /48 prefix'); +is($table->lookup("2001:db8:1:2::1"), 100, 'After delete, falls back to /32'); + +# Test delete non-existent (note: C library returns success for this) +ok($table->delete("2001:db9::/32"), 'Delete non-existent completes without error'); + +# Test zero next hop +ok($table->insert("fc00::/7", 0), 'Insert with next_hop=0'); +is($table->lookup("fd00::1"), 0, 'Lookup returns 0 (not undef)'); + +# Clean up +undef $table; +pass('Table destroyed cleanly'); + +diag("IPv6 basic operations tests completed"); diff --git a/bindings/perl/t/03-batch.t b/bindings/perl/t/03-batch.t new file mode 100644 index 0000000..5f82229 --- /dev/null +++ b/bindings/perl/t/03-batch.t @@ -0,0 +1,87 @@ +#!/usr/bin/env perl +# +# 03-batch.t - Test batch lookup operations +# + +use strict; +use warnings; +use Test::More tests => 20; + +use Net::LPM; + +# IPv4 batch tests +{ + my $table = Net::LPM->new_ipv4(); + + # Setup routes + $table->insert("192.168.0.0/16", 100); + $table->insert("10.0.0.0/8", 200); + $table->insert("172.16.0.0/12", 300); + $table->insert("0.0.0.0/0", 999); + + # Test basic batch lookup + my @addrs = qw(192.168.1.1 10.1.1.1 172.20.0.1 8.8.8.8); + my @results = $table->lookup_batch(\@addrs); + + is(scalar @results, 4, 'Batch returns correct number of results'); + is($results[0], 100, 'First result correct'); + is($results[1], 200, 'Second result correct'); + is($results[2], 300, 'Third result correct'); + is($results[3], 999, 'Fourth result (default) correct'); + + # Test empty batch + my @empty = $table->lookup_batch([]); + is(scalar @empty, 0, 'Empty batch returns empty list'); + + # Test single-element batch + my @single = $table->lookup_batch(["192.168.1.1"]); + is(scalar @single, 1, 'Single-element batch returns one result'); + is($single[0], 100, 'Single-element result correct'); + + # Test larger batch + my @large_addrs; + for my $i (0..99) { + push @large_addrs, "192.168.1.$i"; + } + my @large_results = $table->lookup_batch(\@large_addrs); + is(scalar @large_results, 100, 'Large batch returns 100 results'); + is($large_results[0], 100, 'Large batch first result correct'); + is($large_results[99], 100, 'Large batch last result correct'); + + undef $table; +} + +# IPv6 batch tests +{ + my $table = Net::LPM->new_ipv6(); + + # Setup routes + $table->insert("2001:db8::/32", 100); + $table->insert("2001:db8:1::/48", 200); + $table->insert("fe80::/10", 300); + $table->insert("::/0", 999); + + # Test basic batch lookup + my @addrs = qw(2001:db8::1 2001:db8:1::1 fe80::1 2607:f8b0::1); + my @results = $table->lookup_batch(\@addrs); + + is(scalar @results, 4, 'IPv6 batch returns correct number of results'); + is($results[0], 100, 'IPv6 first result correct'); + is($results[1], 200, 'IPv6 second result (more specific) correct'); + is($results[2], 300, 'IPv6 third result correct'); + is($results[3], 999, 'IPv6 fourth result (default) correct'); + + # Test batch with no matches (before default route) + $table->delete("::/0"); + my @addrs2 = qw(2607:f8b0::1 2607:f8b0::2); + my @results2 = $table->lookup_batch(\@addrs2); + is(scalar @results2, 2, 'Batch with no matches returns correct count'); + is($results2[0], undef, 'No match returns undef'); + is($results2[1], undef, 'No match returns undef'); + + undef $table; +} + +pass('Batch tests completed'); + +diag("Batch lookup tests completed"); diff --git a/bindings/perl/t/04-memory.t b/bindings/perl/t/04-memory.t new file mode 100644 index 0000000..62b9d72 --- /dev/null +++ b/bindings/perl/t/04-memory.t @@ -0,0 +1,55 @@ +#!/usr/bin/env perl +# +# 04-memory.t - Test memory management +# + +use strict; +use warnings; +use Test::More tests => 15; + +use Net::LPM; + +# Test multiple table creation/destruction +{ + my @tables; + for my $i (1..5) { + my $t = Net::LPM->new_ipv4(); + ok(defined $t, "Create table $i"); + $t->insert("10.$i.0.0/16", $i * 100); + push @tables, $t; + } + + # Verify all tables work + for my $i (1..5) { + is($tables[$i-1]->lookup("10.$i.1.1"), $i * 100, "Table $i lookup works"); + } + + # Tables go out of scope here +} +pass('Multiple tables destroyed cleanly'); + +# Test explicit destruction +{ + my $table = Net::LPM->new_ipv4(); + $table->insert("192.168.0.0/16", 100); + is($table->lookup("192.168.1.1"), 100, 'Lookup before undef'); + + undef $table; + pass('Explicit undef works'); +} + +# Test table reuse (same variable) +{ + my $table = Net::LPM->new_ipv4(); + $table->insert("192.168.0.0/16", 100); + + # Replace with new table + $table = Net::LPM->new_ipv6(); + ok($table->is_ipv6, 'Replaced table is IPv6'); + + $table->insert("2001:db8::/32", 200); + is($table->lookup("2001:db8::1"), 200, 'New table works'); +} + +diag("Memory management tests completed"); +diag("For leak detection, run: make valgrind"); diff --git a/bindings/perl/t/05-errors.t b/bindings/perl/t/05-errors.t new file mode 100644 index 0000000..a2334cd --- /dev/null +++ b/bindings/perl/t/05-errors.t @@ -0,0 +1,110 @@ +#!/usr/bin/env perl +# +# 05-errors.t - Test error handling +# + +use strict; +use warnings; +use Test::More tests => 18; + +use Net::LPM; + +# Test invalid prefix format +{ + my $table = Net::LPM->new_ipv4(); + + eval { $table->insert("invalid", 100); }; + like($@, qr/Invalid prefix/, 'Invalid prefix format rejected'); + + eval { $table->insert("192.168.0.0", 100); }; + like($@, qr/Invalid prefix/, 'Missing /prefix_len rejected'); + + eval { $table->insert("192.168.0.0/", 100); }; + like($@, qr/Invalid prefix/, 'Empty prefix length rejected'); + + eval { $table->insert("192.168.0.0/33", 100); }; + like($@, qr/Invalid prefix/, 'Prefix length > 32 rejected for IPv4'); + + eval { $table->insert("192.168.0.0/-1", 100); }; + like($@, qr/Invalid prefix/, 'Negative prefix length rejected'); + + undef $table; +} + +# Test invalid address format +{ + my $table = Net::LPM->new_ipv4(); + $table->insert("0.0.0.0/0", 999); + + eval { $table->lookup("invalid"); }; + like($@, qr/Invalid.*address/, 'Invalid address format rejected'); + + eval { $table->lookup("256.256.256.256"); }; + like($@, qr/Invalid.*address/, 'Invalid IP octets rejected'); + + eval { $table->lookup("2001:db8::1"); }; + like($@, qr/Invalid.*address/, 'IPv6 address in IPv4 table rejected'); + + undef $table; +} + +# Test IPv6 errors +{ + my $table = Net::LPM->new_ipv6(); + + eval { $table->insert("2001:db8::/129", 100); }; + like($@, qr/Invalid prefix/, 'Prefix length > 128 rejected for IPv6'); + + eval { $table->lookup("192.168.1.1"); }; + like($@, qr/Invalid.*address/, 'IPv4 address in IPv6 table rejected'); + + eval { $table->lookup("invalid::address"); }; + like($@, qr/Invalid.*address/, 'Invalid IPv6 address rejected'); + + undef $table; +} + +# Test missing arguments +{ + my $table = Net::LPM->new_ipv4(); + + eval { $table->insert(undef, 100); }; + like($@, qr/Missing prefix/, 'Missing prefix argument detected'); + + eval { $table->insert("192.168.0.0/16"); }; + like($@, qr/Missing next_hop/, 'Missing next_hop argument detected'); + + eval { $table->delete(); }; + like($@, qr/Missing prefix/, 'Missing delete prefix detected'); + + eval { $table->lookup(); }; + like($@, qr/Missing address/, 'Missing lookup address detected'); + + undef $table; +} + +# Test invalid next_hop values +{ + my $table = Net::LPM->new_ipv4(); + + eval { $table->insert("192.168.0.0/16", -1); }; + like($@, qr/non-negative|integer/, 'Negative next_hop rejected'); + + eval { $table->insert("192.168.0.0/16", 1.5); }; + like($@, qr/integer/, 'Float next_hop rejected'); + + undef $table; +} + +# Test batch errors +{ + my $table = Net::LPM->new_ipv4(); + $table->insert("0.0.0.0/0", 999); + + eval { $table->lookup_batch("not_an_array"); }; + like($@, qr/array reference/, 'Non-array-ref to lookup_batch rejected'); + + undef $table; +} + +diag("Error handling tests completed"); diff --git a/bindings/perl/typemap b/bindings/perl/typemap new file mode 100644 index 0000000..bdfa213 --- /dev/null +++ b/bindings/perl/typemap @@ -0,0 +1,30 @@ +# Typemap for Net::LPM XS bindings + +TYPEMAP +NetLPM * T_PTROBJ_SPECIAL +AV * T_AVREF_SPECIAL + +INPUT +T_PTROBJ_SPECIAL + if (sv_derived_from($arg, \"Net::LPM\")) { + IV tmp = SvIV((SV*)SvRV($arg)); + $var = INT2PTR($type, tmp); + } + else if (SvOK($arg) && SvIOK($arg)) { + $var = INT2PTR($type, SvIV($arg)); + } + else { + croak(\"$var is not of type Net::LPM\"); + } + +T_AVREF_SPECIAL + if (SvROK($arg) && SvTYPE(SvRV($arg)) == SVt_PVAV) { + $var = (AV*)SvRV($arg); + } + else { + croak(\"$var is not an array reference\"); + } + +OUTPUT +T_PTROBJ_SPECIAL + sv_setref_pv($arg, \"Net::LPM\", (void*)$var); diff --git a/docker/Dockerfile.perl b/docker/Dockerfile.perl new file mode 100644 index 0000000..861632e --- /dev/null +++ b/docker/Dockerfile.perl @@ -0,0 +1,134 @@ +# Perl bindings container for liblpm +# Multi-stage build: builder (liblpm, Perl XS) -> runtime +# +# Usage: +# Build: docker build -f docker/Dockerfile.perl -t liblpm-perl . +# Run tests: docker run --rm liblpm-perl +# Interactive: docker run -it --rm liblpm-perl bash + +# ============================================================================ +# Stage 1: Build liblpm C library +# ============================================================================ +FROM ubuntu:25.10 AS liblpm-builder + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + gcc-15 \ + g++-15 \ + cmake \ + ninja-build \ + git \ + pkg-config \ + libc6-dev \ + libnuma-dev \ + python3 \ + && rm -rf /var/lib/apt/lists/* + +# Set compiler +RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 100 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-15 100 + +WORKDIR /build + +COPY . /build/ + +# Initialize submodules and build +RUN git config --global --add safe.directory /build && \ + if [ -f .gitmodules ]; then git submodule update --init --recursive; fi && \ + mkdir -p build && cd build && \ + cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTS=OFF \ + -DBUILD_BENCHMARKS=OFF \ + -DENABLE_NATIVE_ARCH=OFF \ + -GNinja \ + .. && \ + ninja && \ + ninja install + +# ============================================================================ +# Stage 2: Build Perl bindings +# ============================================================================ +FROM perl:5.40-bookworm AS perl-builder + +# Install build dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + gcc \ + libc6-dev \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* + +# Copy liblpm from previous stage +COPY --from=liblpm-builder /usr/local/lib/liblpm* /usr/local/lib/ +COPY --from=liblpm-builder /usr/local/include/lpm /usr/local/include/lpm +COPY --from=liblpm-builder /usr/local/lib/pkgconfig/liblpm.pc /usr/local/lib/pkgconfig/ + +# Update library cache +RUN ldconfig + +WORKDIR /build/perl + +# Copy Perl bindings source +COPY bindings/perl/ ./ + +# Build Perl module +RUN perl Makefile.PL && make + +# Run tests during build to verify +RUN prove -Iblib/lib -Iblib/arch t/*.t + +# Create test script +RUN echo '#!/bin/bash\n\ +set -e\n\ +\n\ +echo "=== liblpm Perl Bindings Test Suite ==="\n\ +echo ""\n\ +\n\ +cd /app\n\ +\n\ +echo "=== Module Information ==="\n\ +perl -Iblib/lib -Iblib/arch -MNet::LPM -e '\''print "Net::LPM version: $Net::LPM::VERSION\n"; print "liblpm version: " . Net::LPM->version() . "\n";'\''\n\ +echo ""\n\ +\n\ +echo "=== Running Tests ==="\n\ +prove -Iblib/lib -Iblib/arch t/*.t\n\ +\n\ +echo ""\n\ +echo "=== Running Example ==="\n\ +perl -Iblib/lib -Iblib/arch examples/basic_example.pl\n\ +\n\ +echo ""\n\ +echo "=== Perl Bindings Test Summary ==="\n\ +echo "All tests passed!"\n\ +' > /test.sh && chmod +x /test.sh + +# ============================================================================ +# Stage 3: Runtime +# ============================================================================ +FROM perl:5.40-slim-bookworm AS runtime + +# Install runtime dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + libc6 \ + libgcc-s1 \ + && rm -rf /var/lib/apt/lists/* + +# Copy liblpm runtime library +COPY --from=liblpm-builder /usr/local/lib/liblpm.so* /usr/local/lib/ + +# Update library cache +RUN ldconfig + +WORKDIR /app + +# Copy built Perl module +COPY --from=perl-builder /build/perl/blib /app/blib/ +COPY --from=perl-builder /build/perl/t /app/t/ +COPY --from=perl-builder /build/perl/examples /app/examples/ +COPY --from=perl-builder /test.sh /app/ + +# Default command runs tests +CMD ["/app/test.sh"] diff --git a/docker/README.md b/docker/README.md index c9edc06..70c64b1 100644 --- a/docker/README.md +++ b/docker/README.md @@ -12,6 +12,7 @@ Quick reference for liblpm Docker images. | `liblpm-fuzz` | AFL++ fuzzing | Security testing | | `liblpm-cpp` | C++ bindings | C++ wrapper testing | | `liblpm-go` | Go bindings | Go wrapper testing | +| `liblpm-perl` | Perl XS bindings | Perl wrapper testing | | `liblpm-benchmark` | DPDK benchmarking | Performance comparison | | `liblpm-deb` | DEB package builder | Building Debian/Ubuntu packages | | `liblpm-rpm` | RPM package builder | Building RHEL/Fedora/Rocky packages | @@ -79,6 +80,9 @@ docker run --rm liblpm-cpp # Test Go bindings docker run --rm liblpm-go + +# Test Perl XS bindings +docker run --rm liblpm-perl ``` ### Benchmarking @@ -198,6 +202,34 @@ Go bindings with cgo support. docker run --rm liblpm-go ``` +### liblpm-perl + +Perl 5.40 XS bindings for high-performance access. + +**Size:** ~400MB + +**Features:** +- Perl 5.40+ support +- Native XS bindings (direct C interface) +- Object-oriented Perl API +- Batch lookup operations +- Automatic memory management via DESTROY +- Comprehensive test suite (121 tests) + +```bash +# Run tests +docker run --rm liblpm-perl + +# Interactive development +docker run -it --rm liblpm-perl bash + +# Run examples +docker run --rm liblpm-perl perl -Iblib/lib -Iblib/arch /app/examples/basic_example.pl + +# Run specific test +docker run --rm liblpm-perl prove -Iblib/lib -Iblib/arch /app/t/01-ipv4-basic.t +``` + ### liblpm-benchmark DPDK 24.11 integration for performance comparison. @@ -249,6 +281,7 @@ Approximate sizes (uncompressed): | liblpm-fuzz | ~1GB | | liblpm-cpp | ~800MB | | liblpm-go | ~600MB | +| liblpm-perl | ~400MB | | liblpm-benchmark | ~1.5GB | | liblpm-deb | ~400MB | | liblpm-rpm | ~500MB | diff --git a/scripts/docker-build.sh b/scripts/docker-build.sh index 80f9949..b56af23 100755 --- a/scripts/docker-build.sh +++ b/scripts/docker-build.sh @@ -51,6 +51,7 @@ Available Images: fuzz - AFL++ fuzzing environment cpp - C++ bindings go - Go bindings + perl - Perl XS bindings benchmark - DPDK benchmark environment all - Build all images (default) @@ -114,7 +115,7 @@ while [[ $# -gt 0 ]]; do VERBOSE="--progress=plain" shift ;; - base|dev|test|fuzz|cpp|go|benchmark|all) + base|dev|test|fuzz|cpp|go|perl|benchmark|all) IMAGES+=("$1") shift ;; @@ -217,6 +218,9 @@ build_images() { go) build_image "go" "${DOCKER_DIR}/Dockerfile.go" ;; + perl) + build_image "perl" "${DOCKER_DIR}/Dockerfile.perl" + ;; benchmark) build_image "benchmark" "${DOCKER_DIR}/Dockerfile.benchmark" ;; @@ -227,6 +231,7 @@ build_images() { build_image "fuzz" "${DOCKER_DIR}/Dockerfile.fuzz" build_image "cpp" "${DOCKER_DIR}/Dockerfile.cpp" build_image "go" "${DOCKER_DIR}/Dockerfile.go" + build_image "perl" "${DOCKER_DIR}/Dockerfile.perl" build_image "benchmark" "${DOCKER_DIR}/Dockerfile.benchmark" ;; *)