Skip to content

Local network Peer-to-peer library with peer discovery and callback mechanism.

License

Notifications You must be signed in to change notification settings

sunipkm/peernet

Repository files navigation

PeerNet: A cross-platform local-network P2P library

PeerNet is a wrapper around the ZeroMQ Zyre library, with a callback based design that makes application building easy. PeerNet is written in C, just like Zyre and CZMQ which PeerNet derives from.

In PeerNet, a peer can belong to a unique group, and in that group peers can have unique names. This allows for message parsing based on which peer it came from, unlike Zyre which concerns more with UUIDs which are generated for peers randomly at start.

A Python wrapper for PeerNet is also available here.

Ownership and License

PeerNet is developed by Sunip K. Mukherjee. This project uses the MPL v2 license, see LICENSE. Contributions/ideas are welcome.

Pre-requisites

This version of PeerNet uses mostly Zyre stable API, with the exception of the zyre_set_zcert draft method from Zyre version 2.0.1. PeerNet will continue to be compatible with Zyre untill this method changes, at which point PeerNet will require an update. The exact versions PeerNet was tested against are listed:

  • Zyre 2.0.1 (git checkout v2.0.1)
  • CZMQ 4.2.1 (git checkout v4.2.1)
  • ZeroMQ 4.3.4 (git checkout v4.3.4)
  • libSodium 1.0.18_1 (git checkout 1.0.18-RELEASE)

Additionally, PeerNet requires cmake or autoconf, automake and libtool for the build system to work. pkg-config, pcre (libpcre3-dev) are required for building, alongside Zyre and its pre-requisites.

Build Process (POSIX)

CMake is the preferred build tool, as it builds the chat example and documentations with ease. To build with CMake, within peernet directory, execute:

    $ mkdir build && cd build && cmake ..
    $ make
    $ make test
    $ sudo make install

This will build the project and execute the self-test program. Specify -DCMAKE_INSTALL_PREFIX=/path/to/install to set a custom installation prefix path (hereafter referred to as prefix path). The default prefix path is /usr/local.

Alternatively, in the peernet directory, execute:

    $ ./autogen.sh
    $ ./configure --prefix="/path/to/library"
    $ make
    $ make install

Check out examples/chat/chat.c to understand basic usage. The library is also extensively documented, and a doxygen documentation is generated using the cmake build system.

The PeerNet install prefix path defaults to /usr/local/ if using the cmake build system, or --prefix is not supplied in ./configure. The shared library (.so files in Linux and .dylib files in macOS) are stored in the lib subdirectory under the prefix path. Make sure the lib directory is exported to the LD_LIBRARY_PATH variable so that the shared library can be located by other binaries e.g. the chat example, or the pyPeerNet Python wrapper.

Installing on Windows

It is recommended to use the installer associated with a release. The installer automatically adds the binaries to PATH for pyPeerNet.

Alternatively, obtain the binaries from the zip file included with the latest release and extract the contents. Add \path\to\<x86|x64>\bin to the PATH variable. Replace <version> with the current PeerNet version (e.g. v3.0.0), and choose the platform (x86 or x64) accordingly for your system. This step is crucial for pyPeerNet to work on Windows.

The 'Chat' example

If built using the cmake build system, a chat.exe executable is generated in examples/chat. To run the program, open a terminal window in the peernet directory and run:

    $ cd examples/chat
    $ ./chat.exe peer_name

In order to properly test the program, open another terminal in the peernet directory and repeat the steps above. Using the same peer_name as the first instance will cause the second instance to crash. Multiple such instance can be launched in multiple terminal windows, and writing anything in one such terminal, and pressing enter will cause the instance to send the message to all other instances.

The example starts operation by initializing the peer, and starting it:

    peer_t *peer = peer_new("peer_name", NULL, "password", true); // creates a peer named "peer_name" in the default group with password "password" and encryption enabled.

At this point, a pre-defined callback function is registered in order to capture any peer that has connected.

    peer_on_connect(peer, NULL, &on_connect_callback, NULL); // registers on_connect_callback as a callback for any peer that connects. The callback does not use any local data.

The on_connect_callback() function has the following form:

    void on_connect_callback(peer_t *self, const char *message_type, const char *remote_name, void *local_args, void *remote_args, size_t remote_args_len)
    {
        peer_on_message(self, remote_name, "CHAT", &on_message_callback, NULL);
    }

Essentially, the on_connect callback that was registered, registers a callback function that is executed when any peer sends a message, of type "CHAT", to this peer. The on_message callback absolutely requires a peer name in order to avoid message spamming.

The on_message_callback() function has the following form:

    void on_connect_callback(peer_t *self, const char *message_type, const char *remote_name, void *local_args, void *remote_args, size_t remote_args_len)
    {
        printf("%s> %s\n", remote_name, (char *) remote_args);
    }

The callback will be executed only for a message of type "CHAT". Since instances of chat.exe are talking amongst one another, the message format sanity is guaranteed.

At this point, an instance of the peer can be started:

    peer_start(peer);

CZMQ provides a nice variable that can is set when Ctrl + C is pressed (SIGINT is raised), called zsys_interrupted. A while loop can be run on this variable after starting the peer to monitor input from stdin, and shout the message to all available chat.exe clients:

    while (!zsys_interrupted) // run while SIGINT is not raised
    {
        char message[1024];
        if (!fgets (message, 1024, stdin)) // if this returns -1, program was interrupted
            break;
        message[strlen (message) - 1] = 0; // Drop the trailing linefeed
        assert(!peer_shouts(peer, "CHAT", "%s", message));
    }

A peer can be stopped after getting out of the while loop by either calling peer_stop, or by calling peer_destroy which will also free all the resources associated with that instance of peer:

    peer_destroy(&peer); // destroy peer
    return 0;

