Libtelio (pronounced 'lɪbtælɪɔ') is a client-side library for creating encrypted networks (called Meshnet) on the user's nodes. It supports a number of features, including:
- adapting different Wireguard implementations to secure the connections,
- exit nodes which might be either VPN servers or some nodes in the Meshnet,
- "DNS" which allows name resolution in Meshnets for devices which does not support lookup tables,
- adjustable firewall.
In the latter sections of this README we assume (if it's not explicitly assumed otherwise)
that you are working on a Linux machine. To be able to build libtelio
it is enough to have rust installed.
To go through the short TCLI tutorial you need also docker.
You can build the libtelio library using standard cargo build command. Ensure to set the BYPASS_LLT_SECRETS environment variable to skip the LLT scan:
BYPASS_LLT_SECRETS=1 cargo buildVerify that GCC (GNU Compiler Collection) has been installed:
gcc --versionOtherwise run following command to install it:
sudo apt update
sudo apt install gccTo build libtelio on Windows with x86_64-pc-windows-msvc toolchain you need to install:
- Visual Studio 2019/2022
- Additional Visual Studio components: a. Desktop development with C++ b. Python 3 64-bit c. C++ Clang tools for Windows
- TDM-GCC 64-bit (https://jmeubank.github.io/tdm-gcc/download/)
- Go 1.19
Before running cargo build you need to set msvc environment. Examples for cmd and powershell in case of Visual Studio 2019 Community:
- In cmd.exe run:
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" x64- In powershell run:
Import-Module "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
Enter-VsDevShell -VsInstallPath "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community" -SkipAutomaticLocationVerify that you have cmake installed and visible in the path, for example:
cmake --version
cmake version 3.22.1Otherwise, run the following command to install it (or other appropriate for your system):
sudo apt update
sudo apt install cmakeVerify that you have a libclang installed on your system:
find /usr/lib -name "libclang*\.so"Otherwise, run the following command to install it (or other appropriate for your system):
sudo apt update
sudo apt install libclang-devVerify that protoc (Protocol buffers compiler has been installed in correct version (at least 3.21.12):
protoc --versionOtherwise, run the following command to install it (or other appropriate for your system):
sudo apt update
sudo apt install protobuf-compiler-grpctcli is a simple shell created to test and discover the libtelio library capabilities.
Let's see how to use it to create a simple mesh connection between two docker containers.
First of all, build tcli utility:
cargo build -p tcliYou will need a lightweight Linux docker image with the some networking utilities,
which are missing from the basic Ubuntu image, so let's create a new one.
Make a docker directory in tcli-test:
mkdir -p tcli-test/docker
cd tcli-test/dockerand put there the following simple Dockerfile:
FROM ubuntu
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y iproute2 iputils-ping tcpdump ca-certificatesThen build it and tag it as tcli-test, running the following command from the docker directory:
docker build -t tcli-test .When the image is built, you need to run two copies of it, one for t1:
docker run -itd -v <path to libtelio top-level-directory>:/hostfs --name=t1 --hostname=t1 --privileged tcli-test bashand a second one for t2:
docker run -itd -v <path to libtelio top-level-directory>:/hostfs --name=t2 --hostname=t2 --privileged tcli-test bashYou need to prepare four terminals. In two of them (we will refer to them as T1a and T1b)
run the following command to connect to the container t1:
docker exec -it t1 bashand then run an analogous command for t2 in another two (T2a and T2b):
docker exec -it t2 bashIn the following steps, you will need a token for your NordVPN account
(if you don't have an account, you need to create one),
which you can generate from https://my.nordaccount.com/dashboard/nordvpn/.
In terminals T1a and T2a run tcli:
hostfs/target/debug/tcliand in both of the opened tclis:
login token <NORDVPN_TOKEN>
mesh on <NAME>where <NORDVPN_TOKEN> is the token generated for your NordVPN account and
<NAME> is t1 for T1a and t2 for T2a.
There will be a large JSON config printed - the IP address can be found in
the list "ip_addresses":
>>> mesh on t1
- registered new device.
- got config:
{"identifier":"...","public_key":"...","hostname":"...","os":"linux","os_version":"linux tcli",<span style="color:red">"ip_addresses":["..."]</span>,"traffic_routing_supported":false,"endpoints":["..."],...When you find it set it in your bash terminals
(set the one found in config in T1a in T1b and the one from T2a in T2b):
ip addr add <IP_ADDRESS>/10 dev <NAME>
ip -6 addr add <IPv6_ADDRESS>/64 dev <NAME>
ip link set up dev <NAME>
ip link set dev <NAME> mtu 1420Note: for meshnet to work, you do not need both IPv4 and IPv6 addresses to be set. Only one of them should be enough.
Currently, there is one more issue to overcome: because node t1 was connected
earlier, it doesn't have the information about node t2.
Run mesh on t1 in T1a to fix it.
The containers should be now connected by the mesh, so to try the connection,
run ping in T1b and tcpdump in T2b and see how the packages are flowing.
To run tcli client on native macOS use utun name for interface name instead of t1/t2. Use unique index for utun since there might be some already present.
>>> login token <NORDVPN_TOKEN>
>>> mesh on utun10Find meshnet ip address from "ip_addresses" field the same as in linux case. Then
ifconfig utun10 add <IP_ADDRESS>/10 <IP_ADDRESS>
ifconfig inet6 utun10 add <IPv6_ADDRESS> prefixlen 64
ifconfig utun10 mtu 1420
route add 100.64/10 <IP_ADDRESS>
route add -inet6 fd74:656c:696f::/64 <IPv6_ADDRESS>Or simply run:
>>> mesh set-ipThe main component of the Libtelio library is the telio::Device structure and its methods.
Let's go through the most important ones.
The first of them is the expected function ::new.
It takes three arguments, let's describe them briefly:
features- tells Libtelio which of the additional features should be enabled - the description of them is out of the scope of this READMEevent_cb- event handler, takes aBox<Event>, which will be called by Libtelio to handle events of three types:Error- an error occurs, especially urgent are Critical error events, which means that the library is unable to continue running and it requires a call tohard_resetmethodRelay- when the relay (i.e. Derp) server configuration is changed, contains a JSON with a new oneNode- appears when the Meshnet node's configuration is changed, it contains a JSON with a new one
protect- callback for excluding connections from VPN tunnel (currently used only for android).
telio::Device implements Drop trait, so we don't need to worry
about deinitialization in the end.
Let's look at an example initialization of telio::Device with no additional
features, handling only Node events and not using protect callback:
let (sender, receiver) = mpsc::channel::<Box<Event>>();
let mut device = telio::device::Device::new(
Features::default(),
move |e| {
sender.send(e).unwrap();
},
None,
).unwrap();
...
loop {
let event = receiver.recv().unwrap();
match *event {
Event::Node { body: Some(b) } => println!(
"event node: {:?}:{}; Path = {:?}",
b.state.unwrap(),
b.public_key,
b.path
),
_ => {}
};
}Once the device is initialized we can start it, so it will create a new network
interface in your OS. This might be done using start method.
We need to provide an instance of the DeviceConfig structure:
pub struct DeviceConfig {
pub private_key: SecretKey,
pub adapter: AdapterType,
pub name: Option<String>,
pub tun: Option<Tun>,
}Let's discuss its fields shortly:
private_keyatelio::crypto::SecretKeyinstance containing a 256-bit key,adapterindicating which Wireguard implementation we want to use,nameis the name of the network interface, when omitted, Telio uses the default one,tuna file descriptor of the already opened tunnel, if it's not provided Telio will open a new one.
The API provides a default config which is almost sufficient for simple cases, the only need that needs to be done is the generation of a private key:
let config = DeviceConfig {
private_key: SecretKey::gen(),
..Default::default()
};
device.start(&config).unwrap();To stop the device we can simply call the argumentless Device::stop method.
To turn on the Meshnet feature, you need to call the Device::set_config method
with the proper config. After logging in and registering to the desired
Derp server, the JSON with config may be downloaded from it.
In the case of using the Derp server provided by Nord, you can use a token (like the one
used in the tcli setup) passing it with a user HTTP header (in a form token:<TOKEN>),
to receive the necessary credentials from
https://api.nord.com/v1/users/services/credentials
which will return you another token, with which you may register to
https://api.nord.com/v1/meshnet/machines
passing the token in the authorization data: Bearer token:<TOKEN>.
You will be given an ID with which you can download the config from
https://api.nord.com/v1/meshnet/<ID>/
using the same bearer token.
After the device is started and the JSON config downloaded, we can
deserialize it and finally call set_config:
let config: Config = serde_json::from_str(&serialized_config).unwrap();
self.telio.set_config(&Some(config)).unwrap();To turn the Meshnet off, we need to just call Device::set_config with None.
The other thing you may do with the started device is to set up an exit node. It might be either one of the nodes in your internal network, or some VPN server compatible with Wireguard. To connect to them, we need their public key and endpoint.
If we want to use the NordVPN server, we can obtain them from the JSON
downloaded from
https://api.nordvpn.com/v1/servers/recommendations.
The received JSON is rather big, so to get the needed fields
you can extract the telio::tcli::nord::find_server method and simplify
it a bit to just return a pair of public_key and endpoint.
When you have it, setting up the VPN connection is fairly simple:
let (public_key, endpoint) = find_server();
let exit_node = ExitNode {
identifier: "fa5bbe9b-338b-4bd2-8c97-166ceee65790".to_owned(),
public_key,
allowed_ips: Some(vec![IpNet::new(
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
0,
)
.unwrap()]),
endpoint: Some(endpoint),
};
device.connect_exit_node(&exit_node);For now, unit tests and integration tests are supported on Linux. All tests run on CI for every merge request. Code can't be merged unless builds and tests pass.
Unit tests ensure internal components are working fine. Unit tests probably also pass on MacOS.
cargo testFor information about how to release, please see releasing.md.
Documentation for Libtelio is currently built and deployed to GitHub Pages by manually triggering the build-and-deploy-docs job on the pipeline in GitHub Actions. If you need to build the rustdocs locally, reference the gh-pages CI file and don't forget to set the RUSTDOCFLAGS env variable accordingly.
For information about how to contribute, please see CONTRIBUTING.md.
This project is licensed under the terms of the GNU General Public License v3.0 only