tools: add dvpn

dvpn tool is a VPN based on the DHTNET stack. The tool creates a TUN interface to efficiently route traffic to the destination.

For the client side, upon establishing a connection, the tool creates a TUN interface and sets it as the default route in the routing table, excluding traffic destined for the peer. On the server side, a TUN interface is created, and NAT configuration is used to respond to client requests.

Change-Id: I43ff43982930d97502a64d15aeb2c8df283bdda9
GitLab: #13
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a2272fa..c26432e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -51,7 +51,8 @@
             set(MSGPACK_TARGET "msgpackc-cxx")
         endif()
     endif()
-
+    find_package(yaml-cpp REQUIRED)
+    include_directories(${YAML_CPP_INCLUDE_DIR})
     find_package(fmt)
     pkg_search_module (opendht REQUIRED IMPORTED_TARGET opendht)
     pkg_search_module (pjproject REQUIRED IMPORTED_TARGET libpjproject)
@@ -252,9 +253,17 @@
         tools/dsh/dsh.cpp
         tools/common.cpp)
     target_link_libraries(dsh PRIVATE dhtnet fmt::fmt)
-    target_include_directories(dnc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tools)
+    target_include_directories(dsh PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tools)
     install(TARGETS dsh RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
 
+    add_executable(dvpn
+        tools/dvpn/main.cpp
+        tools/dvpn/dvpn.cpp
+        tools/common.cpp)
+    target_link_libraries(dvpn PRIVATE dhtnet fmt::fmt yaml-cpp)
+    target_include_directories(dvpn PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tools)
+    install(TARGETS dvpn RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
     find_library(READLINE_LIBRARIES readline)
     find_path(READLINE_INCLUDE_DIR readline/readline.h)
     add_library(readline STATIC IMPORTED)
diff --git a/README.md b/README.md
index c2c7932..7bd7663 100644
--- a/README.md
+++ b/README.md
@@ -100,7 +100,7 @@
 
 ## See also
 
-### Dnc: Distributed nc
+### [Dnc: Distributed nc](tools/dnc/README.md)
 
 dnc is a command-line program that provides network connectivity between peers in a Distributed Hash Table (DHT) network. It allows peers to establish connections with other peers and create a TCP socket on a remote devices, similar to the behavior of the traditional nc utility.
 #### SSH configuration
@@ -127,13 +127,13 @@
 ssh mypeer@dnc/2f4975e7b11a0908bd400b27130fe9a496d0f415
 ```
 
-### Dsh: Distributed shell
+### [Dsh: Distributed shell](tools/dsh/README.md)
 
 dsh is a Distributed Shell command-line program that enables peers to establish connections with other peers in a Distributed Hash Table (DHT) network and execute a binary on the remote target.
 
 #### Setting up the Server (Listening) and Default Command
 
-To set up the dsh server to listen for incoming connections and execute bash by default if no file is specified, execute the following command on the server:
+To set up tahe dsh server to listen for incoming connections and execute bash by default if no file is specified, execute the following command on the server:
 ```sh
 dsh -l
 ```
@@ -143,9 +143,32 @@
 ```sh
 dsh -I /home/<local_user>/.dhtnet/client <peer_id>
 ```
-##### Using Different Certificates
 
-If the client and server are on the same machine, they should use different certificates for authentication, so make sure to specify different identity file paths for the client and server. This ensures that they use separate certificates. In the example above, we specified the client's identity file path as /home/<local_user>/.dhtnet/client
+### [Dvpn: Distributed VPN](tools/dvpn/README.md)
+
+dvpn is a powerful VPN tool built on the foundation of the DHTNet library. dvpn supports both server and client modes, offering flexibility in deployment sceanrios.
+
+Before using dvpn, please fellow the [configuration instractions](tools/dvpn/README.md#configuration).
+
+#### Setting up the Server (Listening)
+
+To set up a dvpn server to listen for incoming connections, execute the following command on the server:
+```sh
+sudo dvpn -l
+```
+
+#### Connecting from the Client
+
+Replace <peer_id> with the actual peer ID you want to connect to:
+```sh
+sudo dvpn -I /home/<local_user>/.dhtnet/client <peer_id>
+```
+
+**Note**: **dvpn** requires sudo privileges to create and configure TUN interfaces on both the client and server sides.
+
+### Using Different Certificates
+
+If the client and server are on the same machine, they should use different certificates for authentication, so make sure to specify different identity file paths for the client and server. This ensures that they use separate certificates. In the examples above, we specified the client's identity file path as /home/<local_user>/.dhtnet/client
 
 ## Report issues
 
diff --git a/tools/dvpn/README.md b/tools/dvpn/README.md
new file mode 100644
index 0000000..d0bbb39
--- /dev/null
+++ b/tools/dvpn/README.md
@@ -0,0 +1,79 @@
+# dvpn - Distributed VPN
+
+## Overview
+
+**dvpn** is a VPN tool based on DHTNet library.
+
+**Key Features**:
+
+- **Decentralized Architecture**: Utilizes a DHT for peer discovery and communication.
+- **Client-Server Model**: Supports both server and client modes for flexible deployment.
+- **TUN Interface**: Implements a TUN interface for network communication.
+- **VPN Security**: Ensures secure communication channels using cryptography and identity verification.
+- **Configuration Flexibility** : Modify configuration settings and the setup script independently, avoiding the need to rebuild the entire project..
+
+## Configuration
+
+Before using **dvpn**, make sure to disable IPv6. To disable IPv6, follow these steps:
+
+1. Open the terminal.
+2. Edit the `/etc/sysctl.conf` file using a text editor.
+3. Add the following lines at the end of the file:
+    ```shell
+    net.ipv6.conf.all.disable_ipv6 = 1
+    net.ipv6.conf.default.disable_ipv6 = 1
+    ```
+4. Save the file and exit the text editor.
+5. Apply the changes by running the following command:
+    ```shell
+    sudo sysctl -p
+    ```
+
+Additionally, follow these steps to update your configuration:
+
+1. Locate the default configuration file at `dhtnet/tools/dvpn/test_config.yaml`.
+2. Update the `script_path` section by providing the absolute path for the `dvpn_up.sh` file.
+
+### Options
+
+**dvpn** accepts the following command-line options:
+
+- `-h, --help`: Display help information
+- `-V, --version`: Display the version information of **dvpn**.
+- `-l, --listen`: Run **dvpn** in listen mode, allowing the program to accept incoming VPN connections.
+- `-b, --bootstrap <BOOTSTRAP_ADDRESS>`: Specify the address of a bootstrap node to connect to an existing DHT network. This option requires an argument. The default value is "bootstrap.jami.net" if not specified.
+- `-I, --id_path <IDENTITY_PATH>`: Specify the path to the identity file, which contains information about your identity and is used for DHT network interactions. This option requires an argument. The default value is "$HOME/.dhtnet" if not specified.
+- `-t, --turn_host <TURN_SERVER>`: Specify the hostname or IP address of the TURN (Traversal Using Relays around NAT) server to use for network traversal. This option requires an argument. The default value is "turn.jami.net" if not specified.
+- `-u, --turn_user <TURN_USERNAME>`: Specify the username for authentication with the TURN server. This option requires an argument. The default value is "ring" if not specified.
+- `-w, --turn_pass <TURN_PASSWORD>`: Specify the password for authentication with the TURN server. This option requires an argument. The default value is "ring" if not specified.
+- `-r, --turn_realm <TURN_REALM>`: Specify the realm for authentication with the TURN server. This option requires an argument. The default value is "ring" if not specified.
+- `-c, --configuration_path_file <CONF_PATH>`: Specify the path to the configuration file. The default value is "dhtnet/tools/dvpn/test_config.yaml" if not specified.
+- `<PEER_ID>`: The peer ID argument is required when not running in listen mode. It specifies the ID of the target peer or device in the DHT network with which the connection should be established.
+
+To run a dvpn server, you can use the following command:
+```shell
+sudo ./dvpn -l
+```
+
+To connect to a dvpn server, you can use the following command:
+```shell
+sudo ./dvpn <PEER_ID>
+```
+
+**Note**: **dvpn** requires sudo privileges to create and configure TUN interfaces on both the client and server sides.
+
+
+## VPN Setup Process
+
+For each connection, **dvpn** dynamically creates a new TUN interface, utilizing information read from the configuration file (default: `test_config.yaml`), and passes it to the setup script (default: `dvpn_up.sh`). As a result, the server generates a unique TUN interface for each client, while the client creates only one interface.
+
+
+Following this, the setup script takes charge of configuring the TUN interface and establishing routing logic for the client, as well as managing Network Address Translation (NAT) for the server.
+
+The configuration file includes the path to the setup script and the IP address and IP peer address prefixes. The server uses these prefixes to generate a valid address, and the client dynamically receives these addresses from the server during the connection process.
+
+
+The TUN interfaces are configured as follows:
+
+- **Server TUN Interface:** `<server tun address> 255.255.255.255 <client tun address>`
+- **Client TUN Interface:** `<client tun address> 255.255.255.255 <server tun address>`
diff --git a/tools/dvpn/dvpn.1 b/tools/dvpn/dvpn.1
new file mode 100644
index 0000000..6cbc73c
--- /dev/null
+++ b/tools/dvpn/dvpn.1
@@ -0,0 +1,90 @@
+.TH dvpn 1 "December 2023" "dvpn Manual"
+
+.SH NAME
+dvpn \- Distributed VPN
+
+.SH SYNOPSIS
+.B dvpn
+[\-h | \-\-help] [\-\-version] [\-\-listen] [\-\-bootstrap <BOOTSTRAP_ADDRESS>] [\-\-id_path <IDENTITY_PATH>] [\-\-turn_host <TURN_SERVER>] [\-\-turn_user <TURN_USERNAME>] [\-\-turn_pass <TURN_PASSWORD>] [\-\-turn_realm <TURN_REALM>] [\-\-configuration_path_file <CONF_PATH>] <PEER_ID>
+
+.SH DESCRIPTION
+.B dvpn
+is a VPN tool based on the DHTNet library, utilizing a decentralized architecture for peer discovery and communication. It supports both server and client modes, implements a TUN interface for network communication and offers configuration flexibility.
+
+.SH OPTIONS
+.TP
+.B \-h, \-\-help
+Display help information.
+
+.TP
+.B \-V, \-\-version
+Display the version information of dvpn.
+
+.TP
+.B \-l, \-\-listen
+Run dvpn in listen mode, allowing the program to accept incoming VPN connections.
+
+.TP
+.B \-b, \-\-bootstrap <BOOTSTRAP_ADDRESS>
+Specify the address of a bootstrap node to connect to an existing DHT network. Default is "bootstrap.jami.net" if not specified.
+
+.TP
+.B \-I, \-\-id_path <IDENTITY_PATH>
+Specify the path to the identity file used for DHT network interactions. Default is "$HOME/.dhtnet" if not specified.
+
+.TP
+.B \-t, \-\-turn_host <TURN_SERVER>
+Specify the hostname or IP address of the TURN server for network traversal.
+
+.TP
+.B \-u, \-\-turn_user <TURN_USERNAME>
+Specify the username for authentication with the TURN server.
+
+.TP
+.B \-w, \-\-turn_pass <TURN_PASSWORD>
+Specify the password for authentication with the TURN server.
+
+.TP
+.B \-r, \-\-turn_realm <TURN_REALM>
+Specify the realm for authentication with the TURN server.
+
+.TP
+.B \-c, \-\-configuration_path_file <CONF_PATH>
+Specify the path to the configuration file. Default is "dhtnet/tools/dvpn/test_config.yaml" if not specified.
+
+.TP
+.B <PEER_ID>
+The peer ID argument is required when not running in listen mode. It specifies the ID of the target peer or device in the DHT network.
+
+.SH VPN SETUP PROCESS
+For each connection,
+.B dvpn
+dynamically creates a new TUN interface, utilizing information from the configuration file. The setup script then configures the TUN interface and establishes routing logic for the client, as well as managing NAT for the server.
+
+The TUN interfaces are configured as follows:
+
+Server TUN Interface:
+.IP
+<server tun address> 255.255.255.255 <client tun address>
+
+Client TUN Interface:
+.IP
+<client tun address> 255.255.255.255 <server tun address>
+
+.SH NOTES
+.B dvpn
+requires sudo privileges to create and configure TUN interfaces on both the client and server sides.
+
+.SH EXAMPLES
+To run a dvpn server:
+.IP
+$ sudo ./dvpn -l
+
+To connect to a dvpn server:
+.IP
+$ sudo ./dvpn <PEER_ID>
+
+.SH SEE ALSO
+The DHTNet library documentation for more details.
+
+.SH "dvpn 1.0"                           December 2023                         DVPN(1)
\ No newline at end of file
diff --git a/tools/dvpn/dvpn.cpp b/tools/dvpn/dvpn.cpp
new file mode 100644
index 0000000..212b340
--- /dev/null
+++ b/tools/dvpn/dvpn.cpp
@@ -0,0 +1,391 @@
+/*
+ *  Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "dvpn.h"
+#include "certstore.h"
+#include "connectionmanager.h"
+#include "fileutils.h"
+#include "../common.h"
+
+#include <opendht/log.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <sys/wait.h>
+#include <yaml-cpp/yaml.h>
+#include <fstream>
+
+struct Config
+{
+    std::string ip_address;
+    std::string ip_peer_address;
+    std::string ip_address_ipv6;
+    std::string ip_peer_address_ipv6;
+    std::string script_path;
+
+    Config(const YAML::Node& node, std::string_view tun_device)
+    {
+        std::string_view tun_device_str(tun_device);
+        auto tun_device_number = tun_device_str.substr(3);
+
+        if (node["ip_address"])
+            ip_address = fmt::format("{}{}",
+                                     node["ip_address"].as<std::string>(),
+                                     tun_device_number);
+        if (node["ip_peer_address"])
+            ip_peer_address = fmt::format("{}{}",
+                                          node["ip_peer_address"].as<std::string>(),
+                                          tun_device_number);
+        if (node["script_path"])
+            script_path = node["script_path"].as<std::string>();
+        if (node["ip_address_ipv6"])
+            ip_address_ipv6 = fmt::format("{}{}",
+                                     node["ip_address_ipv6"].as<std::string>(),
+                                     tun_device_number);
+        if (node["ip_peer_address_ipv6"])
+            ip_peer_address_ipv6 = fmt::format("{}{}",
+                                          node["ip_peer_address_ipv6"].as<std::string>(),
+                                          tun_device_number);
+    }
+
+    YAML::Node toYAML() const
+    {
+        YAML::Node node;
+        node["ip_address"] = ip_address;
+        node["ip_peer_address"] = ip_peer_address;
+        node["script_path"] = script_path;
+        node["ip_address_ipv6"] = ip_address_ipv6;
+        node["ip_peer_address_ipv6"] = ip_peer_address_ipv6;
+        return node;
+    }
+};
+
+// Call a script shell
+int
+call_script_shell(const char* script,
+                  const char* remote_tun_ip,
+                  const char* tun_ip,
+                  const char* tun_device,
+                  const char* remote_address,
+                  const char* is_client,
+                  const char* remote_tun_ip_ipv6,
+                  const char* tun_ip_ipv6)
+{
+    pid_t pid;
+    int status;
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    if ((pid = fork()) < 0) {
+        perror("fork");
+        return -1;
+    } else if (pid == 0) {
+        if (execl(script,
+                  script,
+                  remote_tun_ip,
+                  tun_ip,
+                  tun_device,
+                  remote_address,
+                  is_client,
+                  remote_tun_ip_ipv6,
+                  tun_ip_ipv6,
+                  (char*) 0)
+            < 0) {
+            perror("execl");
+            return -1;
+        }
+
+    } else {
+        while (wait(&status) != pid) {
+            // wait for completion
+        }
+    }
+    return 0;
+}
+
+int
+open_tun(char* dev)
+{
+    int fd; // file descriptor
+    struct ifreq ifr;
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    if ((fd = open("/dev/net/tun", O_RDWR)) < 0) {
+        perror("Opening /dev/net/tun");
+        return -1;
+    }
+
+    memset(&ifr, 0, sizeof(ifr));
+    ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
+
+    if (*dev) {
+        /* if a device name was specified, put it in the structure; otherwise,
+         * the kernel will try to allocate the "next" device of the
+         * specified type */
+        strncpy(ifr.ifr_name, dev, IFNAMSIZ);
+    }
+
+    if (ioctl(fd, TUNSETIFF, (void*) &ifr) < 0) {
+        perror("Configuring TUN interface");
+        close(fd);
+        return -1;
+    }
+    strcpy(dev, ifr.ifr_name);
+    return fd;
+}
+
+dhtnet::Dvpn::Dvpn(const std::filesystem::path& path,
+                   dht::crypto::Identity identity,
+                   const std::string& bootstrap,
+                   const std::string& turn_host,
+                   const std::string& turn_user,
+                   const std::string& turn_pass,
+                   const std::string& turn_realm,
+                   const std::string& configuration_file)
+    : logger(dht::log::getStdLogger())
+    , ioContext(std::make_shared<asio::io_context>())
+{
+    auto certStore = std::make_shared<tls::CertificateStore>(path / "certstore", logger);
+    ioContextRunner = std::thread([context = ioContext, logger = logger] {
+        try {
+            auto work = asio::make_work_guard(*context);
+            context->run();
+        } catch (const std::exception& ex) {
+            if (logger)
+                logger->error("Error in ioContextRunner: {}", ex.what());
+        }
+    });
+
+    auto config = connectionManagerConfig(path,
+                                          identity,
+                                          bootstrap,
+                                          logger,
+                                          certStore,
+                                          ioContext,
+                                          iceFactory,
+                                          turn_host,
+                                          turn_user,
+                                          turn_pass,
+                                          turn_realm);
+    // create a connection manager
+    connectionManager = std::make_unique<ConnectionManager>(std::move(config));
+
+    connectionManager->onDhtConnected(identity.first->getPublicKey());
+    connectionManager->onICERequest([this](const dht::Hash<32>&) { // handle ICE request
+        if (logger)
+            logger->debug("ICE request received");
+        return true;
+    });
+}
+
+dhtnet::DvpnServer::DvpnServer(const std::filesystem::path& path,
+                               dht::crypto::Identity identity,
+                               const std::string& bootstrap,
+                               const std::string& turn_host,
+                               const std::string& turn_user,
+                               const std::string& turn_pass,
+                               const std::string& turn_realm,
+                               const std::string& configuration_file)
+    : Dvpn(path, identity, bootstrap, turn_host, turn_user, turn_pass, turn_realm, configuration_file)
+{
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+
+    connectionManager->onChannelRequest(
+        [&](const std::shared_ptr<dht::crypto::Certificate>&, const std::string& channel) {
+            // handle channel request
+            if (logger)
+                logger->debug("Channel request received: {}", channel);
+            return true;
+        });
+
+    connectionManager->onConnectionReady([=](const DeviceId&,
+                                             const std::string& channel,
+                                             std::shared_ptr<ChannelSocket> socket) {
+        char tun_device[IFNAMSIZ] = {0}; // IFNAMSIZ is typically the maximum size for interface names
+        // create a TUN interface
+        int tun_fd = open_tun(tun_device);
+        if (tun_fd < 0) {
+            if (logger)
+                logger->error("Error opening TUN interface");
+        }
+        auto tun_stream = std::make_shared<asio::posix::stream_descriptor>(*ioContext, tun_fd);
+
+        if (socket) {
+            // read yaml file
+            YAML::Node config = YAML::LoadFile(configuration_file.c_str());
+            auto conf = Config(config, tun_device);
+
+            // call script shell function to configure tun interface
+            if (call_script_shell(conf.script_path.c_str(),
+                                  conf.ip_peer_address.c_str(),
+                                  conf.ip_address.c_str(),
+                                  tun_device,
+                                  strdup(socket->getRemoteAddress().toString().c_str()),
+                                  "false",
+                                  conf.ip_peer_address_ipv6.c_str(),
+                                  conf.ip_address_ipv6.c_str())
+                < 0) {
+                if (logger)
+                    logger->error("Error configuring IP address");
+            }
+
+            MetaData val;
+            val.addrClient = conf.ip_peer_address;
+            val.addrServer = conf.ip_address;
+            val.addrClientIpv6 = conf.ip_peer_address_ipv6;
+            val.addrServerIpv6 = conf.ip_address_ipv6;
+            msgpack::sbuffer buffer(64);
+            msgpack::pack(buffer, val);
+
+            std::error_code ec;
+            int res = socket->write(reinterpret_cast<const uint8_t*>(buffer.data()),
+                                    buffer.size(),
+                                    ec);
+            if (res < 0) {
+                if (logger)
+                    logger->error("Send peer TUN IP addr - error: {}", ec.message());
+            }
+            auto buffer_data = std::make_shared<std::vector<uint8_t>>(BUFFER_SIZE);
+            readFromPipe(socket, tun_stream, buffer_data);
+            socket->setOnRecv([tun_stream, this](const uint8_t* data, size_t size) {
+                auto data_copy = std::make_shared<std::vector<uint8_t>>(data, data + size);
+                asio::async_write(*tun_stream,
+                                  asio::buffer(*data_copy),
+                                  [data_copy, this](const std::error_code& error,
+                                                    std::size_t bytesWritten) {
+                                      if (error) {
+                                          if (logger)
+                                              logger->error("Error writing to TUN interface: {}",
+                                                            error.message());
+                                      }
+                                  });
+                return size;
+            });
+        }
+    });
+}
+
+// Build a client
+dhtnet::DvpnClient::DvpnClient(dht::InfoHash peer_id,
+                               const std::filesystem::path& path,
+                               dht::crypto::Identity identity,
+                               const std::string& bootstrap,
+
+                               const std::string& turn_host,
+                               const std::string& turn_user,
+                               const std::string& turn_pass,
+                               const std::string& turn_realm,
+                               const std::string& configuration_file)
+    : Dvpn(path, identity, bootstrap, turn_host, turn_user, turn_pass, turn_realm, configuration_file)
+{
+    // connect to a peer
+    connectionManager->connectDevice(
+        peer_id, "dvpn://", [=](std::shared_ptr<ChannelSocket> socket, const dht::InfoHash&) {
+            if (socket) {
+                // create a TUN interface
+                tun_fd = open_tun(tun_device);
+                if (tun_fd < 0) {
+                    if (logger)
+                        logger->error("Error opening TUN interface");
+                }
+
+                tun_stream = std::make_shared<asio::posix::stream_descriptor>(*ioContext, tun_fd);
+
+                socket->setOnRecv([=](const uint8_t* data, size_t size) {
+                    if (connection_state == CommunicationState::METADATA) {
+                        pac_.reserve_buffer(size);
+                        memcpy(pac_.buffer(), data, size);
+                        pac_.buffer_consumed(size);
+
+                        msgpack::object_handle oh;
+                        if (pac_.next(oh)) {
+                            try {
+                                auto msg = oh.get().as<MetaData>();
+                                YAML::Node config = YAML::LoadFile(configuration_file.c_str());
+                                auto conf = Config(config, tun_device);
+                                // configure tun interface by calling script shell function
+                                if (call_script_shell(conf.script_path.c_str(),
+                                                      msg.addrServer.c_str(),
+                                                      msg.addrClient.c_str(),
+                                                      tun_device,
+                                                      strdup(socket->getRemoteAddress()
+                                                                 .toString()
+                                                                 .c_str()),
+
+                                                      "true",
+                                                      msg.addrServerIpv6.c_str(),
+                                                      msg.addrClientIpv6.c_str())
+                                    < 0) {
+                                    if (logger)
+                                        logger->error("Error configuring IP address");
+                                }
+                                connection_state = CommunicationState::DATA;
+                            } catch (...) {
+                                if (logger)
+                                    logger->error("Error parsing metadata");
+                            }
+                        }
+                        return size;
+                    } else if (connection_state == CommunicationState::DATA) {
+                        auto data_copy = std::make_shared<std::vector<uint8_t>>(data, data + size);
+                        asio::async_write(*tun_stream,
+                                          asio::buffer(*data_copy),
+                                          [data_copy, this](const std::error_code& error,
+                                                            std::size_t bytesWritten) {
+                                              if (error) {
+                                                  if (logger)
+                                                      logger->error(
+                                                          "Error writing to TUN interface: {}",
+                                                          error.message());
+                                              }
+                                          });
+                        return size;
+                    }
+                    return size;
+                });
+                auto buffer = std::make_shared<std::vector<uint8_t>>(BUFFER_SIZE);
+                readFromPipe(socket, tun_stream, buffer);
+            }
+        });
+
+    connectionManager->onConnectionReady(
+        [&](const DeviceId&, const std::string& channel, std::shared_ptr<ChannelSocket> socket) {
+            if (logger)
+                logger->debug("Connected!");
+        });
+}
+
+void
+dhtnet::Dvpn::run()
+{
+    ioContext->run();
+}
+
+dhtnet::Dvpn::~Dvpn()
+{
+    ioContext->stop();
+    ioContextRunner.join();
+}
diff --git a/tools/dvpn/dvpn.h b/tools/dvpn/dvpn.h
new file mode 100644
index 0000000..42382aa
--- /dev/null
+++ b/tools/dvpn/dvpn.h
@@ -0,0 +1,109 @@
+/*
+ *  Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+#include "connectionmanager.h"
+#include "multiplexed_socket.h"
+#include "ice_transport_factory.h"
+#include "certstore.h"
+
+#include <asio.hpp>
+
+namespace dhtnet {
+
+/*
+    Both the client and the server have a TUN interface.
+    The server creates a a TUN interface for each client.
+    The client needs to know the server TUN address (peer address in the TUN configuration).
+    The server send its TUN addresses to the client in the first packet.
+    Two states are used to handle this:
+    - METADATA: the first packet is sent by the server and contains its TUN address
+    - DATA: the actual data
+*/
+
+struct MetaData
+{
+    std::string addrClient;
+    std::string addrServer;
+    std::string addrClientIpv6;
+    std::string addrServerIpv6;
+    MSGPACK_DEFINE_MAP(addrClient, addrServer, addrClientIpv6, addrServerIpv6);
+};
+
+class Dvpn
+{
+public:
+    Dvpn(const std::filesystem::path& path,
+         dht::crypto::Identity identity,
+         const std::string& bootstrap,
+         const std::string& turn_host,
+         const std::string& turn_user,
+         const std::string& turn_pass,
+         const std::string& turn_realm,
+         const std::string& configuration_file);
+    ~Dvpn();
+    void run();
+
+    std::unique_ptr<ConnectionManager> connectionManager;
+    std::shared_ptr<Logger> logger;
+    std::shared_ptr<tls::CertificateStore> certStore;
+    std::shared_ptr<IceTransportFactory> iceFactory;
+    std::shared_ptr<asio::io_context> ioContext;
+    std::thread ioContextRunner;
+    enum class CommunicationState { METADATA, DATA };
+
+};
+
+class DvpnServer : public Dvpn
+{
+public:
+    // Build a server
+    DvpnServer(const std::filesystem::path& path,
+               dht::crypto::Identity identity,
+               const std::string& bootstrap,
+               const std::string& turn_host,
+               const std::string& turn_user,
+               const std::string& turn_pass,
+               const std::string& turn_realm,
+               const std::string& configuration_file);
+};
+
+class DvpnClient : public Dvpn
+{
+public:
+    // Build a client
+    DvpnClient(dht::InfoHash peer_id,
+               const std::filesystem::path& path,
+               dht::crypto::Identity identity,
+               const std::string& bootstrap,
+               const std::string& turn_host,
+               const std::string& turn_user,
+               const std::string& turn_pass,
+               const std::string& turn_realm,
+               const std::string& configuration_file);
+
+private:
+    msgpack::unpacker pac_ {};
+    CommunicationState connection_state = CommunicationState::METADATA;
+    int tun_fd;
+    char tun_device[IFNAMSIZ] = {0}; // IFNAMSIZ is typically the maximum size for interface names
+    std::shared_ptr<asio::posix::stream_descriptor> tun_stream;
+
+};
+
+} // namespace dhtnet
\ No newline at end of file
diff --git a/tools/dvpn/dvpn_up.sh b/tools/dvpn/dvpn_up.sh
new file mode 100755
index 0000000..e977362
--- /dev/null
+++ b/tools/dvpn/dvpn_up.sh
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+# peer-to-peer tun address
+ptp_address="$1"
+# TUN interface address
+tun_address="$2"
+# TUN interface name
+tun="$3"
+# VPN server address (remote peer public address)
+server="$4"
+# Check if the peer is a vpn client
+[ "$5" = "true" ] && is_client=true || is_client=false
+# TUN interface address ipv6
+tun_address_ipv6="$6"
+# peer-to-peer tun address ipv6
+ptp_address_ipv6="$7"
+
+# Function to set up routes
+setup_route() {
+    # Get the gateway address of the default route
+    gw=$(ip route show default | awk '/default/ {print $3; exit}')
+
+    existing_route=$(ip route show "$server" | awk '/via/ { print $3 }')
+    echo "existing_route "$existing_route""
+
+    if [ "$existing_route" = "$gw" ]; then
+        echo "Route to $server via $gw already exists."
+    else
+        ip route del "$server" &> /dev/null
+        ip route add "$server" via "$gw" metric 50 || echo "Failed to add route to $server via $gw."
+        echo "Route to $server via $gw added."
+    fi
+
+    ip route add default dev "$tun" metric 50
+    ip -6 route add default dev "$tun" metric 50
+}
+
+
+# Function to set up NAT
+setup_nat() {
+    sysctl -w net.ipv4.ip_forward=1
+    # enable ipv6 forwarding
+    sysctl -w net.ipv6.conf.all.forwarding=1
+    public_interface=$(ip route | awk '/default/{print $5}')
+
+    # Check if the NAT rule already exists
+    iptables -C -t nat -A POSTROUTING -o "$public_interface" -j MASQUERADE || iptables -t nat -A POSTROUTING -o "$public_interface" -j MASQUERADE
+
+    # Allow traffic from the private network to the public network
+    iptables -A FORWARD -i "$tun" -o "$public_interface" -m state --state RELATED,ESTABLISHED -j ACCEPT
+    iptables -A FORWARD -i "$public_interface" -o "$tun" -j ACCEPT
+
+    public_interface_ipv6=$(ip -6 route | awk '/default/{print $5}')
+
+    # Check if the default interface ipv6 is the same as the default interface ipv4
+    if [ -n "$public_interface_ipv6" ] && [ "$public_interface" != "$public_interface_ipv6" ]; then
+        # Check if the NAT rule already exists
+        iptables -C -t nat -A POSTROUTING -o "$public_interface_ipv6" -j MASQUERADE || iptables -t nat -A POSTROUTING -o "$public_interface_ipv6" -j MASQUERADE
+
+        # Allow traffic from the private network to the public network
+        iptables -A FORWARD -i "$tun" -o "$public_interface_ipv6" -m state --state RELATED,ESTABLISHED -j ACCEPT
+        iptables -A FORWARD -i "$public_interface_ipv6" -o "$tun" -j ACCEPT
+    fi
+}
+
+# Configure TUN interface IP address, mask, and peer-to-peer address
+ip -6 addr add "$tun_address_ipv6" remote "$ptp_address_ipv6" dev "$tun"
+ip addr add "$tun_address" remote "$ptp_address" dev "$tun"
+
+# Bring up the TUN interface
+ip link set dev "$tun" up
+# Check if TUN interface is up
+if ip addr show "$tun"; then
+    echo "TUN interface $tun is up."
+else
+    echo "TUN interface $tun is not up."
+fi
+if $is_client; then
+    # For client: set up routes
+    setup_route
+else
+    # For server: set up NAT
+    setup_nat
+fi
\ No newline at end of file
diff --git a/tools/dvpn/main.cpp b/tools/dvpn/main.cpp
new file mode 100644
index 0000000..66f8e13
--- /dev/null
+++ b/tools/dvpn/main.cpp
@@ -0,0 +1,205 @@
+/*
+ *  Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#include "dvpn.h"
+#include "common.h"
+
+#include <string>
+#include <vector>
+#include <iostream>
+#include <unistd.h>
+#include <getopt.h>
+#if __has_include(<fmt/std.h>)
+#include <fmt/std.h>
+#else
+#include <fmt/ostream.h>
+#endif
+#include <netinet/in.h>
+
+struct dhtvpn_params
+{
+    bool help {false};
+    bool version {false};
+    bool listen {false};
+    std::filesystem::path path {};
+    std::string bootstrap {};
+    dht::InfoHash peer_id {};
+    std::string turn_host {};
+    std::string turn_user {};
+    std::string turn_pass {};
+    std::string turn_realm {};
+    std::string configuration_file {};
+};
+
+static const constexpr struct option long_options[]
+    = {{"help", no_argument, nullptr, 'h'},
+       {"version", no_argument, nullptr, 'v'},
+       {"listen", no_argument, nullptr, 'l'},
+       {"bootstrap", required_argument, nullptr, 'b'},
+       {"id_path", required_argument, nullptr, 'I'},
+       {"turn_host", required_argument, nullptr, 't'},
+       {"turn_user", required_argument, nullptr, 'u'},
+       {"turn_pass", required_argument, nullptr, 'w'},
+       {"turn_realm", required_argument, nullptr, 'r'},
+       {"configuration_file", required_argument, nullptr, 'c'},
+       {nullptr, 0, nullptr, 0}};
+
+dhtvpn_params
+parse_args(int argc, char** argv)
+{
+    dhtvpn_params params;
+    int opt;
+    while ((opt = getopt_long(argc, argv, "hvlw:r:u:t:I:b:c:", long_options, nullptr)) != -1) {
+        switch (opt) {
+        case 'h':
+            params.help = true;
+            break;
+        case 'v':
+            params.version = true;
+            break;
+        case 'l':
+            params.listen = true;
+            break;
+        case 'b':
+            params.bootstrap = optarg;
+            break;
+        case 'I':
+            params.path = optarg;
+            break;
+        case 't':
+            params.turn_host = optarg;
+            break;
+        case 'u':
+            params.turn_user = optarg;
+            break;
+        case 'w':
+            params.turn_pass = optarg;
+            break;
+        case 'r':
+            params.turn_realm = optarg;
+            break;
+        case 'c':
+            params.configuration_file = optarg;
+            break;
+        default:
+            std::cerr << "Invalid option" << std::endl;
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    // If not listening, the peer_id argument is required
+    if (!params.listen && !params.help && !params.version) {
+        if (optind < argc) {
+            params.peer_id = dht::InfoHash(argv[optind]);
+            optind++; // Move to the next argument
+        } else {
+            std::cerr << "Error: Missing peer_id argument.\n";
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    // default values
+
+    if (params.bootstrap.empty())
+        params.bootstrap = "bootstrap.jami.net";
+    if (params.path.empty())
+        params.path = std::filesystem::path(getenv("HOME")) / ".dhtnet";
+    if (params.turn_host.empty())
+        params.turn_host = "turn.jami.net";
+    if (params.turn_user.empty())
+        params.turn_user = "ring";
+    if (params.turn_pass.empty())
+        params.turn_pass = "ring";
+    if (params.turn_realm.empty())
+        params.turn_realm = "ring";
+    if (params.configuration_file.empty())
+        params.configuration_file = std::filesystem::path(__FILE__).parent_path()/"test_config.yaml";
+
+    return params;
+}
+
+static void
+setSipLogLevel()
+{
+    int level = 0;
+    if (char* envvar = getenv("SIPLOGLEVEL")) {
+        // From 0 (min) to 6 (max)
+        level = std::clamp(std::stoi(envvar), 0, 6);
+    }
+
+    pj_log_set_level(level);
+    pj_log_set_log_func([](int level, const char* data, int len) {
+        fmt::print("{}", std::string_view(data, len));
+    });
+}
+
+int
+main(int argc, char** argv)
+{
+    setSipLogLevel();
+    auto params = parse_args(argc, argv);
+
+    if (params.help) {
+        fmt::print(
+            "Usage: dvpn [options] [PEER_ID]\n"
+            "\nOptions:\n"
+            "  -h, --help            Show this help message and exit.\n"
+            "  -v, --version         Display the program version.\n"
+            "  -l, --listen          Start the program in listen mode.\n"
+            "  -b, --bootstrap       Specify the bootstrap option with an argument.\n"
+            "  -I, --id_path         Specify the id_path option with an argument.\n"
+            "  -t, --turn_host       Specify the turn_host option with an argument.\n"
+            "  -u, --turn_user       Specify the turn_user option with an argument.\n"
+            "  -w, --turn_pass       Specify the turn_pass option with an argument.\n"
+            "  -r, --turn_realm      Specify the turn_realm option with an argument.\n"
+            "  -c, --configuration_file Specify the configuration_file path option with an argument.\n"
+            "\n");
+        return EXIT_SUCCESS;
+    }
+    if (params.version) {
+        fmt::print("dvpn v1.0\n");
+        return EXIT_SUCCESS;
+    }
+
+    fmt::print("dvpn 1.0\n");
+    auto identity = dhtnet::loadIdentity(params.path);
+    fmt::print("Loaded identity: {} from {}\n", identity.second->getId(), params.path);
+
+    std::unique_ptr<dhtnet::Dvpn> dvpn;
+    if (params.listen) {
+        // create dvpn instance
+        dvpn = std::make_unique<dhtnet::DvpnServer>(params.path,
+                                                    identity,
+                                                    params.bootstrap,
+                                                    params.turn_host,
+                                                    params.turn_user,
+                                                    params.turn_pass,
+                                                    params.turn_realm,
+                                                    params.configuration_file);
+    } else {
+        dvpn = std::make_unique<dhtnet::DvpnClient>(params.peer_id,
+                                                    params.path,
+                                                    identity,
+                                                    params.bootstrap,
+                                                    params.turn_host,
+                                                    params.turn_user,
+                                                    params.turn_pass,
+                                                    params.turn_realm,
+                                                    params.configuration_file);
+    }
+    dvpn->run();
+    return EXIT_SUCCESS;
+}
diff --git a/tools/dvpn/test_config.yaml b/tools/dvpn/test_config.yaml
new file mode 100644
index 0000000..6b7c513
--- /dev/null
+++ b/tools/dvpn/test_config.yaml
@@ -0,0 +1,5 @@
+script_path: "/path/to/home/dhtnet/tools/dvpn/dvpn_up.sh"
+ip_address: "10.80.77."
+ip_peer_address: "10.60.78."
+ip_address_ipv6: "fd00:0:0:1::"
+ip_peer_address_ipv6: "fd00:0:0:2::"
\ No newline at end of file