All in all, with 36 lines of code, a terminal chat client is implemented.

Building on Windows

Building PeerNet on Windows systems is not straight forward. Use of the attached binaries is suggested.

To begin with, install Visual Studio Community Edition 2019 with CMake tools. After the tools are installed, navigate to the Start Menu, and find the Visual Studio 2019 directory sub-menu. Under the sub-menu, select <x64|x86> Native Tools Command Prompt depending on your platform (use the cross-compile command tools as needed). This command prompt sets up the build environment with cmake, cl (the Visual C++ compiler) etc. Check if you can access git, cmake and cl in this command line by typing in the commands one by one. By default, the command line should open to %USERPROFILE%\source\repos directory. In case it does not, it is recommended that you use the following commands to create the aforementioned path and work there.

cd %USERPROFILE%
mkdir source
cd source
mkdir repos
cd repos

Note: Git for Windows may need to be installed separately prior to opening this command line.

Building Libsodium

Libsodium is the cryptographic backend that is required. In order to build it:

git clone https://github.com/jedisct1/libsodium.git
cd libsodium
git checkout 1.0.18-RELEASE
cd builds\msvc\build
buildall.bat
cd ..\..\..\

At this point, the command line should be in the libsodium directory. The library files can be found under bin\<Win32|x64>\<Debug|Release>\<Platform Toolset>\<dynamic|ltcg|static>.

Here, the <Platform Toolset> is the platform toolset you are using: v100 for VS2010, v140 for VS2015, v141 for VS2017, v142 for VS2019 etc.

At this point, in comamnd line:

start .
cd ..

This will open a new File Explorer window in the libsodium directory, and the required build files for libsodium can be accessed. The command line is now in the root directory containing the libsodium directory.

Building ZeroMQ

git clone https://github.com/zeromq/libzmq.git
cd libzmq
git checkout v4.3.4
mkdir build
cd build
cmake .. -DBUILD_STATIC=OFF -DBUILD_SHARED=ON -DZMQ_BUILD_TESTS=ON -DWITH_LIBSODIUM=ON -DCMAKE_INCLUDE_PATH=..\libsodium\src\libsodium\include -DCMAKE_LIBRARY_PATH=..\libsodium\bin\<Win32|x64>\Release\<Platform Toolset>\dynamic -DCMAKE_INSTALL_PREFIX=C:\libzmq
cmake --build . --config Release --target install
cd ..\..\

Insert the relevant platform (Win32/x64) and toolset (v142 for VS2019) in the cmake command.

This will build ZeroMQ with libsodium, and install libzmq into C:\libzmq. You may need to run your shell with administrator privilege in order to write to the system disk, or change to a directory more suited for your needs. Once zeromq is installed, manually copy the libsodium.dll file from libsodium\bin\<Win32|x64>\Release\<Platform Toolset>\dynamic directory to C:\libzmq\bin directory, and the libsodium.lib file from the same directory to C:\libzmq\lib. Replace C:\libzmq with the CMAKE_INSTALL_PREFIX for zeromq if it is different.

Note: zeromq builds are toolset specific, and the compiled libraries and DLLs contain the toolset number and version info in the file name. Adequately edit the zeromq*.lib file name in peernet\build_win.bat file in case a different version and toolset is used. This behavior is not tested.

Building CZMQ

git clone https://github.com/zeromq/czmq.git
cd czmq
git checkout v4.2.1
mkdir build
cd build
cmake .. -DCZMQ_BUILD_SHARED=ON -DCZMQ_BUILD_STATIC=OFF -DCMAKE_PREFIX_PATH=C:\libzmq -DCMAKE_INSTALL_PREFIX=C:\libzmq
cmake --build . --config Release --target install

This will build and install czmq into C:\libzmq. Replace C:\libzmq with the path you used in the previous step.

Building Zyre

git clone https://github.com/zeromq/zyre.git
cd zyre
git checkout v2.0.1
mkdir build
cd build
cmake .. -DZYRE_BUILD_SHARED=ON -DZYRE_BUILD_STATIC=OFF -DCMAKE_PREFIX_PATH=C:\libzmq -DCMAKE_INSTALL_PREFIX=C:\libzmq -DENABLE-DRAFTS=YES 
cmake --build . --config Release --target install

This will build and install zyre into C:\libzmq. Replace C:\libzmq with the path you used in the previous step.

Building PeerNet

git clone https://github.com/sunipkm/peernet
cd peernet
git checkout v3.0.0
build_win

This will create the Release directory. The relevant files in this case are libpeer.dll and libpeer.lib. These files are copied to C:\libzmq\bin and C:\libzmq\lib, respectively. The header files (peer.h, peer_library.h, peer_private.h) are copied from peernet\include directory to C:\libzmq\include. Additionally, the chat example (peerchat.exe) is copied to the C:\libzmq\bin directory.

Finally, C:\libzmq\bin directory should be added to PATH for the dynamic libraries to be loaded automatically. At this point, start peerchat.exe in C:\libzmq\lib from file explorer to ensure there are no error messages related to missing DLLs. Run peerchat.exe in command line (which should be available anywhere if the directory is in path) to test.

Possible Security Concern

Compared to the first version of the API where any Zyre client could snoop at our secret group name and guessed how to join the local network, an extra password authentication layer has been added. However, this layer is only marginally more effective as nothing prevents an adversary from snooping the group name (that is probably not changing very often), and starting a peer in that group (which will become the first peer connected, hence the primary arbiter of the group as it will allow/disallow the next peer to enter the group.) This is definitely going to prevent intrusion into an existing network, but does not prevent access after an attempt at a network restart. More work is needed in this front, and ideas are very much welcome.