Skip to content

Commit

Permalink
Add dtcli
Browse files Browse the repository at this point in the history
  • Loading branch information
ThrasherLT committed May 27, 2024
1 parent ba19683 commit 2acfe91
Show file tree
Hide file tree
Showing 16 changed files with 1,142 additions and 145 deletions.
Empty file added .unreleased/LLT-4746
Empty file.
275 changes: 186 additions & 89 deletions Cargo.lock

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions clis/tcli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2018"
license = "GPL-3.0-only"
repository = "https://github.com/NordSecurity/libtelio"
default-run = "tcli"

[dependencies]
dirs = "4.0.0"
Expand All @@ -15,13 +16,20 @@ reqwest = { version = "0.11.16", default-features = false, features = [
] }
rustyline = "11.0.0"
shellwords = "1.1.0"
# Used only for checking if the daemon is running.
sysinfo = "0.30.11"
# Used as a lightweight and safe (because a TCP server has the risk of remote code execution)
# way for the API and daemon to communicate.
# Tokio support is needed, because the daemon runs on the async runtime.
interprocess = { version = "1.2.1" }

anyhow.workspace = true
base64.workspace = true
clap.workspace = true
crypto_box.workspace = true
hex.workspace = true
ipnetwork.workspace = true
futures.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
tracing-appender.workspace = true
Expand All @@ -45,3 +53,8 @@ telio-task.workspace = true
telio-traversal.workspace = true
telio-utils.workspace = true
telio-wg.workspace = true

[target.'cfg(unix)'.dependencies]
# Used to avoid the manual work of daemonizing the libtelio process.
# Also is the only reason why Windows isn't supported.
daemonize = "0.5.0"
67 changes: 67 additions & 0 deletions clis/tcli/contrib/example_vpn_disable_linux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/bin/bash

# This is a simple example script on how to use TCLID to turn off VPN and clean up routing.

# NOTE: This is meant to be run from the libtelio directory.
# And uses the debug build so that you could iterate quicker builds when debugging.

TCLID_PATH="target/debug/tclid"

