The tale of Crypto++ and ns-3 CMake wrappers
Written: 2023/04/30, Rome
Tags: ns-3, CMake, external library, Crypto++, libcryptopp, undefined reference
Summary
With ns-3 switching to the CMake build system, integrating external libraries into modules or simulation scripts became slightly more complicated. In particular, the Crypto++ library seems to pose very specific issues: developers giving up on CMake support and ns-3 wrappers to CMake clashing with libcryptopp linking preferences.
This post shows an easy solution for custom ns-3 modules that require Crypto++ as a dependency.
Integrating external libraries into ns-3 modules
Starting with ns-3.36, CMake has been adopted as build system for ns-3. In order to prevent radical changes affecting users, developers included a handy wrapper that closely resembles ns-3's previous build system Waf, in terms of configuration, build, and execution options. In a similar fashion, wrappers for CMake commands have been made available for users in their CMakeLists.txt files required by ns-3 modules.
However, the previous build system Waf allowed for a rather straightforward definition of external library dependencies with respect to CMake: official documentation cites that depending on the specific third-party library, users have to carefully pick the right CMake configuration for their case [1].
The Crypto++ case
Crypto++ [2] is a free and open source library for cryptographic schemes. Understandably, there are several instances of users wanting to use said library within their simulation scripts and modules, with solutions shown for ns-3 versions as old as ns-3.22 [3, 4].
However, such builds were leveraging the previous system Waf, and current ns-3 releases apparently pose some integration problems with this library [5, 6].
Recommendations from developers
The aforementioned problems between Crypto++ and ns-3 builds based on CMake stem from developers of the former library discontinuing CMake support within the main source tree [7]. A CMake-oriented version of Crypto++ has been independently maintained by the community [8].
Assuming you are in need for an official build of Crypto++ in your ns-3.36+ module, you are most likely:
Downloading a release from official repository
mkdir cryptopp_src && cd cryptopp_src
wget https://cryptopp.com/cryptopp870.zip
unzip cryptopp870.zipBuilding Crypto++ as a static library [9]
make
Note: by default, the static version of Crypto++ will be built; developers recommend using this version of the library [10]Installing the library
make install
Note: the default installation path is /usr/local/lib for the library and /usr/local/include/cryptopp for headers [11]Ensuring the correct build outcome
./cryptest.exe v
First attempt at ns-3 integration
Upon creating your ns-3 module via the integrated utility, you might be tempted to link Crypto++ as a static library following common CMake guidelines [12] and mixing them with ns-3's custom macros. The CMakeLists.txt of a module named test-cryptopp will look like this:
You can see how lines 13~15 satisfy the Crypto++ dependency via a static import, then using ns-3's custom macro build_lib, more specifically its argument LIBRARIES_TO_LINK, at line 23 we request to link libcryptopp previously added to the project.
For completeness, the following function has been defined in order to test the outcome of Crypto++ integration with ns-3:
Afterwards, you will configure and build ns-3 by launching the usual ./ns3 configure and ./ns3 build commands with your preferred options.
And the result is... Oops!
Linking errors all over the place!
Apparently this is a common error with Crypto++, and it is about the g++ -l linking option being passed early if such errors are shown while the vtables are present in the static object [13].
Diving into ns-3 CMake wrappers
Similarly to any project leveraging CMake, ns-3's main CMakeLists.txt [14] defines various project-wide options and recursively looks for sub-directories and their respective CMakeLists.txt files. The build_lib macro used in custom modules uses the LIBRARIES_TO_LINK argument in the following way [15]:
ns3-module-macros.cmake file
src CMakeLists.txt file
utils CMakeLists.txt file
Enforcing library linking order
Noticing how the LIB_AS_NEEDED_POST variable was consistently appearing near the end of the build commands, I explored the general ns-3 CMake macro file to understand what kind of values this variable was containing [18]:
macros-and-definitions.cmake file
For this reason, the LIB_AS_NEEDED_POST variable would have to also contain the -libcryptopp option, guaranteeing its correct position according to Crypto++ recommendations.
Finally getting Crypto++ to work with ns-3
Putting together all the clues, a CMakeLists.txt file for our custom module with a Crypto++ dependency as a static library would look like this:
Afterwards, you will configure and build ns-3 by launching:
export CRYPTOPP_CONFIG="-DNS3_CRYPTOPP_INCLUDE=/usr/local/include -DNS3_CRYPTOPP_LIB=/usr/local/lib/libcryptopp.a"
./ns3 configure <your-preferred-ns3-options> -- $CRYPTOPP_CONFIG
./ns3 build
And the result is...
Compilation & linking done!
Now running an example that simply runs the previously defined CryptoPPTest() function using:
./ns3 run test-cryptopp-example
Example successfully run!
Using this CMakeLists.txt solution allows for the usage of customized Crypto++ installation paths via NS3_CRYPTOPP_INCLUDE and NS3_CRYPTOPP_LIB variables, which can be specified at ns-3 configuration time prior to building.
Disclaimer
The steps in this post have been carried out in a Docker container based on Ubuntu 18.04 LTS, using ns-3.37 and Crypto++ 8.7.0. The image used is egiona/ns3-woss:u18.04-n3.37-w1.12.4 (more details).
The solution hereby presented might not be optimal if multiple ns-3 modules link the same Crypto++ dependency: I have NOT tested that situation. From my understanding, I anticipate it would be better to use this workaround sparingly: introduce the required library in an otherwise empty custom module (e.g. ns3-cryptopp-dependency), and afterwards leverage standard ns-3 module dependency system targeting the first module. This means that a new module requiring the same dependendy would list ${libns3-cryptopp-dependency} among its LIBRARIES_TO_LINK argument list. Again, I have NOT tested this case either.
CMake might also allow more elegant solutions that are unknown to me, and I encourage anyone with such experience to point it out!
References
- https://www.nsnam.org/docs/manual/html/working-with-cmake.html#linking-third-party-libraries
- https://github.com/weidai11/cryptoppΒ
- https://groups.google.com/g/ns-3-users/c/eAjT3lywlYY
- https://www.mehic.info/2016/04/installing-and-crypto-libcryptopp-with-ns3/
- https://groups.google.com/g/ns-3-users/c/QGvnl25KS2M
- https://www.projectguideline.com/secrets-of-using-cryptography-in-ns-3-using-crypto-library/
- https://github.com/weidai11/cryptopp/issues/249#issuecomment-412829700
- https://github.com/abdes/cryptopp-cmake
- https://www.cryptopp.com/wiki/Linux#Build_and_Install_the_Library
- https://www.cryptopp.com/wiki/Linux#Crypto++_LibrariesΒ
- https://www.cryptopp.com/wiki/GNUmakefile#Installing_the_LibraryΒ
- https://discourse.cmake.org/t/how-to-statically-link-external-library-by-target-link-libraries/1718/2
- https://www.cryptopp.com/wiki/Linux#-l_option
- https://gitlab.com/nsnam/ns-3-dev/-/blob/master/CMakeLists.txt
- https://gitlab.com/nsnam/ns-3-dev/-/blob/master/build-support/custom-modules/ns3-module-macros.cmake
- https://gitlab.com/nsnam/ns-3-dev/-/blob/master/src/CMakeLists.txt
- https://gitlab.com/nsnam/ns-3-dev/-/blob/master/utils/CMakeLists.txt
- https://gitlab.com/nsnam/ns-3-dev/-/blob/master/build-support/macros-and-definitions.cmake