├── pkg ├── debian │ ├── compat │ ├── source │ │ └── format │ ├── liblpm1.install │ ├── liblpm-lua.install │ ├── liblpm-dev.install │ ├── changelog │ ├── rules │ └── control ├── version.txt ├── Makefile └── SPECS │ └── liblpm.spec ├── src ├── jni │ ├── README.md │ ├── Makefile │ ├── org │ │ └── netbsd │ │ │ └── liblpm │ │ │ ├── LPM.java │ │ │ └── LPMTest.java │ └── org_netbsd_liblpm_LPM.c ├── lpm.h ├── lua │ ├── Makefile │ ├── t_lpm.lua │ └── lpm_lua.c ├── Makefile ├── t_lpm.c └── lpm.c ├── LICENSE ├── .github └── workflows │ └── ci.yaml └── README.md /pkg/debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /pkg/version.txt: -------------------------------------------------------------------------------- 1 | 0.2.1 2 | -------------------------------------------------------------------------------- /pkg/debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /pkg/debian/liblpm1.install: -------------------------------------------------------------------------------- 1 | usr/lib/*/lib*.so.* 2 | -------------------------------------------------------------------------------- /pkg/debian/liblpm-lua.install: -------------------------------------------------------------------------------- 1 | usr/lib/*/lua/*/*.so 2 | -------------------------------------------------------------------------------- /pkg/debian/liblpm-dev.install: -------------------------------------------------------------------------------- 1 | usr/include/* 2 | usr/lib/*/lib*.a 3 | -------------------------------------------------------------------------------- /pkg/debian/changelog: -------------------------------------------------------------------------------- 1 | lpm (0.0.1) unstable; urgency=medium 2 | 3 | * Initial release. 4 | 5 | -- rmind Fri, 18 May 2018 23:34:33 +0100 6 | -------------------------------------------------------------------------------- /src/jni/README.md: -------------------------------------------------------------------------------- 1 | # LPM library Java bindings 2 | 3 | ## Building 4 | 5 | The below is an example of building on Debian 10. Set the `JAVA_HOME` 6 | according to your JDK version and system. 7 | 8 | ```shell 9 | $ uname -srv 10 | Linux 4.9.0-9-amd64 #1 SMP Debian 4.9.168-1 (2019-04-12) 11 | $ sudo apt-get install openjdk-8-jre-headless openjdk-8-jdk-headless 12 | ... 13 | $ cd liblpm/src/jni 14 | $ JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 make test 15 | ``` 16 | 17 | This will produce the `liblpm.jar` file. Use it in your project. 18 | -------------------------------------------------------------------------------- /pkg/Makefile: -------------------------------------------------------------------------------- 1 | PROJ= liblpm 2 | 3 | all: 4 | @ echo "targets" 5 | @ echo " make rpm" 6 | @ echo " make deb" 7 | 8 | rpm: 9 | mkdir -p SOURCES && tar czpvf SOURCES/$(PROJ).tar.gz ../src 10 | rpmbuild -ba -v --define "_topdir ${PWD}" SPECS/$(PROJ).spec 11 | @ echo && printf "\x1B[32mRPM packages:\033[0m\n" && ls -1 RPMS/* 12 | 13 | deb: 14 | cp -R ../src ./SOURCES 15 | dpkg-buildpackage -rfakeroot -us -uc -b 16 | @ echo && printf "\x1B[32mDEB packages:\033[0m\n" && ls -1 ../*.deb 17 | 18 | clean: 19 | rm -rf BUILD BUILDROOT RPMS SOURCES SRPMS 20 | 21 | .PHONY: all rpm deb clean 22 | -------------------------------------------------------------------------------- /src/lpm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Mindaugas Rasiukevicius 3 | * All rights reserved. 4 | * 5 | * Use is subject to license terms, as specified in the LICENSE file. 6 | */ 7 | 8 | #ifndef _LPM_H_ 9 | #define _LPM_H_ 10 | 11 | __BEGIN_DECLS 12 | 13 | typedef struct lpm lpm_t; 14 | typedef void (*lpm_dtor_t)(void *, const void *, size_t, void *); 15 | 16 | lpm_t * lpm_create(void); 17 | void lpm_destroy(lpm_t *); 18 | void lpm_clear(lpm_t *, lpm_dtor_t, void *); 19 | 20 | int lpm_insert(lpm_t *, const void *, size_t, unsigned, void *); 21 | int lpm_remove(lpm_t *, const void *, size_t, unsigned); 22 | void * lpm_lookup(lpm_t *, const void *, size_t); 23 | void * lpm_lookup_prefix(lpm_t *, const void *, size_t, unsigned); 24 | int lpm_strtobin(const char *, void *, size_t *, unsigned *); 25 | 26 | __END_DECLS 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /pkg/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | #export DH_VERBOSE = 1 4 | 5 | PKGVERSION:=$(shell cat version.txt) 6 | DEB_HOST_MULTIARCH?=$(shell dpkg-architecture -qDEB_HOST_MULTIARCH) 7 | LIBDIR:=/usr/lib/$(DEB_HOST_MULTIARCH) 8 | INCDIR:=/usr/include 9 | 10 | LUA_VER?=5.1 11 | LUA_CFLAGS:=$(shell pkg-config --cflags lua$(LUA_VER)) 12 | LUA_LDFLAGS:=$(shell pkg-config --cflags --libs lua$(LUA_VER)) 13 | 14 | %: 15 | dh $@ --sourcedirectory=SOURCES --parallel 16 | 17 | override_dh_auto_build: 18 | dh_auto_build -- lib LIBDIR=$(LIBDIR) 19 | dh_auto_build -- -C lua lib \ 20 | LUA_CFLAGS=$(LUA_CFLAGS) LUA_LDFLAGS=$(LUA_LDFLAGS) 21 | 22 | override_dh_auto_test: 23 | dh_auto_test tests 24 | 25 | override_dh_auto_install: 26 | dh_auto_install -- install \ 27 | LIBDIR=$(LIBDIR) INCDIR=$(INCDIR) 28 | dh_auto_install -- -C lua install \ 29 | LUA_CFLAGS=$(LUA_CFLAGS) LUA_LDFLAGS=$(LUA_LDFLAGS) \ 30 | LUA_LIBDIR=$(LIBDIR)/lua/$(LUA_VER) 31 | 32 | override_dh_strip: 33 | dh_strip -p liblpm1 --dbg-package=liblpm1-dbg 34 | dh_strip -a --remaining-packages 35 | 36 | override_dh_gencontrol: 37 | dh_gencontrol -- -v$(PKGVERSION) 38 | -------------------------------------------------------------------------------- /pkg/debian/control: -------------------------------------------------------------------------------- 1 | Source: lpm 2 | Priority: extra 3 | Maintainer: https://github.com/rmind 4 | Build-Depends: 5 | debhelper (>= 9), 6 | make, 7 | libtool, 8 | liblua5.1-0-dev 9 | Standards-Version: 3.9.1 10 | Homepage: https://github.com/rmind/liblpm 11 | License: BSD-2-clause 12 | 13 | Package: liblpm1 14 | Section: lib 15 | Architecture: any 16 | Depends: ${shlibs:Depends}, ${misc:Depends} 17 | Description: Longest Prefix Match (LPM) library 18 | Longest Prefix Match (LPM) library supporting IPv4 and IPv6. 19 | 20 | Package: liblpm-lua 21 | Section: lib 22 | Architecture: any 23 | Depends: ${shlibs:Depends}, ${misc:Depends}, liblpm1 (= ${binary:Version}) 24 | Provides: ${lua:Provides} 25 | Description: Lua bindings for Longest Prefix Match (LPM) library 26 | Lua bindings for Longest Prefix Match (LPM) library supporting IPv4 and IPv6. 27 | 28 | Package: liblpm1-dbg 29 | Section: debug 30 | Architecture: any 31 | Depends: ${misc:Depends}, liblpm1 (= ${binary:Version}) 32 | Description: Debug symbols for liblpm1 33 | Debug symbols for liblpm1. 34 | 35 | Package: liblpm-dev 36 | Section: libdevel 37 | Architecture: any 38 | Depends: ${shlibs:Depends}, ${misc:Depends}, liblpm1 (= ${binary:Version}) 39 | Description: Development files for liblpm1 40 | Development files for liblpm1. 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2016 Mindaugas Rasiukevicius 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | -------------------------------------------------------------------------------- /pkg/SPECS/liblpm.spec: -------------------------------------------------------------------------------- 1 | %define version %(cat %{_topdir}/version.txt) 2 | %define luaver 5.1 3 | %define lualibdir %{_libdir}/lua/%{luaver} 4 | 5 | Name: liblpm 6 | Version: %{version} 7 | Release: 1%{?dist} 8 | Summary: Longest Prefix Match (LPM) library 9 | Group: System Environment/Libraries 10 | License: BSD 11 | URL: https://github.com/rmind/liblpm 12 | Source0: liblpm.tar.gz 13 | 14 | BuildRequires: make 15 | BuildRequires: libtool 16 | BuildRequires: lua >= %{luaver}, lua-devel >= %{luaver} 17 | Requires: lua >= %{luaver} 18 | 19 | %description 20 | Longest Prefix Match (LPM) library supporting IPv4 and IPv6. 21 | 22 | %prep 23 | %setup -q -n src 24 | 25 | %check 26 | make tests 27 | 28 | %build 29 | make %{?_smp_mflags} lib LIBDIR=%{_libdir} 30 | make %{?_smp_mflags} -C lua lib LIBDIR=%{lualibdir} 31 | 32 | %install 33 | make install \ 34 | DESTDIR=%{buildroot} \ 35 | LIBDIR=%{_libdir} \ 36 | INCDIR=%{_includedir} \ 37 | MANDIR=%{_mandir} 38 | make -C lua install \ 39 | DESTDIR=%{buildroot} \ 40 | LUA_LIBDIR=%{lualibdir} 41 | 42 | %files 43 | %{_libdir}/liblpm.* 44 | 45 | %{_includedir}/* 46 | #%{_mandir}/* 47 | 48 | # 49 | # Lua module. 50 | # 51 | %package lua 52 | Summary: Lua bindings for the LPM library 53 | Group: Development/Libraries 54 | Requires: %{name} = %{version}-%{release} 55 | 56 | %description lua 57 | Lua bindings for the LPM library. 58 | 59 | %files lua 60 | %{lualibdir}/* 61 | 62 | %changelog 63 | * Wed Jun 1 2016 Mindaugas Rasiukevicius 0.2.0-1 64 | - Initial release of the liblpm library. 65 | -------------------------------------------------------------------------------- /src/jni/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This file is in the Public Domain. 3 | # 4 | 5 | CFLAGS+= -std=c99 -O2 -g -Wall -Wextra -Werror 6 | CFLAGS+= -D_POSIX_C_SOURCE=200809L 7 | CFLAGS+= -D_GNU_SOURCE -D_DEFAULT_SOURCE 8 | 9 | CFLAGS+= -Wno-unused-parameter 10 | 11 | ifeq ($(MAKECMDGOALS),tests) 12 | DEBUG= 1 13 | endif 14 | 15 | ifeq ($(DEBUG),1) 16 | CFLAGS+= -O0 -DDEBUG -fno-omit-frame-pointer 17 | else 18 | CFLAGS+= -DNDEBUG 19 | endif 20 | 21 | JAVA_HOME?= /usr/lib/jvm/java/ 22 | 23 | CFLAGS+= -I.. -I$(JAVA_HOME)/include 24 | # Sadly, jni_md.h location is OS dependent 25 | CFLAGS+= -I$(shell dirname $(shell find -L $(JAVA_HOME) -name jni_md.h | head -1)) 26 | 27 | OBJS= org_netbsd_liblpm_LPM.o ../lpm.o 28 | LIB= org_netbsd_liblpm_LPM 29 | 30 | JAR_LIB= liblpm.jar 31 | 32 | JAR= $(JAVA_HOME)/bin/jar 33 | JAVA= $(JAVA_HOME)/bin/java 34 | JAVAC= $(JAVA_HOME)/bin/javac 35 | 36 | %.o: %.c 37 | $(CC) $(CFLAGS) -fPIC -c $< 38 | 39 | $(LIB).so: $(OBJS) 40 | $(CC) $(LDFLAGS) -fPIC -shared -o $@ $(notdir $^) 41 | 42 | org_netbsd_liblpm_LPM.o: org_netbsd_liblpm_LPM.h org_netbsd_liblpm_LPM.c 43 | 44 | org_netbsd_liblpm_LPM.h org/netbsd/liblpm/LPM.class: org/netbsd/liblpm/LPM.java 45 | $(JAVAC) -h . $< 46 | 47 | %.class: %.java 48 | $(JAVAC) $< 49 | 50 | liblpm.jar: org/netbsd/liblpm/LPM.class $(LIB).so 51 | $(JAR) cf $(JAR_LIB) org/netbsd/liblpm/LPM.class $(LIB).so 52 | 53 | .PHONY: test clean 54 | 55 | test: liblpm.jar org/netbsd/liblpm/LPMTest.class 56 | $(JAVA) -classpath .:liblpm.jar org.netbsd.liblpm.LPMTest 57 | 58 | clean: 59 | @ rm -rf *.so *.o \ 60 | org_netbsd_liblpm_LPM.h org/netbsd/liblpm/*.class $(JAR_LIB) 61 | -------------------------------------------------------------------------------- /src/lua/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This file is in the Public Domain. 3 | # 4 | 5 | PROJ= lpm 6 | 7 | CFLAGS+= -std=c99 -O2 -g -Wall -Wextra -Werror 8 | CFLAGS+= -D_POSIX_C_SOURCE=200809L 9 | CFLAGS+= -D_GNU_SOURCE -D_DEFAULT_SOURCE 10 | 11 | # 12 | # Extended warning flags. 13 | # 14 | CFLAGS+= -Wno-unknown-warning-option # gcc vs clang 15 | 16 | CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith 17 | CFLAGS+= -Wmissing-declarations -Wredundant-decls -Wnested-externs 18 | CFLAGS+= -Wshadow -Wcast-qual -Wcast-align -Wwrite-strings 19 | CFLAGS+= -Wold-style-definition 20 | CFLAGS+= -Wsuggest-attribute=noreturn -Wjump-misses-init 21 | 22 | # New GCC 6/7 flags: 23 | #CFLAGS+= -Wduplicated-cond -Wmisleading-indentation -Wnull-dereference 24 | #CFLAGS+= -Wduplicated-branches -Wrestrict 25 | 26 | ifeq ($(DEBUG),1) 27 | CFLAGS+= -Og -DDEBUG -fno-omit-frame-pointer 28 | CFLAGS+= -fsanitize=address 29 | LDFLAGS+= -fsanitize=address 30 | else 31 | CFLAGS+= -DNDEBUG 32 | endif 33 | 34 | # Lua module 35 | LUA_CFLAGS?= $(shell pkg-config --cflags lua) 36 | LUA_LDFLAGS?= $(shell pkg-config --cflags --libs lua) 37 | CFLAGS+= $(LUA_CFLAGS) -I.. -fPIC 38 | LDFLAGS+= $(LUA_LDFLAGS) 39 | LDFLAGS+= -L../.libs -llpm 40 | 41 | OBJS= lpm_lua.o 42 | LIB= lpm 43 | 44 | install: LUA_ILIBDIR= $(DESTDIR)/$(LUA_LIBDIR)/ 45 | 46 | obj: $(OBJS) 47 | 48 | lib: $(LIB).so 49 | 50 | %.o: %.c 51 | $(CC) $(CFLAGS) -c $< 52 | 53 | $(LIB).so: $(OBJS) 54 | $(CC) $(LDFLAGS) -fPIC -shared -o $@ $(notdir $^) 55 | 56 | install: 57 | mkdir -p $(LUA_ILIBDIR) && install -c lpm.so $(LUA_ILIBDIR) 58 | 59 | clean: 60 | rm -rf .libs *.so *.o *.lo *.la t_$(PROJ) 61 | 62 | .PHONY: all obj lib install clean 63 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - "master" 7 | workflow_dispatch: 8 | jobs: 9 | build: 10 | runs-on: ubuntu-20.04 11 | name: Build on ${{ matrix.arch }} using ${{ matrix.cc }} 12 | 13 | strategy: 14 | matrix: 15 | cc: 16 | - "gcc" 17 | - "clang" 18 | arch: 19 | - "amd64" 20 | - "aarch64" 21 | - "ppc64le" 22 | steps: 23 | - uses: actions/checkout@v2 24 | - uses: uraimo/run-on-arch-action@v2.1.1 25 | if: ${{ matrix.arch != 'amd64' }} 26 | name: Build and test 27 | with: 28 | arch: ${{ matrix.arch }} 29 | distro: ubuntu20.04 30 | 31 | githubToken: ${{ github.token }} 32 | 33 | env: | 34 | CC: ${{ matrix.cc }} 35 | 36 | install: | 37 | apt-get update -y -q 38 | apt-get install -q -y \ 39 | build-essential \ 40 | clang \ 41 | debhelper \ 42 | libtool \ 43 | liblua5.1-dev 44 | 45 | run: | 46 | # Build the package. 47 | (cd pkg && make deb) 48 | # Compile and run the tests. 49 | (cd src && make clean && DEBUG=1 make tests) 50 | 51 | - name: Build and test 52 | if: ${{ matrix.arch == 'amd64' }} 53 | env: 54 | CC: ${{ matrix.cc }} 55 | 56 | run: | 57 | sudo apt-get install -q -y \ 58 | build-essential \ 59 | clang \ 60 | debhelper \ 61 | libtool \ 62 | liblua5.1-dev 63 | 64 | # Build the package. 65 | (cd pkg && make deb) 66 | # Compile and run the tests. 67 | (cd src && make clean && DEBUG=1 make tests) 68 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This file is in the Public Domain. 3 | # 4 | 5 | PROJ= lpm 6 | 7 | SYSNAME:= $(shell uname -s) 8 | SYSARCH:= $(shell uname -m) 9 | 10 | CFLAGS+= -std=c99 -O2 -g -Wall -Wextra -Werror 11 | ifneq ($(SYSNAME), FreeBSD) 12 | CFLAGS+= -D_POSIX_C_SOURCE=200809L 13 | endif 14 | CFLAGS+= -D_GNU_SOURCE -D_DEFAULT_SOURCE 15 | 16 | # 17 | # Extended warning flags. 18 | # 19 | CFLAGS+= -Wno-unknown-warning-option # gcc vs clang 20 | 21 | CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith 22 | CFLAGS+= -Wmissing-declarations -Wredundant-decls -Wnested-externs 23 | CFLAGS+= -Wshadow -Wcast-qual -Wcast-align -Wwrite-strings 24 | CFLAGS+= -Wold-style-definition 25 | CFLAGS+= -Wsuggest-attribute=noreturn -Wjump-misses-init 26 | CFLAGS+= -Wduplicated-cond -Wmisleading-indentation -Wnull-dereference 27 | CFLAGS+= -Wduplicated-branches -Wrestrict 28 | 29 | ifeq ($(MAKECMDGOALS),tests) 30 | DEBUG= 1 31 | endif 32 | 33 | ifeq ($(DEBUG),1) 34 | CFLAGS+= -Og -DDEBUG -fno-omit-frame-pointer 35 | ifeq ($(SYSARCH),x86_64) 36 | CFLAGS+= -fsanitize=address -fsanitize=undefined 37 | LDFLAGS+= -fsanitize=address -fsanitize=undefined 38 | endif 39 | else 40 | CFLAGS+= -DNDEBUG 41 | endif 42 | 43 | # C library 44 | INCS= lpm.h 45 | OBJS= lpm.o 46 | LIB= liblpm 47 | 48 | $(LIB).la: LDFLAGS+= -rpath $(LIBDIR) -version-info 1:0:0 49 | install/%.la: ILIBDIR= $(DESTDIR)/$(LIBDIR) 50 | install: IINCDIR= $(DESTDIR)/$(INCDIR)/ 51 | #install: IMANDIR= $(DESTDIR)/$(MANDIR)/man3/ 52 | 53 | obj: $(OBJS) 54 | 55 | lib: $(LIB).la 56 | 57 | %.o: %.c 58 | $(CC) $(CFLAGS) -c $< 59 | 60 | %.lo: %.c 61 | libtool --mode=compile --tag CC $(CC) $(CFLAGS) -fPIC -c $< 62 | 63 | $(LIB).la: $(shell echo $(OBJS) | sed 's/\.o/\.lo/g') 64 | libtool --mode=link --tag CC $(CC) $(LDFLAGS) -o $@ $(notdir $^) 65 | 66 | install/%.la: %.la 67 | mkdir -p $(ILIBDIR) 68 | libtool --mode=install install -c $(notdir $@) $(ILIBDIR)/$(notdir $@) 69 | 70 | install: $(addprefix install/,$(LIB).la) 71 | libtool --mode=finish $(LIBDIR) 72 | mkdir -p $(IINCDIR) && install -c $(INCS) $(IINCDIR) 73 | #mkdir -p $(IMANDIR) && install -c $(MANS) $(IMANDIR) 74 | 75 | tests: $(OBJS) t_$(PROJ).o 76 | $(CC) $(CFLAGS) $^ -o t_$(PROJ) 77 | MALLOC_CHECK_=3 ./t_$(PROJ) 78 | 79 | clean: 80 | libtool --mode=clean rm 81 | rm -rf .libs *.so *.o *.lo *.la t_$(PROJ) 82 | 83 | .PHONY: all obj lib install tests clean 84 | -------------------------------------------------------------------------------- /src/lua/t_lpm.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- This file is in the Public Domain. 3 | -- 4 | 5 | local lpm = require("lpm") 6 | 7 | local acl = lpm.new() 8 | local some_info = { val = "test" } 9 | local some_more_info = { lav = "tset" } 10 | local addr, preflen, ok, ret 11 | 12 | addr, preflen = lpm.tobin("10.0.0.0/25") 13 | ok = acl:insert(addr, preflen) 14 | assert(ok) 15 | 16 | ret = acl:lookup(lpm.tobin("10.0.0.1")) 17 | assert(ret == true) 18 | 19 | ret = acl:lookup(lpm.tobin("10.0.0.128")) 20 | assert(ret == nil) 21 | 22 | addr, preflen = lpm.tobin("10.1.1.0/24") 23 | ok = acl:insert(addr, preflen, some_info) 24 | assert(ok) 25 | 26 | ret = acl:lookup(lpm.tobin("10.1.1.100")) 27 | assert(ret.val == "test") 28 | 29 | ret = acl:lookup(lpm.tobin("10.2.2.1")) 30 | assert(ret == nil) 31 | 32 | 33 | local function tracer(ind, tbl) 34 | local trc 35 | if _VERSION == "Lua 5.1" then 36 | -- Lua 5.1 only calls the __gc metamethod on userdata. There is an 37 | -- undocumented function called 'newproxy' to create blank userdata. 38 | -- This function is deprecated and absent in later Lua versions. 39 | trc = newproxy(true) 40 | getmetatable(trc).__gc = function () table.insert(tbl, ind) end 41 | else 42 | trc = {} 43 | setmetatable(trc, {__gc = function () table.insert(tbl, ind) end}) 44 | end 45 | return trc 46 | end 47 | 48 | -- test overwrite/remove 49 | local gcl = {} 50 | acl = lpm.new() 51 | collectgarbage() -- cleanup old lpm 52 | 53 | addr, preflen = lpm.tobin("1.2.3.4/5") 54 | ok = acl:insert(addr, preflen, tracer("i1", gcl)) 55 | assert(ok) 56 | ok = acl:insert(addr, preflen, tracer("i2", gcl)) 57 | assert(ok) 58 | collectgarbage() -- tracer i1 should have been GC'd. 59 | assert(table.remove(gcl) == "i1") 60 | 61 | addr, preflen = lpm.tobin("84.42.21.0/28") 62 | ok = acl:insert(addr, preflen, tracer("q1", gcl)) 63 | assert(ok) 64 | addr, preflen = lpm.tobin("84.42.21.0/24") 65 | ok = acl:insert(addr, preflen, tracer("q2", gcl)) 66 | assert(ok) 67 | collectgarbage() -- no tracers should have been removed 68 | assert(#gcl == 0) 69 | 70 | ok = acl:remove(addr, preflen) 71 | assert(ok) 72 | collectgarbage() -- tracer q2 should have been GC'd, and NOT q1. 73 | assert(table.remove(gcl) == "q2" and #gcl == 0) 74 | ok = acl:remove(addr, preflen) 75 | assert(not ok) 76 | collectgarbage() -- no tracers should have been removed 77 | assert(#gcl == 0) 78 | 79 | acl = nil 80 | collectgarbage() -- lpm collected 81 | collectgarbage() -- remaining tracers (i1, q1) should have been removed. 82 | assert(#gcl == 2) 83 | 84 | print("ok") 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Longest Prefix Match (LPM) library 2 | 3 | [![Build Status](https://travis-ci.org/rmind/liblpm.svg?branch=master)](https://travis-ci.org/rmind/liblpm) 4 | 5 | Longest Prefix Match (LPM) library supporting IPv4 and IPv6. 6 | The implementation is written in C99 and is distributed under the 7 | 2-clause BSD license. 8 | 9 | Additionally, bindings are available for **Lua** and **Java**. 10 | Specifications to build RPM and DEB packages are also provided. 11 | 12 | ## API 13 | 14 | * `lpm_t *lpm_create(void)` 15 | * Construct a new LPM object. 16 | 17 | * `void lpm_destroy(lpm_t *lpm)` 18 | * Destroy the LPM object and any entries in it. 19 | 20 | * `void lpm_clear(lpm_t *lpm, lpm_dtor_t *dtor, void *arg)` 21 | * Remove all entries in the LPM object. It calls the passed destructor 22 | function, if it is not `NULL`, as it traverses the entries. The destructor 23 | function prototype: 24 | * `typedef void (*lpm_dtor_t)(void *arg, const void *key, size_t len, void *val);` 25 | 26 | * `int lpm_insert(lpm_t *lpm, const void *addr, size_t len, unsigned preflen, void *val)` 27 | * Insert the network address of a given length and prefix length into 28 | the LPM object and associate the entry with specified pointer value. 29 | The address must be in the network byte order. Returns 0 on success 30 | or -1 on failure. 31 | 32 | * `int lpm_remove(lpm_t *lpm, const void *addr, size_t len, unsigned preflen)` 33 | * Remove the network address of a given length and prefix length from 34 | the LPM object. Returns 0 on success or -1 on failure. 35 | 36 | * `void *lpm_lookup_prefix(lpm_t *lpm, const void *addr, size_t len, unsigned preflen)` 37 | * Retrieve the pointer associated with a specific prefix. 38 | Returns the said pointer, or `NULL` on failure. 39 | 40 | * `void *lpm_lookup(lpm_t *lpm, const void *addr, size_t len)` 41 | * Lookup the given address performing the longest prefix match. 42 | Returns the associated pointer value on success or `NULL` on failure. 43 | 44 | * `int lpm_strtobin(const char *cidr, void *addr, size_t *len, unsigned *preflen)` 45 | * Convert a string in CIDR notation to a binary address, to be stored in 46 | the `addr` buffer and its length in `len`, as well as the prefix length (if 47 | not specified, then the maximum length of the address family will be set). 48 | The address will be stored in the network byte order. Its buffer must 49 | provide at least 4 or 16 bytes (depending on the address family). Returns 50 | zero on success and -1 on failure. 51 | 52 | ## Examples 53 | 54 | ### Lua 55 | 56 | ```lua 57 | local lpm = require("lpm") 58 | 59 | local acl = lpm.new() 60 | local some_info = { val = "test" } 61 | 62 | local addr, preflen = lpm.tobin("10.0.0.0/24") 63 | if not acl:insert(addr, preflen, some_info) then 64 | print("acl:insert() failed") 65 | return -1 66 | end 67 | 68 | local ret = acl:lookup(lpm.tobin("10.0.0.100")) 69 | print(ret.val) 70 | ``` 71 | 72 | ### Java 73 | 74 | See [README](src/jni) how to build the JAR and the 75 | [test case](src/jni/org/netbsd/liblpm/LPMTest.java) as example 76 | how to use the Java API 77 | 78 | ## Packages 79 | 80 | Just build the package, install it and link the library using the 81 | `-llpm` flag. 82 | * RPM (tested on RHEL/CentOS 7): `cd pkg && make rpm` 83 | * DEB (tested on Debian 9): `cd pkg && make deb` 84 | -------------------------------------------------------------------------------- /src/jni/org/netbsd/liblpm/LPM.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Henry Rodrick 3 | * All rights reserved. 4 | * 5 | * Use is subject to license terms, as specified in the LICENSE file. 6 | */ 7 | 8 | package org.netbsd.liblpm; 9 | 10 | import java.io.*; 11 | import java.net.InetAddress; 12 | 13 | public class LPM { 14 | static { 15 | File temp = null; 16 | final String name = "org_netbsd_liblpm_LPM.so"; 17 | try { 18 | InputStream in = LPM.class.getResourceAsStream("/" + name); 19 | byte[] buffer = new byte[1024]; 20 | int read = -1; 21 | temp = File.createTempFile(name, ""); 22 | FileOutputStream fos = new FileOutputStream(temp); 23 | 24 | while((read = in.read(buffer)) != -1) { 25 | fos.write(buffer, 0, read); 26 | } 27 | fos.close(); 28 | in.close(); 29 | 30 | System.load(temp.getAbsolutePath()); 31 | } catch (IOException e) { 32 | throw new RuntimeException(e); 33 | } finally { 34 | if (temp != null) { 35 | temp.delete(); 36 | } 37 | } 38 | } 39 | 40 | private long lpm; 41 | 42 | private native long init(); 43 | private native void destroy(long lpm); 44 | private native int insert(long lpm, String cidr, T value); 45 | private native int insert(long lpm, byte[] address, int prefixLength, T value); 46 | private native T lookup(long lpm, String addr); 47 | private native T lookup(long lpm, byte[] address); 48 | private native T lookupPrefix(long lpm, String cidr); 49 | private native T lookupPrefix(long lpm, byte[] address, int prefixLength); 50 | private native int remove(long lpm, String cidr); 51 | private native int remove(long lpm, byte[] address, int prefixLength); 52 | private native void clear(long lpm); 53 | 54 | private void validateCIDR(byte[] address, int prefixLength) { 55 | if (address == null) { 56 | throw new IllegalArgumentException( 57 | "address must not be null"); 58 | } 59 | if (address.length != 4 && address.length != 16) { 60 | throw new IllegalArgumentException( 61 | "address length must be 4 or 16 bytes"); 62 | } 63 | if (prefixLength < 0 || prefixLength > address.length * 8) { 64 | throw new IllegalArgumentException( 65 | "prefix length must be >= 0 and <= " + 66 | (address.length * 8)); 67 | } 68 | } 69 | 70 | public LPM() { 71 | lpm = init(); 72 | if (lpm == 0) { 73 | throw new RuntimeException("Failed to initialize liblpm"); 74 | } 75 | } 76 | 77 | public boolean insert(String cidr, T value) { 78 | if (cidr == null) { 79 | throw new IllegalArgumentException( 80 | "CIDR must not be null"); 81 | } 82 | return insert(lpm, cidr, value) == 0; 83 | } 84 | 85 | public boolean insert(InetAddress inet, int prefixLength, T value) { 86 | return insert(inet.getAddress(), prefixLength, value); 87 | } 88 | 89 | public boolean insert(byte[] address, int prefixLength, T value) { 90 | validateCIDR(address, prefixLength); 91 | return insert(lpm, address, prefixLength, value) == 0; 92 | } 93 | 94 | public T lookup(String address) { 95 | if (address == null) { 96 | throw new IllegalArgumentException( 97 | "address must not be null"); 98 | } 99 | return lookup(lpm, address); 100 | } 101 | 102 | public T lookup(InetAddress inet) { 103 | return lookup(inet.getAddress()); 104 | } 105 | 106 | public T lookup(byte[] address) { 107 | validateCIDR(address, address.length); 108 | return lookup(lpm, address); 109 | } 110 | 111 | public T lookupPrefix(String cidr) { 112 | if (cidr == null) { 113 | throw new IllegalArgumentException( 114 | "cidr must not be null"); 115 | } 116 | 117 | return lookupPrefix(lpm, cidr); 118 | } 119 | 120 | public T lookupPrefix(InetAddress inet, int prefixLength) { 121 | return lookupPrefix(inet.getAddress(), prefixLength); 122 | } 123 | 124 | public T lookupPrefix(byte[] address, int prefixLength) { 125 | validateCIDR(address, prefixLength); 126 | return lookupPrefix(lpm, address, prefixLength); 127 | } 128 | 129 | public boolean remove(String cidr) { 130 | if (cidr == null) { 131 | throw new IllegalArgumentException( 132 | "CIDR must not be null"); 133 | } 134 | return remove(lpm, cidr) == 0; 135 | } 136 | 137 | public boolean remove(InetAddress inet, int prefixLength) { 138 | return remove(inet.getAddress(), prefixLength); 139 | } 140 | 141 | public boolean remove(byte[] address, int prefixLength) { 142 | validateCIDR(address, address.length); 143 | return remove(lpm, address, prefixLength) == 0; 144 | } 145 | 146 | public void clear() { 147 | clear(lpm); 148 | } 149 | 150 | protected void finalize() throws Throwable { 151 | destroy(lpm); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/jni/org/netbsd/liblpm/LPMTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | *This file is in the Public Domain. 3 | */ 4 | package org.netbsd.liblpm; 5 | 6 | import java.net.InetAddress; 7 | 8 | public class LPMTest { 9 | public static void main(String[] args) throws Exception { 10 | // Run a few iterations to trigger some GC activity 11 | for (int n = 0; n < 10; n++) { 12 | basicTest(); 13 | } 14 | System.out.println("ok"); 15 | } 16 | 17 | private static void basicTest() throws Exception { 18 | LPM lpm = new LPM(); 19 | assertEqual(lpm.insert("10.0.0.0/8", "foo"), true); 20 | assertEqual(lpm.insert("10.10.0.0/16", "bar"), true); 21 | assertEqual(lpm.insert("10", "bar"), false); 22 | assertEqual(lpm.insert("2001:db8::1/64", "baz"), true); 23 | assertEqual(lpm.insert(new byte[]{ 127, 126, 0, 1 }, 15, "raw4"), true); 24 | assertEqual(lpm.insert(new byte[]{ 25 | 127, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0 26 | }, 66, "raw6"), true); 27 | assertEqual(lpm.insert(InetAddress.getByName("127.0.0.1"), 32, "lo4"), true); 28 | assertEqual(lpm.insert(InetAddress.getByName("::1"), 128, "lo6"), true); 29 | 30 | try { 31 | assertEqual(lpm.insert(new byte[]{127, 0, 1}, 8, "asdf"), true); 32 | throw new AssertionError("Should throw for illegal address length"); 33 | } catch(IllegalArgumentException e) { 34 | } 35 | 36 | try { 37 | assertEqual(lpm.insert(new byte[]{127, 0, 0, 1}, -1, "asdf"), true); 38 | throw new AssertionError("Should throw for illegal prefix length"); 39 | } catch(IllegalArgumentException e) { 40 | } 41 | 42 | try { 43 | assertEqual(lpm.insert(new byte[]{127, 0, 0, 1}, 33, "asdf"), true); 44 | throw new AssertionError("Should throw for illegal prefix length"); 45 | } catch(IllegalArgumentException e) { 46 | } 47 | 48 | assertEqual(lpm.lookupPrefix("10.0.0.0/9"), null); 49 | assertEqual(lpm.lookupPrefix(InetAddress.getByName("10.0.0.0"), 9), null); 50 | assertEqual(lpm.lookupPrefix(new byte[]{10, 0, 0, 0}, 9), null); 51 | assertEqual(lpm.lookupPrefix("2001:db8::1/65"), null); 52 | assertEqual(lpm.lookupPrefix(InetAddress.getByName("2001:db8::1"), 65), null); 53 | assertEqual(lpm.lookupPrefix(new byte[]{ 54 | 127, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0 55 | }, 67), null); 56 | 57 | assertEqual(lpm.lookupPrefix("10.0.0.0/8"), "foo"); 58 | assertEqual(lpm.lookupPrefix(InetAddress.getByName("10.0.0.0"), 8), "foo"); 59 | assertEqual(lpm.lookupPrefix(new byte[]{10, 0, 0, 0}, 8), "foo"); 60 | assertEqual(lpm.lookupPrefix("2001:db8::1/64"), "baz"); 61 | assertEqual(lpm.lookupPrefix(InetAddress.getByName("2001:db8::1"), 64), "baz"); 62 | assertEqual(lpm.lookupPrefix(new byte[]{ 63 | 127, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0 64 | }, 66), "raw6"); 65 | 66 | assertEqual(lpm.lookup("10.0.1.2"), "foo"); 67 | assertEqual(lpm.lookup("10.10.3.4"), "bar"); 68 | assertEqual(lpm.lookup("11.10.3.4"), null); 69 | assertEqual(lpm.lookup("2001:db8::abcd"), "baz"); 70 | assertEqual(lpm.lookup("127.127.2.3"), "raw4"); 71 | assertEqual(lpm.lookup("127.124.2.3"), null); 72 | assertEqual(lpm.lookup("7f00:0000:0000:0000:7f00::abcd"), "raw6"); 73 | assertEqual(lpm.lookup("7f00:0000:0000:0000:3f00::abcd"), null); 74 | assertEqual(lpm.lookup(new byte[]{ 127, 127, 2, 3 }), "raw4"); 75 | assertEqual(lpm.lookup(new byte[]{ 76 | 127, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 1 77 | }), "raw6"); 78 | assertEqual(lpm.lookup(new byte[]{ 79 | 127, 0, 0, 0, 0, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 1 80 | }), null); 81 | assertEqual(lpm.lookup(InetAddress.getByName("127.0.0.1")), "lo4"); 82 | assertEqual(lpm.lookup(InetAddress.getByName("::1")), "lo6"); 83 | 84 | assertEqual(lpm.remove("10.10.0.0/16"), true); 85 | assertEqual(lpm.lookup("10.10.3.4"), "foo"); 86 | assertEqual(lpm.remove(new byte[]{ 127, 126, 0, 1 }, 15), true); 87 | assertEqual(lpm.lookup(new byte[]{ 127, 127, 2, 3 }), null); 88 | assertEqual(lpm.remove(new byte[]{ 89 | 127, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0 90 | }, 66), true); 91 | assertEqual(lpm.lookup(new byte[]{ 92 | 127, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 1 93 | }), null); 94 | assertEqual(lpm.remove(InetAddress.getByName("127.0.0.1"), 32), true); 95 | assertEqual(lpm.remove(InetAddress.getByName("::1"), 128), true); 96 | assertEqual(lpm.lookup(InetAddress.getByName("127.0.0.1")), null); 97 | assertEqual(lpm.lookup(InetAddress.getByName("::1")), null); 98 | 99 | lpm.clear(); 100 | assertEqual(lpm.lookup("10.10.3.4"), null); 101 | 102 | for (int i = 0; i < 255; i++) { 103 | for (int j = 0; j < 255; j++) { 104 | lpm.insert("11.11." + i + "." + 105 | j + "/32", "foo" + i + "-" + j); 106 | } 107 | } 108 | } 109 | 110 | private static void assertEqual(Object a, Object b) { 111 | if ((a == null && b != null) || (a != null && !a.equals(b))) { 112 | throw new AssertionError( 113 | "Assertion failed: " + a + " != " + b); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/lua/lpm_lua.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Mindaugas Rasiukevicius 3 | * All rights reserved. 4 | * 5 | * Use is subject to license terms, as specified in the LICENSE file. 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "lpm.h" 16 | 17 | int luaopen_lpm(lua_State *); 18 | static int lua_lpm_new(lua_State *); 19 | static int lua_lpm_tobin(lua_State *); 20 | static int lua_lpm_insert(lua_State *); 21 | static int lua_lpm_remove(lua_State *); 22 | static int lua_lpm_lookup(lua_State *); 23 | static int lua_lpm_clear(lua_State *); 24 | static int lua_lpm_gc(lua_State *); 25 | 26 | static const struct luaL_Reg lpm_lib_methods[] = { 27 | { "new", lua_lpm_new }, 28 | { "tobin", lua_lpm_tobin }, 29 | { NULL, NULL } 30 | }; 31 | 32 | static const struct luaL_Reg lpm_methods[] = { 33 | { "insert", lua_lpm_insert }, 34 | { "remove", lua_lpm_remove }, 35 | { "lookup", lua_lpm_lookup }, 36 | { "clear", lua_lpm_clear }, 37 | { "__gc", lua_lpm_gc }, 38 | { NULL, NULL } 39 | }; 40 | 41 | #define LPM_METATABLE "lpm-obj-methods" 42 | #define LPM_VALID ((void *)(uintptr_t)0x1) 43 | 44 | typedef struct { 45 | lpm_t * lpm; 46 | } lpm_lua_t; 47 | 48 | typedef struct { 49 | int refidx; 50 | } lpm_luaref_t; 51 | 52 | int 53 | luaopen_lpm(lua_State *L) 54 | { 55 | if (luaL_newmetatable(L, LPM_METATABLE)) { 56 | #if LUA_VERSION_NUM >= 502 57 | luaL_setfuncs(L, lpm_methods, 0); 58 | #else 59 | luaL_register(L, NULL, lpm_methods); 60 | #endif 61 | lua_pushliteral(L, "__index"); 62 | lua_pushvalue(L, -2); 63 | lua_settable(L, -3); 64 | 65 | lua_pushliteral(L, "__metatable"); 66 | lua_pushliteral(L, "must not access this metatable"); 67 | lua_settable(L, -3); 68 | } 69 | lua_pop(L, 1); 70 | 71 | #if LUA_VERSION_NUM >= 502 72 | luaL_newlib(L, lpm_lib_methods); 73 | #else 74 | luaL_register(L, "lpm", lpm_lib_methods); 75 | #endif 76 | return 1; 77 | } 78 | 79 | static int 80 | lua_lpm_new(lua_State *L) 81 | { 82 | lpm_lua_t *lctx; 83 | lpm_t *lpm; 84 | 85 | if ((lpm = lpm_create()) == NULL) { 86 | luaL_error(L, "OOM"); 87 | return 0; 88 | } 89 | lctx = (lpm_lua_t *)lua_newuserdata(L, sizeof(lpm_lua_t)); 90 | if (lctx == NULL) { 91 | lpm_destroy(lpm); 92 | luaL_error(L, "OOM"); 93 | return 0; 94 | } 95 | lctx->lpm = lpm; 96 | luaL_getmetatable(L, LPM_METATABLE); 97 | lua_setmetatable(L, -2); 98 | return 1; 99 | } 100 | 101 | static int 102 | lua_lpm_tobin(lua_State *L) 103 | { 104 | uint8_t addr[16]; 105 | const char *cidr; 106 | unsigned preflen; 107 | size_t len; 108 | 109 | cidr = lua_tolstring(L, 1, &len); 110 | if (!cidr || lpm_strtobin(cidr, addr, &len, &preflen) == -1) { 111 | return 0; 112 | } 113 | lua_pushlstring(L, (const char *)addr, len); 114 | lua_pushinteger(L, preflen); 115 | return 2; 116 | } 117 | 118 | static lpm_lua_t * 119 | lua_lpm_getctx(lua_State *L) 120 | { 121 | void *ud = luaL_checkudata(L, 1, LPM_METATABLE); 122 | luaL_argcheck(L, ud != NULL, 1, "`" LPM_METATABLE "' expected"); 123 | return (lpm_lua_t *)ud; 124 | } 125 | 126 | static int 127 | lua_lpm_gc(lua_State *L) 128 | { 129 | lpm_lua_t *lctx = lua_lpm_getctx(L); 130 | lua_lpm_clear(L); 131 | lpm_destroy(lctx->lpm); 132 | return 0; 133 | } 134 | 135 | static int 136 | lua_lpm_insert(lua_State *L) 137 | { 138 | lpm_lua_t *lctx = lua_lpm_getctx(L); 139 | const uint8_t *addr; 140 | unsigned preflen; 141 | size_t len; 142 | lpm_luaref_t *ref, *oldref; 143 | 144 | addr = (const uint8_t *)lua_tolstring(L, 2, &len); 145 | luaL_argcheck(L, addr && (len == 4 || len == 16), 2, 146 | "`addr' binary string of 4 or 16 bytes expected"); 147 | 148 | preflen = lua_tointeger(L, 3); 149 | luaL_argcheck(L, preflen <= 128, 3, "invalid `prefix-len'"); 150 | 151 | if (!lua_isnoneornil(L, 4)) { 152 | if ((ref = malloc(sizeof(lpm_luaref_t))) == NULL) { 153 | return 0; 154 | } 155 | lua_pushvalue(L, 4); 156 | ref->refidx = luaL_ref(L, LUA_REGISTRYINDEX); 157 | } else { 158 | ref = LPM_VALID; 159 | } 160 | oldref = lpm_lookup_prefix(lctx->lpm, addr, len, preflen); 161 | if (lpm_insert(lctx->lpm, addr, len, preflen, ref) == 0) { 162 | if (oldref && oldref != LPM_VALID) { 163 | luaL_unref(L, LUA_REGISTRYINDEX, oldref->refidx); 164 | free(oldref); 165 | } 166 | lua_pushboolean(L, 1); 167 | return 1; 168 | } 169 | return 0; 170 | } 171 | 172 | static int 173 | lua_lpm_remove(lua_State *L) 174 | { 175 | lpm_lua_t *lctx = lua_lpm_getctx(L); 176 | const uint8_t *addr; 177 | unsigned preflen; 178 | size_t len; 179 | lpm_luaref_t *ref; 180 | 181 | addr = (const uint8_t *)lua_tolstring(L, 2, &len); 182 | luaL_argcheck(L, addr && (len == 4 || len == 16), 2, 183 | "`addr' binary string of 4 or 16 bytes expected"); 184 | 185 | preflen = lua_tointeger(L, 3); 186 | luaL_argcheck(L, preflen <= 128, 3, "invalid `prefix-len'"); 187 | 188 | ref = lpm_lookup_prefix(lctx->lpm, addr, len, preflen); 189 | if (lpm_remove(lctx->lpm, addr, len, preflen) == 0) { 190 | if (ref && ref != LPM_VALID) { 191 | luaL_unref(L, LUA_REGISTRYINDEX, ref->refidx); 192 | free(ref); 193 | } 194 | lua_pushboolean(L, 1); 195 | return 1; 196 | } 197 | return 0; 198 | } 199 | 200 | static int 201 | lua_lpm_lookup(lua_State *L) 202 | { 203 | lpm_lua_t *lctx = lua_lpm_getctx(L); 204 | const uint8_t *addr; 205 | size_t len; 206 | lpm_luaref_t *ref; 207 | 208 | addr = (const uint8_t *)lua_tolstring(L, 2, &len); 209 | luaL_argcheck(L, addr && (len == 4 || len == 16), 2, 210 | "`addr' binary string of 4 or 16 bytes expected"); 211 | 212 | if ((ref = lpm_lookup(lctx->lpm, addr, len)) != NULL) { 213 | if (ref == LPM_VALID) { 214 | lua_pushboolean(L, 1); 215 | } else { 216 | lua_rawgeti(L, LUA_REGISTRYINDEX, ref->refidx); 217 | } 218 | return 1; 219 | } 220 | return 0; 221 | } 222 | 223 | static void 224 | lua_lpm_unref(void *arg, const void *key, size_t len, void *val) 225 | { 226 | if (val && val != LPM_VALID) { 227 | lua_State *L = arg; 228 | lpm_luaref_t *ref = val; 229 | luaL_unref(L, LUA_REGISTRYINDEX, ref->refidx); 230 | free(ref); 231 | } 232 | (void)key; 233 | (void)len; 234 | } 235 | 236 | static int 237 | lua_lpm_clear(lua_State *L) 238 | { 239 | lpm_lua_t *lctx = lua_lpm_getctx(L); 240 | lpm_clear(lctx->lpm, lua_lpm_unref, L); 241 | return 0; 242 | } 243 | -------------------------------------------------------------------------------- /src/jni/org_netbsd_liblpm_LPM.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Henry Rodrick 3 | * All rights reserved. 4 | * 5 | * Use is subject to license terms, as specified in the LICENSE file. 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | #include "org_netbsd_liblpm_LPM.h" 12 | #include "lpm.h" 13 | 14 | static void 15 | lpm_jni_dtor(void *arg, const void *key, size_t len, void *val) 16 | { 17 | JNIEnv *env = (JNIEnv*)arg; 18 | (*env)->DeleteGlobalRef(env, val); 19 | } 20 | 21 | JNIEXPORT jlong JNICALL 22 | Java_org_netbsd_liblpm_LPM_init(JNIEnv *env, jobject obj) 23 | { 24 | return (jlong)lpm_create(); 25 | } 26 | 27 | JNIEXPORT void JNICALL 28 | Java_org_netbsd_liblpm_LPM_destroy(JNIEnv *env, jobject obj, jlong lpm_ref) 29 | { 30 | lpm_t *lpm = (lpm_t *)lpm_ref; 31 | lpm_clear(lpm, lpm_jni_dtor, env); 32 | lpm_destroy(lpm); 33 | } 34 | 35 | JNIEXPORT void JNICALL 36 | Java_org_netbsd_liblpm_LPM_clear(JNIEnv *env, jobject obj, jlong lpm_ref) 37 | { 38 | lpm_clear((lpm_t *)lpm_ref, lpm_jni_dtor, env); 39 | } 40 | 41 | JNIEXPORT jint JNICALL 42 | Java_org_netbsd_liblpm_LPM_insert__JLjava_lang_String_2Ljava_lang_Object_2 43 | (JNIEnv *env, jobject obj, jlong lpm_ref, jstring cidr, jobject value) 44 | { 45 | lpm_t *lpm = (lpm_t *)lpm_ref; 46 | jobject val_ref, old_val_ref; 47 | const char *cidr_s; 48 | uint32_t addr[4]; 49 | size_t len; 50 | unsigned pref; 51 | int ret; 52 | 53 | cidr_s = (*env)->GetStringUTFChars(env, cidr, NULL); 54 | if (cidr_s == NULL) { 55 | return -1; 56 | } 57 | ret = lpm_strtobin(cidr_s, addr, &len, &pref); 58 | (*env)->ReleaseStringUTFChars(env, cidr, cidr_s); 59 | if (ret != 0) { 60 | return ret; 61 | } 62 | 63 | old_val_ref = lpm_lookup_prefix(lpm, addr, len, pref); 64 | 65 | val_ref = (*env)->NewGlobalRef(env, value); 66 | if (val_ref == NULL) { 67 | return -1; 68 | } 69 | ret = lpm_insert(lpm, addr, len, pref, (void *)val_ref); 70 | if (ret != 0) { 71 | (*env)->DeleteGlobalRef(env, val_ref); 72 | } else if (old_val_ref != NULL) { 73 | (*env)->DeleteGlobalRef(env, old_val_ref); 74 | } 75 | return ret; 76 | } 77 | 78 | JNIEXPORT jint JNICALL 79 | Java_org_netbsd_liblpm_LPM_insert__J_3BILjava_lang_Object_2 80 | (JNIEnv *env, jobject obj, jlong lpm_ref, jbyteArray addr_ref, 81 | jint pref, jobject value) 82 | { 83 | lpm_t *lpm = (lpm_t *)lpm_ref; 84 | jobject val_ref, old_val_ref; 85 | jbyte *addr; 86 | size_t len; 87 | int ret; 88 | 89 | len = (*env)->GetArrayLength(env, addr_ref); 90 | assert(len == 16 || len == 4); 91 | 92 | addr = (*env)->GetByteArrayElements(env, addr_ref, NULL); 93 | if (addr == NULL) { 94 | return -1; 95 | } 96 | 97 | old_val_ref = lpm_lookup_prefix(lpm, addr, len, pref); 98 | 99 | val_ref = (*env)->NewGlobalRef(env, value); 100 | if (val_ref == NULL) { 101 | (*env)->ReleaseByteArrayElements(env, addr_ref, addr, JNI_ABORT); 102 | return -1; 103 | } 104 | ret = lpm_insert(lpm, addr, len, pref, (void *)val_ref); 105 | if (ret != 0) { 106 | (*env)->DeleteGlobalRef(env, val_ref); 107 | } else if (old_val_ref != NULL) { 108 | (*env)->DeleteGlobalRef(env, old_val_ref); 109 | } 110 | (*env)->ReleaseByteArrayElements(env, addr_ref, addr, JNI_ABORT); 111 | 112 | return ret; 113 | } 114 | 115 | JNIEXPORT jobject JNICALL JNICALL 116 | Java_org_netbsd_liblpm_LPM_lookup__JLjava_lang_String_2(JNIEnv *env, 117 | jobject obj, jlong lpm_ref, jstring addr) 118 | { 119 | lpm_t *lpm = (lpm_t *)lpm_ref; 120 | const char *addr_s; 121 | uint32_t addr_buf[4]; 122 | size_t len; 123 | unsigned pref; 124 | 125 | addr_s = (*env)->GetStringUTFChars(env, addr, NULL); 126 | if (addr_s == NULL) { 127 | /* XXX would be better to throw an exception in this case */ 128 | return NULL; 129 | } 130 | 131 | if (lpm_strtobin(addr_s, addr_buf, &len, &pref) != 0) { 132 | (*env)->ReleaseStringUTFChars(env, addr, addr_s); 133 | return NULL; 134 | } 135 | (*env)->ReleaseStringUTFChars(env, addr, addr_s); 136 | 137 | return lpm_lookup(lpm, addr_buf, len); 138 | } 139 | 140 | JNIEXPORT jobject JNICALL 141 | Java_org_netbsd_liblpm_LPM_lookup__J_3B(JNIEnv *env, jobject obj, 142 | jlong lpm_ref, jbyteArray addr_ref) 143 | { 144 | lpm_t *lpm = (lpm_t *)lpm_ref; 145 | jbyte *addr; 146 | size_t len; 147 | void *ret; 148 | 149 | len = (*env)->GetArrayLength(env, addr_ref); 150 | assert(len == 16 || len == 4); 151 | 152 | addr = (*env)->GetByteArrayElements(env, addr_ref, NULL); 153 | if (addr == NULL) { 154 | return NULL; 155 | } 156 | ret = lpm_lookup(lpm, addr, len); 157 | (*env)->ReleaseByteArrayElements(env, addr_ref, addr, JNI_ABORT); 158 | return ret; 159 | } 160 | 161 | JNIEXPORT jobject JNICALL 162 | Java_org_netbsd_liblpm_LPM_lookupPrefix__JLjava_lang_String_2 163 | (JNIEnv *env, jobject obj, jlong lpm_ref, jstring cidr) 164 | { 165 | lpm_t *lpm = (lpm_t *)lpm_ref; 166 | const char *cidr_s; 167 | uint32_t addr[4]; 168 | size_t len; 169 | unsigned pref; 170 | int ret; 171 | 172 | cidr_s = (*env)->GetStringUTFChars(env, cidr, NULL); 173 | if (cidr_s == NULL) { 174 | return NULL; 175 | } 176 | ret = lpm_strtobin(cidr_s, addr, &len, &pref); 177 | (*env)->ReleaseStringUTFChars(env, cidr, cidr_s); 178 | if (ret != 0) { 179 | return NULL; 180 | } 181 | 182 | return lpm_lookup_prefix(lpm, addr, len, pref); 183 | } 184 | 185 | JNIEXPORT jobject JNICALL 186 | Java_org_netbsd_liblpm_LPM_lookupPrefix__J_3BI 187 | (JNIEnv *env, jobject obj, jlong lpm_ref, jbyteArray addr_ref, jint pref) 188 | { 189 | lpm_t *lpm = (lpm_t *)lpm_ref; 190 | jbyte *addr; 191 | size_t len; 192 | void *ret; 193 | 194 | len = (*env)->GetArrayLength(env, addr_ref); 195 | assert(len == 16 || len == 4); 196 | 197 | addr = (*env)->GetByteArrayElements(env, addr_ref, NULL); 198 | if (addr == NULL) { 199 | return NULL; 200 | } 201 | 202 | ret = lpm_lookup_prefix(lpm, addr, len, pref); 203 | 204 | (*env)->ReleaseByteArrayElements(env, addr_ref, addr, JNI_ABORT); 205 | 206 | return ret; 207 | } 208 | 209 | JNIEXPORT jint JNICALL 210 | Java_org_netbsd_liblpm_LPM_remove__JLjava_lang_String_2(JNIEnv *env, 211 | jobject obj, jlong lpm_ref, jstring addr) 212 | { 213 | lpm_t *lpm = (lpm_t *)lpm_ref; 214 | const char *addr_s; 215 | uint32_t addr_buf[4]; 216 | size_t len; 217 | unsigned pref; 218 | int ret; 219 | void *val; 220 | 221 | addr_s = (*env)->GetStringUTFChars(env, addr, NULL); 222 | if (addr_s == NULL) { 223 | /* XXX would be better to throw an exception in this case */ 224 | return -1; 225 | } 226 | ret = lpm_strtobin(addr_s, addr_buf, &len, &pref); 227 | (*env)->ReleaseStringUTFChars(env, addr, addr_s); 228 | if (ret != 0) { 229 | return ret; 230 | } 231 | 232 | val = lpm_lookup_prefix(lpm, addr_buf, len, pref); 233 | if (val == NULL) { 234 | return -1; 235 | } 236 | ret = lpm_remove(lpm, addr_buf, len, pref); 237 | (*env)->DeleteGlobalRef(env, val); 238 | return ret; 239 | } 240 | 241 | JNIEXPORT jint JNICALL 242 | Java_org_netbsd_liblpm_LPM_remove__J_3BI(JNIEnv *env, jobject obj, 243 | jlong lpm_ref, jbyteArray addr_ref, jint pref) 244 | { 245 | lpm_t *lpm = (lpm_t *)lpm_ref; 246 | jbyte *addr; 247 | void *val; 248 | size_t len; 249 | int ret; 250 | 251 | len = (*env)->GetArrayLength(env, addr_ref); 252 | assert(len == 16 || len == 4); 253 | 254 | addr = (*env)->GetByteArrayElements(env, addr_ref, NULL); 255 | if (addr == NULL) { 256 | return -1; 257 | } 258 | 259 | val = lpm_lookup_prefix(lpm, addr, len, pref); 260 | if (val == NULL) { 261 | (*env)->ReleaseByteArrayElements(env, addr_ref, addr, JNI_ABORT); 262 | return -1; 263 | } 264 | 265 | ret = lpm_remove(lpm, addr, len, pref); 266 | 267 | (*env)->DeleteGlobalRef(env, val); 268 | (*env)->ReleaseByteArrayElements(env, addr_ref, addr, JNI_ABORT); 269 | 270 | return ret; 271 | } 272 | -------------------------------------------------------------------------------- /src/t_lpm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is in the Public Domain. 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "lpm.h" 11 | 12 | static void 13 | ipv4_basic_test(void) 14 | { 15 | lpm_t *lpm; 16 | uint32_t addr; 17 | size_t len; 18 | unsigned pref; 19 | void *val; 20 | int ret; 21 | 22 | lpm = lpm_create(); 23 | assert(lpm != NULL); 24 | 25 | /* 26 | * Simple /32 cases. 27 | */ 28 | 29 | lpm_strtobin("10.0.0.1", &addr, &len, &pref); 30 | ret = lpm_insert(lpm, &addr, len, pref, (void *)0xa); 31 | assert(ret == 0); 32 | 33 | lpm_strtobin("10.0.0.1", &addr, &len, &pref); 34 | ret = lpm_insert(lpm, &addr, len, pref, (void *)0xb); 35 | assert(ret == 0); 36 | 37 | lpm_strtobin("10.0.0.1", &addr, &len, &pref); 38 | val = lpm_lookup(lpm, &addr, len); 39 | assert(val == (void *)0xb); 40 | 41 | lpm_strtobin("10.0.0.2", &addr, &len, &pref); 42 | val = lpm_lookup(lpm, &addr, len); 43 | assert(val == NULL); 44 | 45 | /* 46 | * Single /24 network. 47 | */ 48 | 49 | lpm_strtobin("10.1.1.0/24", &addr, &len, &pref); 50 | ret = lpm_insert(lpm, &addr, len, pref, (void *)0x100); 51 | assert(ret == 0); 52 | 53 | lpm_strtobin("10.1.1.64", &addr, &len, &pref); 54 | val = lpm_lookup(lpm, &addr, len); 55 | assert(val == (void *)0x100); 56 | 57 | /* 58 | * Two adjacent /25 networks. 59 | */ 60 | 61 | lpm_strtobin("10.2.2.0/25", &addr, &len, &pref); 62 | ret = lpm_insert(lpm, &addr, len, pref, (void *)0x101); 63 | assert(ret == 0); 64 | 65 | lpm_strtobin("10.2.2.128/25", &addr, &len, &pref); 66 | ret = lpm_insert(lpm, &addr, len, pref, (void *)0x102); 67 | assert(ret == 0); 68 | 69 | lpm_strtobin("10.2.2.127", &addr, &len, &pref); 70 | val = lpm_lookup(lpm, &addr, len); 71 | assert(val == (void *)0x101); 72 | 73 | lpm_strtobin("10.2.2.255", &addr, &len, &pref); 74 | val = lpm_lookup(lpm, &addr, len); 75 | assert(val == (void *)0x102); 76 | 77 | /* 78 | * Longer /31 prefix in /29. 79 | */ 80 | 81 | lpm_strtobin("10.3.3.60/31", &addr, &len, &pref); 82 | ret = lpm_insert(lpm, &addr, len, pref, (void *)0x103); 83 | assert(ret == 0); 84 | 85 | lpm_strtobin("10.3.3.56/29", &addr, &len, &pref); 86 | ret = lpm_insert(lpm, &addr, len, pref, (void *)0x104); 87 | assert(ret == 0); 88 | 89 | lpm_strtobin("10.3.3.61", &addr, &len, &pref); 90 | val = lpm_lookup(lpm, &addr, len); 91 | assert(val == (void *)0x103); 92 | 93 | lpm_strtobin("10.3.3.62", &addr, &len, &pref); 94 | val = lpm_lookup(lpm, &addr, len); 95 | assert(val == (void *)0x104); 96 | 97 | /* 98 | * No match (out of range by one). 99 | */ 100 | 101 | lpm_strtobin("10.3.3.55", &addr, &len, &pref); 102 | val = lpm_lookup(lpm, &addr, len); 103 | assert(val == NULL); 104 | 105 | lpm_strtobin("10.3.3.64", &addr, &len, &pref); 106 | val = lpm_lookup(lpm, &addr, len); 107 | assert(val == NULL); 108 | 109 | /* 110 | * Default. 111 | */ 112 | lpm_strtobin("0.0.0.0/0", &addr, &len, &pref); 113 | ret = lpm_insert(lpm, &addr, len, pref, (void *)0x105); 114 | assert(ret == 0); 115 | 116 | lpm_strtobin("10.3.3.64", &addr, &len, &pref); 117 | val = lpm_lookup(lpm, &addr, len); 118 | assert(val == (void *)0x105); 119 | 120 | lpm_destroy(lpm); 121 | } 122 | 123 | static void 124 | ipv4_basic_random(void) 125 | { 126 | const unsigned nitems = 1024; 127 | lpm_t *lpm; 128 | uint32_t addr[nitems]; 129 | 130 | lpm = lpm_create(); 131 | assert(lpm != NULL); 132 | 133 | for (unsigned i = 0; i < nitems; i++) { 134 | uint32_t x; 135 | int ret; 136 | 137 | x = addr[i] = random(); 138 | ret = lpm_insert(lpm, &addr[i], 4, 32, (void *)(uintptr_t)x); 139 | assert(ret == 0); 140 | } 141 | for (unsigned i = 0; i < nitems; i++) { 142 | const uint32_t x = addr[i]; 143 | void *val = lpm_lookup(lpm, &x, 4); 144 | assert((uintptr_t)val == x); 145 | } 146 | 147 | lpm_destroy(lpm); 148 | } 149 | 150 | static void 151 | ipv6_basic_test(void) 152 | { 153 | lpm_t *lpm; 154 | uint32_t addr[4]; 155 | size_t len; 156 | unsigned pref; 157 | void *val; 158 | int ret; 159 | 160 | lpm = lpm_create(); 161 | assert(lpm != NULL); 162 | 163 | lpm_strtobin("abcd:8000::1", addr, &len, &pref); 164 | val = lpm_lookup(lpm, addr, len); 165 | assert(val == NULL); 166 | 167 | lpm_strtobin("::/0", addr, &len, &pref); 168 | ret = lpm_insert(lpm, addr, len, pref, (void *)0x100); 169 | assert(ret == 0); 170 | 171 | lpm_strtobin("abcd:8000::1", addr, &len, &pref); 172 | val = lpm_lookup(lpm, addr, len); 173 | assert(val == (void *)0x100); 174 | 175 | lpm_strtobin("abcd:8000::/17", addr, &len, &pref); 176 | ret = lpm_insert(lpm, addr, len, pref, (void *)0x101); 177 | assert(ret == 0); 178 | 179 | lpm_strtobin("abcd:8000::1", addr, &len, &pref); 180 | val = lpm_lookup(lpm, addr, len); 181 | assert(val == (void *)0x101); 182 | 183 | lpm_strtobin("abcd::/16", addr, &len, &pref); 184 | ret = lpm_insert(lpm, addr, len, pref, (void *)0x102); 185 | assert(ret == 0); 186 | 187 | lpm_strtobin("abcd:abcd::/32", addr, &len, &pref); 188 | ret = lpm_insert(lpm, addr, len, pref, (void *)0x103); 189 | assert(ret == 0); 190 | 191 | lpm_strtobin("dddd:abcd::abcd/128", addr, &len, &pref); 192 | ret = lpm_insert(lpm, addr, len, pref, (void *)0x104); 193 | assert(ret == 0); 194 | 195 | lpm_strtobin("aaaa:bbbb:cccc:dddd:abcd:8000::/81", addr, &len, &pref); 196 | ret = lpm_insert(lpm, addr, len, pref, (void *)0x105); 197 | assert(ret == 0); 198 | 199 | lpm_strtobin("aaaa:bbbb:cccc:dddd:abcd::/80", addr, &len, &pref); 200 | ret = lpm_insert(lpm, addr, len, pref, (void *)0x106); 201 | assert(ret == 0); 202 | 203 | lpm_strtobin("ffff:ffff:ffff:ffff::/64", addr, &len, &pref); 204 | ret = lpm_insert(lpm, addr, len, pref, (void *)0x107); 205 | assert(ret == 0); 206 | 207 | lpm_strtobin("abcd:ffff::abcd", addr, &len, &pref); 208 | val = lpm_lookup(lpm, addr, len); 209 | assert(val == (void *)0x101); 210 | 211 | lpm_strtobin("abcd:0000::abcd", addr, &len, &pref); 212 | val = lpm_lookup(lpm, addr, len); 213 | assert(val == (void *)0x102); 214 | 215 | lpm_strtobin("abcd:abcd::abc0", addr, &len, &pref); 216 | val = lpm_lookup(lpm, addr, len); 217 | assert(val == (void *)0x103); 218 | 219 | lpm_strtobin("dddd:abcd::abcd", addr, &len, &pref); 220 | val = lpm_lookup(lpm, addr, len); 221 | assert(val == (void *)0x104); 222 | 223 | lpm_strtobin("aaaa:bbbb:cccc:dddd:abcd:ffff::abcd", addr, &len, &pref); 224 | val = lpm_lookup(lpm, addr, len); 225 | assert(val == (void *)0x105); 226 | 227 | lpm_strtobin("aaaa:bbbb:cccc:dddd:abcd::abcd", addr, &len, &pref); 228 | val = lpm_lookup(lpm, addr, len); 229 | assert(val == (void *)0x106); 230 | 231 | lpm_strtobin("ffff:ffff:ffff:ffff::abcd", addr, &len, &pref); 232 | val = lpm_lookup(lpm, addr, len); 233 | assert(val == (void *)0x107); 234 | 235 | lpm_strtobin("ffff:ffff:ffff:ffff::/64", addr, &len, &pref); 236 | ret = lpm_remove(lpm, addr, len, pref); 237 | assert(ret == 0); 238 | 239 | lpm_strtobin("ffff:ffff:ffff:fffe::abcd", addr, &len, &pref); 240 | val = lpm_lookup(lpm, addr, len); 241 | assert(val == (void *)0x100); 242 | 243 | lpm_destroy(lpm); 244 | } 245 | 246 | static void 247 | removal_test(void) 248 | { 249 | lpm_t *lpm; 250 | uint32_t addr[4]; 251 | size_t len; 252 | unsigned pref; 253 | int ret; 254 | 255 | lpm = lpm_create(); 256 | assert(lpm != NULL); 257 | 258 | lpm_strtobin("fd00::1/96", addr, &len, &pref); 259 | ret = lpm_remove(lpm, addr, len, pref); 260 | assert(ret == -1); 261 | 262 | lpm_strtobin("ffff:ffff:ffff:ffff::/64", addr, &len, &pref); 263 | ret = lpm_insert(lpm, addr, len, pref, NULL); 264 | assert(ret == 0); 265 | 266 | ret = lpm_remove(lpm, addr, len, pref); 267 | assert(ret == 0); 268 | ret = lpm_remove(lpm, addr, len, pref); 269 | assert(ret == -1); 270 | 271 | lpm_destroy(lpm); 272 | } 273 | 274 | static void 275 | default_test(void) 276 | { 277 | lpm_t *lpm; 278 | uint32_t addr[16] = {0}; 279 | void *val; 280 | int ret; 281 | 282 | lpm = lpm_create(); 283 | assert(lpm != NULL); 284 | 285 | /* ::/0 */ 286 | ret = lpm_insert(lpm, addr, 16, 0, (void *)6); 287 | assert(ret == 0); 288 | 289 | /* 0.0.0.0/0 */ 290 | ret = lpm_insert(lpm, addr, 4, 0, (void *)4); 291 | assert(ret == 0); 292 | 293 | val = lpm_lookup(lpm, addr, 16); 294 | assert(val == (void *)6); 295 | val = lpm_lookup(lpm, addr, 4); 296 | assert(val == (void *)4); 297 | 298 | lpm_destroy(lpm); 299 | } 300 | 301 | int 302 | main(void) 303 | { 304 | ipv4_basic_test(); 305 | ipv4_basic_random(); 306 | ipv6_basic_test(); 307 | removal_test(); 308 | default_test(); 309 | puts("ok"); 310 | return 0; 311 | } 312 | -------------------------------------------------------------------------------- /src/lpm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Mindaugas Rasiukevicius 3 | * All rights reserved. 4 | * 5 | * Use is subject to license terms, as specified in the LICENSE file. 6 | */ 7 | 8 | /* 9 | * Longest Prefix Match (LPM) library supporting IPv4 and IPv6. 10 | * 11 | * Algorithm: 12 | * 13 | * Each prefix gets its own hash map and all added prefixes are saved 14 | * in a bitmap. On a lookup, we perform a linear scan of hash maps, 15 | * iterating through the added prefixes only. Usually, there are only 16 | * a few unique prefixes used and such simple algorithm is very efficient. 17 | * With many IPv6 prefixes, the linear scan might become a bottleneck. 18 | */ 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "lpm.h" 34 | 35 | #define LPM_MAX_PREFIX (128) 36 | #define LPM_MAX_WORDS (LPM_MAX_PREFIX >> 5) 37 | #define LPM_TO_WORDS(x) ((x) >> 2) 38 | #define LPM_HASH_STEP (8) 39 | #define LPM_LEN_IDX(len) ((len) >> 4) 40 | 41 | #ifdef DEBUG 42 | #define ASSERT assert 43 | #else 44 | #define ASSERT(x) 45 | #endif 46 | 47 | typedef struct lpm_ent { 48 | struct lpm_ent *next; 49 | void * val; 50 | unsigned len; 51 | uint8_t key[]; 52 | } lpm_ent_t; 53 | 54 | typedef struct { 55 | unsigned hashsize; 56 | unsigned nitems; 57 | lpm_ent_t ** bucket; 58 | } lpm_hmap_t; 59 | 60 | struct lpm { 61 | uint32_t bitmask[LPM_MAX_WORDS]; 62 | void * defvals[2]; 63 | lpm_hmap_t prefix[LPM_MAX_PREFIX + 1]; 64 | }; 65 | 66 | static const uint32_t zero_address[LPM_MAX_WORDS]; 67 | 68 | lpm_t * 69 | lpm_create(void) 70 | { 71 | lpm_t *lpm; 72 | 73 | if ((lpm = calloc(1, sizeof(lpm_t))) == NULL) { 74 | return NULL; 75 | } 76 | return lpm; 77 | } 78 | 79 | void 80 | lpm_clear(lpm_t *lpm, lpm_dtor_t dtor, void *arg) 81 | { 82 | for (unsigned n = 0; n <= LPM_MAX_PREFIX; n++) { 83 | lpm_hmap_t *hmap = &lpm->prefix[n]; 84 | 85 | if (!hmap->hashsize) { 86 | ASSERT(!hmap->bucket); 87 | continue; 88 | } 89 | for (unsigned i = 0; i < hmap->hashsize; i++) { 90 | lpm_ent_t *entry = hmap->bucket[i]; 91 | 92 | while (entry) { 93 | lpm_ent_t *next = entry->next; 94 | 95 | if (dtor) { 96 | dtor(arg, entry->key, 97 | entry->len, entry->val); 98 | } 99 | free(entry); 100 | entry = next; 101 | } 102 | } 103 | free(hmap->bucket); 104 | hmap->bucket = NULL; 105 | hmap->hashsize = 0; 106 | hmap->nitems = 0; 107 | } 108 | if (dtor) { 109 | dtor(arg, zero_address, 4, lpm->defvals[0]); 110 | dtor(arg, zero_address, 16, lpm->defvals[1]); 111 | } 112 | memset(lpm->bitmask, 0, sizeof(lpm->bitmask)); 113 | memset(lpm->defvals, 0, sizeof(lpm->defvals)); 114 | } 115 | 116 | void 117 | lpm_destroy(lpm_t *lpm) 118 | { 119 | lpm_clear(lpm, NULL, NULL); 120 | free(lpm); 121 | } 122 | 123 | /* 124 | * fnv1a_hash: Fowler-Noll-Vo hash function (FNV-1a variant). 125 | */ 126 | static uint32_t 127 | fnv1a_hash(const void *buf, size_t len) 128 | { 129 | uint32_t hash = 2166136261UL; 130 | const uint8_t *p = buf; 131 | 132 | while (len--) { 133 | hash ^= *p++; 134 | hash *= 16777619U; 135 | } 136 | return hash; 137 | } 138 | 139 | static bool 140 | hashmap_rehash(lpm_hmap_t *hmap, unsigned size) 141 | { 142 | lpm_ent_t **bucket; 143 | unsigned hashsize; 144 | 145 | for (hashsize = 1; hashsize < size; hashsize <<= 1) { 146 | continue; 147 | } 148 | if ((bucket = calloc(1, hashsize * sizeof(lpm_ent_t *))) == NULL) { 149 | return false; 150 | } 151 | for (unsigned n = 0; n < hmap->hashsize; n++) { 152 | lpm_ent_t *list = hmap->bucket[n]; 153 | 154 | while (list) { 155 | lpm_ent_t *entry = list; 156 | uint32_t hash = fnv1a_hash(entry->key, entry->len); 157 | const unsigned i = hash & (hashsize - 1); 158 | 159 | list = entry->next; 160 | entry->next = bucket[i]; 161 | bucket[i] = entry; 162 | } 163 | } 164 | hmap->hashsize = hashsize; 165 | free(hmap->bucket); // may be NULL 166 | hmap->bucket = bucket; 167 | return true; 168 | } 169 | 170 | static lpm_ent_t * 171 | hashmap_insert(lpm_hmap_t *hmap, const void *key, size_t len) 172 | { 173 | const unsigned target = hmap->nitems + LPM_HASH_STEP; 174 | const size_t entlen = offsetof(lpm_ent_t, key[len]); 175 | uint32_t hash, i; 176 | lpm_ent_t *entry; 177 | 178 | if (hmap->hashsize < target && !hashmap_rehash(hmap, target)) { 179 | return NULL; 180 | } 181 | 182 | hash = fnv1a_hash(key, len); 183 | i = hash & (hmap->hashsize - 1); 184 | entry = hmap->bucket[i]; 185 | while (entry) { 186 | if (entry->len == len && memcmp(entry->key, key, len) == 0) { 187 | return entry; 188 | } 189 | entry = entry->next; 190 | } 191 | 192 | if ((entry = malloc(entlen)) != NULL) { 193 | memcpy(entry->key, key, len); 194 | entry->next = hmap->bucket[i]; 195 | entry->len = len; 196 | 197 | hmap->bucket[i] = entry; 198 | hmap->nitems++; 199 | } 200 | return entry; 201 | } 202 | 203 | static lpm_ent_t * 204 | hashmap_lookup(lpm_hmap_t *hmap, const void *key, size_t len) 205 | { 206 | const uint32_t hash = fnv1a_hash(key, len); 207 | const unsigned i = hash & (hmap->hashsize - 1); 208 | lpm_ent_t *entry; 209 | 210 | if (hmap->hashsize == 0) { 211 | return NULL; 212 | } 213 | entry = hmap->bucket[i]; 214 | 215 | while (entry) { 216 | if (entry->len == len && memcmp(entry->key, key, len) == 0) { 217 | return entry; 218 | } 219 | entry = entry->next; 220 | } 221 | return NULL; 222 | } 223 | 224 | static int 225 | hashmap_remove(lpm_hmap_t *hmap, const void *key, size_t len) 226 | { 227 | const uint32_t hash = fnv1a_hash(key, len); 228 | const unsigned i = hash & (hmap->hashsize - 1); 229 | lpm_ent_t *prev = NULL, *entry; 230 | 231 | if (hmap->hashsize == 0) { 232 | return -1; 233 | } 234 | entry = hmap->bucket[i]; 235 | 236 | while (entry) { 237 | if (entry->len == len && memcmp(entry->key, key, len) == 0) { 238 | if (prev) { 239 | prev->next = entry->next; 240 | } else { 241 | hmap->bucket[i] = entry->next; 242 | } 243 | free(entry); 244 | return 0; 245 | } 246 | prev = entry; 247 | entry = entry->next; 248 | } 249 | return -1; 250 | } 251 | 252 | /* 253 | * compute_prefix: given the address and prefix length, compute and 254 | * return the address prefix. 255 | */ 256 | static inline void 257 | compute_prefix(const unsigned nwords, const uint32_t *addr, 258 | unsigned preflen, uint32_t *prefix) 259 | { 260 | uint32_t addr2[4]; 261 | 262 | if ((uintptr_t)addr & 3) { 263 | /* Unaligned address: just copy for now. */ 264 | memcpy(addr2, addr, nwords * 4); 265 | addr = addr2; 266 | } 267 | for (unsigned i = 0; i < nwords; i++) { 268 | if (preflen == 0) { 269 | prefix[i] = 0; 270 | continue; 271 | } 272 | if (preflen < 32) { 273 | uint32_t mask = htonl(0xffffffff << (32 - preflen)); 274 | prefix[i] = addr[i] & mask; 275 | preflen = 0; 276 | } else { 277 | prefix[i] = addr[i]; 278 | preflen -= 32; 279 | } 280 | } 281 | } 282 | 283 | /* 284 | * lpm_insert: insert the CIDR into the LPM table. 285 | * 286 | * => Returns zero on success and -1 on failure. 287 | */ 288 | int 289 | lpm_insert(lpm_t *lpm, const void *addr, 290 | size_t len, unsigned preflen, void *val) 291 | { 292 | const unsigned nwords = LPM_TO_WORDS(len); 293 | uint32_t prefix[nwords]; 294 | lpm_ent_t *entry; 295 | ASSERT(len == 4 || len == 16); 296 | 297 | if (preflen == 0) { 298 | /* 0-length prefix is a special case. */ 299 | lpm->defvals[LPM_LEN_IDX(len)] = val; 300 | return 0; 301 | } 302 | compute_prefix(nwords, addr, preflen, prefix); 303 | entry = hashmap_insert(&lpm->prefix[preflen], prefix, len); 304 | if (entry) { 305 | const unsigned n = --preflen >> 5; 306 | lpm->bitmask[n] |= 0x80000000U >> (preflen & 31); 307 | entry->val = val; 308 | return 0; 309 | } 310 | return -1; 311 | } 312 | 313 | /* 314 | * lpm_remove: remove the specified prefix. 315 | */ 316 | int 317 | lpm_remove(lpm_t *lpm, const void *addr, size_t len, unsigned preflen) 318 | { 319 | const unsigned nwords = LPM_TO_WORDS(len); 320 | uint32_t prefix[nwords]; 321 | ASSERT(len == 4 || len == 16); 322 | 323 | if (preflen == 0) { 324 | lpm->defvals[LPM_LEN_IDX(len)] = NULL; 325 | return 0; 326 | } 327 | compute_prefix(nwords, addr, preflen, prefix); 328 | return hashmap_remove(&lpm->prefix[preflen], prefix, len); 329 | } 330 | 331 | /* 332 | * lpm_lookup: find the longest matching prefix given the IP address. 333 | * 334 | * => Returns the associated value on success or NULL on failure. 335 | */ 336 | void * 337 | lpm_lookup(lpm_t *lpm, const void *addr, size_t len) 338 | { 339 | const unsigned nwords = LPM_TO_WORDS(len); 340 | unsigned i, n = nwords; 341 | uint32_t prefix[nwords]; 342 | 343 | while (n--) { 344 | uint32_t bitmask = lpm->bitmask[n]; 345 | 346 | while ((i = ffs(bitmask)) != 0) { 347 | const unsigned preflen = (32 * n) + (32 - --i); 348 | lpm_hmap_t *hmap = &lpm->prefix[preflen]; 349 | lpm_ent_t *entry; 350 | 351 | compute_prefix(nwords, addr, preflen, prefix); 352 | entry = hashmap_lookup(hmap, prefix, len); 353 | if (entry) { 354 | return entry->val; 355 | } 356 | bitmask &= ~(1U << i); 357 | } 358 | } 359 | return lpm->defvals[LPM_LEN_IDX(len)]; 360 | } 361 | 362 | /* 363 | * lpm_lookup_prefix: return the value associated with a prefix 364 | * 365 | * => Returns the associated value on success or NULL on failure. 366 | */ 367 | void * 368 | lpm_lookup_prefix(lpm_t *lpm, const void *addr, size_t len, unsigned preflen) 369 | { 370 | const unsigned nwords = LPM_TO_WORDS(len); 371 | uint32_t prefix[nwords]; 372 | lpm_ent_t *entry; 373 | ASSERT(len == 4 || len == 16); 374 | 375 | if (preflen == 0) { 376 | return lpm->defvals[LPM_LEN_IDX(len)]; 377 | } 378 | compute_prefix(nwords, addr, preflen, prefix); 379 | entry = hashmap_lookup(&lpm->prefix[preflen], prefix, len); 380 | if (entry) { 381 | return entry->val; 382 | } 383 | return NULL; 384 | } 385 | 386 | /* 387 | * lpm_strtobin: convert CIDR string to the binary IP address and mask. 388 | * 389 | * => The address will be in the network byte order. 390 | * => Returns 0 on success or -1 on failure. 391 | */ 392 | int 393 | lpm_strtobin(const char *cidr, void *addr, size_t *len, unsigned *preflen) 394 | { 395 | char *p, buf[INET6_ADDRSTRLEN]; 396 | 397 | strncpy(buf, cidr, sizeof(buf)); 398 | buf[sizeof(buf) - 1] = '\0'; 399 | 400 | if ((p = strchr(buf, '/')) != NULL) { 401 | const ptrdiff_t off = p - buf; 402 | *preflen = atoi(&buf[off + 1]); 403 | buf[off] = '\0'; 404 | } else { 405 | *preflen = LPM_MAX_PREFIX; 406 | } 407 | 408 | if (inet_pton(AF_INET6, buf, addr) == 1) { 409 | *len = 16; 410 | return 0; 411 | } 412 | if (inet_pton(AF_INET, buf, addr) == 1) { 413 | if (*preflen == LPM_MAX_PREFIX) { 414 | *preflen = 32; 415 | } 416 | *len = 4; 417 | return 0; 418 | } 419 | return -1; 420 | } 421 | --------------------------------------------------------------------------------