# This script takes a single argument which is the name of the tunnel that Telio is using.
if [ $# -eq 0 ]; then
echo "No tunnel name provided, using tun10 as default."
TUNNEL_NAME="tun10"
else
TUNNEL_NAME=$1
fi

# Only Linux is supported at the moment.
if [[ $(uname) != "Linux" ]]; then
echo "This script is only compatible with Linux."
exit 126
fi

# Check if the script is being run as root to precent half run scripts.
if [ "$EUID" -ne 0 ]; then
echo "ERROR: This script must be run with root privileges."
exit 13
fi

if ! [ -f "$TCLID_PATH" ]; then
echo "Cannot find TCLID in $TCLID_PATH directory. Please build TCLID and run it from libtelio root."
exit 127
fi

# Check if the interface exists.
if ! ip link show | grep -q "$TUNNEL_NAME"; then
echo "Tunnel $TUNNEL_NAME does not exist. Enable VPN before disabling it again."
exit 1
fi

# Ensure ip is installed.
if ! command -v ip &> /dev/null; then
echo "iproute2 is required but not found. Please install it."
exit 127
fi

# Save current external IP to verif that it had changed later.
old_public_ip=$(curl -s ifconfig.me)
echo "External IP before enabling VPN: ${old_public_ip}"

# Stop WireGuard.
eval ${TCLID_PATH} dev stop
# Stop libtelio daemon.
eval ${TCLID_PATH} quit

# Delete VPN route.
# Routing into tunnel and routing from tunnel to VPN server will be deleted automatically when stopping Wireguard.
ip rule del not from all fwmark 11673110 lookup 73110

# Check if the public IP really changed.
new_public_ip=$(curl -s ifconfig.me)
if [ "$new_public_ip" == "$old_public_ip" ]; then
echo "Error: Public IP had not been changed!"
exit 1
fi

echo "Success: External IP after enabling VPN: ${new_public_ip}, you are no longer connected over a VPN."
117 changes: 117 additions & 0 deletions clis/tcli/contrib/example_vpn_enable_linux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/bin/bash

# This is a simple example script on how to use TCLID to set up VPN.

# NOTE: This is meant to be run from the libtelio directory.
# Also it assumes that your nord token is in the $NORD_TOKEN variable. So also make sure to run it with `sudo -E`.
# And uses the debug build so that you could iterate quicker builds when debugging.

# This script takes a single argument which is the name of the tunnel for Telio to use.
if [ $# -eq 0 ]; then
echo "No tunnel name provided, using tun10 as default."
TUNNEL_NAME="tun10"
else
TUNNEL_NAME=$1
fi

TCLID_PATH="target/debug/tclid"

# Below are a few checks to make sure that the environment is correct for this script to run and not mess up the network configuration:
# If it does mess up the network configuration, you can just restart your PC ;)

# Only Linux is supported at the moment.
if [[ $(uname) != "Linux" ]]; then
echo "This script is only compatible with Linux."
exit 126
fi

# Ensure curl is installed.
if ! command -v curl &> /dev/null; then
echo "curl is required but not found. Please install it."
exit 127
fi

# Ensure jq is installed.
if ! command -v jq &> /dev/null; then
echo "jq is required but not found. Please install it."
exit 127
fi

# Ensure ip is installed.
if ! command -v ip &> /dev/null; then
echo "iproute2 is required but not found. Please install it."
exit 127
fi

if ! [ -f "$TCLID_PATH" ]; then
echo "Cannot find TCLID in $TCLID_PATH directory. Please build TCLID and run it from libtelio root."
exit 127
fi

# Function to validate NORD_TOKEN
validate_nord_token() {
local token="$1"
if [[ ! "$token" =~ ^[[:alnum:]]{64}$ ]]; then
echo "Error: Invalid NORD_TOKEN. It must be a 64-character-long alphanumeric string."
return 1
fi
}

# Check if the script is being run as root to precent half run scripts.
if [ "$EUID" -ne 0 ]; then
echo "ERROR: This script must be run with root privileges."
exit 13
fi

# Check if the NORD_TOKEN variable is set
if [ -z "$NORD_TOKEN" ]; then
echo "NORD_TOKEN variable is not set."
read -p "Please enter your NORD_TOKEN or run this script with \`sudo -E\`: " NORD_TOKEN
fi

# Validate the entered NORD_TOKEN
if ! validate_nord_token "$NORD_TOKEN"; then
exit 1
fi

# Below is the actual libtelio usage:

# Check if the interface already exists.
if ip link show | grep -q "$TUNNEL_NAME"; then
echo "Tunnel $TUNNEL_NAME already exists. Disable VPN before enabling it again."
exit 1
fi

# Save current external IP to verif that it had changed later.
old_public_ip=$(curl -s ifconfig.me)
echo "External IP before enabling VPN: ${old_public_ip}"

# Starting WireGuard.
eval ${TCLID_PATH} dev start boringtun ${TUNNEL_NAME}

# Getting VPN server IP and public key.
recommended_servers_list=$(curl -s "https://api.nordvpn.com/v1/servers/recommendations?&filters\[servers_technologies\]\[identifier\]=wireguard_udp&limit=1" -u token:$NORD_TOKEN)
recommended_server_public_key=$(echo ${recommended_servers_list} | jq --raw-output '.[].technologies[]|select(.identifier == "wireguard_udp")|.metadata[].value')
recommended_server_ip=$(echo ${recommended_servers_list} | jq --raw-output '.[].station')

# Configuring interface.
ip addr add dev ${TUNNEL_NAME} 10.5.0.2/10
ip link set up dev ${TUNNEL_NAME}
ip link set dev ${TUNNEL_NAME} mtu 1420

# Connecting to VPN.
eval ${TCLID_PATH} dev con ${recommended_server_public_key} ${recommended_server_ip}:51820

# Creating VPN route.
ip route add default dev ${TUNNEL_NAME} table 73110
ip route add ${recommended_server_ip} dev ${TUNNEL_NAME} table 73110
ip rule add not from all fwmark 11673110 lookup 73110

# Check if the public IP really changed.
new_public_ip=$(curl -s ifconfig.me)
if [ "$new_public_ip" == "$old_public_ip" ]; then
echo "Error: Public IP had not been changed!"
exit 1
fi

echo "Success: External IP after enabling VPN: ${new_public_ip}, you are now connected over a VPN."
19 changes: 15 additions & 4 deletions clis/tcli/src/main.rs → clis/tcli/src/bin/tcli.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
#![allow(unwrap_check)]

mod cli;
mod derp;
mod nord;
use std::{fs, io::Write, sync::Arc, time::SystemTime};

use anyhow::{anyhow, Result};
use clap::Parser;
use dirs::home_dir;
use parking_lot::Mutex;
use regex::Regex;
use std::{io::Write, sync::Arc, time::SystemTime};
use tracing::level_filters::LevelFilter;

use telio_model::{config::Server, event::Event as DevEvent, features::Features};

use tcli::cli;

#[derive(Parser)]
struct Args {
/// Pass [Features] in json format. Configure optional features.
Expand All @@ -24,6 +25,16 @@ struct Args {
fn main() -> Result<()> {
let args = Args::parse();

let (non_blocking_writer, _tracing_worker_guard) =
tracing_appender::non_blocking(fs::File::create("tcli.log")?);
tracing_subscriber::fmt()
.with_max_level(LevelFilter::TRACE)
.with_writer(non_blocking_writer)
.with_ansi(false)
.with_line_number(true)
.with_level(true)
.init();

let token = std::env::var("NORD_TOKEN").ok();

let features: Features = args
Expand Down
Loading

0 comments on commit 2acfe91

Please sign in to comment.