Pull request 574: TRUST-228 lint part 1

Squashed commit of the following:

commit 3e269033f16e4672860c0fe24cbe8df4b09065f4
Author: Sergey Fionov <sfionov@adguard.com>
Date:   Wed Dec 24 20:09:14 2025 +0300

    Applied suggestion

commit 14468d33f3ae87accce3a2f2a5c31744a13ac70c
Merge: e359993e 305f5b9a
Author: Andrey Meshkov <am@adguard.com>
Date:   Wed Dec 24 20:06:20 2025 +0300

    Merge with master

commit e359993e58219942ed5515e54177a8c0250d5efe
Author: Andrey Meshkov <am@adguard.com>
Date:   Wed Dec 24 19:59:48 2025 +0300

    Fixed python issue

commit 652c8481c7707cf8f8ef7743009fafd7597b280c
Author: Andrey Meshkov <am@adguard.com>
Date:   Wed Dec 24 19:47:57 2025 +0300

    clang-format

commit d43bf98b664a1136eb28c93304c0fe6b6bf1f247
Author: Andrey Meshkov <am@adguard.com>
Date:   Wed Dec 24 19:47:47 2025 +0300

    Added lint targets
This commit is contained in:
Andrey Meshkov
2025-12-24 20:24:10 +03:00
parent 305f5b9af7
commit 7e2ce27f21
48 changed files with 981 additions and 664 deletions
+1 -1
View File
@@ -13,7 +13,7 @@
},
"no-inline-html": {
"allowed_elements": [
"a"
"a", "p", "picture", "source", "img"
]
},
"no-trailing-spaces": {
+3
View File
@@ -0,0 +1,3 @@
third-party
fastlane
env
+50 -50
View File
@@ -2,159 +2,159 @@
## 0.99.63
* [Features] Add option to allow inbound connections to the specified UDP/TCP ports when
- [Features] Add option to allow inbound connections to the specified UDP/TCP ports when
`ag::VpnWinTunnelSettings::block_untunneled` is enabled.
* See `ag::VpnWinTunnelSettings::block_untunneled_exclude_ports`.
- See `ag::VpnWinTunnelSettings::block_untunneled_exclude_ports`.
## 0.95.31
* [Feature] IPv6 support must now be explicitly specified on each `VpnEndpoint`. Previously, the library assumed
- [Feature] IPv6 support must now be explicitly specified on each `VpnEndpoint`. Previously, the library assumed
that all endpoints in a location have IPv6 support if any of the endpoints in a location had an IPv6 address.
* See `VpnEndpoint::has_ipv6`.
- See `VpnEndpoint::has_ipv6`.
## 0.94.7
* [Feature] Add an option to use a post-quantum group for key exchange in TLS handshakes.
* See `vpn_post_quantum_group_set_enabled`.
- [Feature] Add an option to use a post-quantum group for key exchange in TLS handshakes.
- See `vpn_post_quantum_group_set_enabled`.
## 0.93.28
* Handler profiling is now disabled by default.
- Handler profiling is now disabled by default.
## 0.93.18
* [Feature] Add an option to profile VPN handler execution: if enabled, a warning will be written to the log
- [Feature] Add an option to profile VPN handler execution: if enabled, a warning will be written to the log
whenever a handler call is taking too long. Profiling is enabled by default. Applications might want to disable
it when running in production.
* See `ag::vpn_handler_profiling_set_enabled`.
- See `ag::vpn_handler_profiling_set_enabled`.
## 0.92.182
* [Feature] The library now notify an application with information about connection. This event contain info
- [Feature] The library now notify an application with information about connection. This event contain info
about source ip, destination (ip, domain or both), transport protocol and action (bypass/tunnel).
For this purpose, new event `VPN_EVENT_CONNECTION_INFO` was introduced in `VpnEvent`.
## 0.92.115
* Added a new `VpnConnectAction`: `VPN_CA_REJECT`.
- Added a new `VpnConnectAction`: `VPN_CA_REJECT`.
## 0.92.107
* Added `VpnConnectedInfo::relay_address`.
- Added `VpnConnectedInfo::relay_address`.
## 0.92.100
* Added `VpnEndpoint::remote_id`. See the field's doc for details.
- Added `VpnEndpoint::remote_id`. See the field's doc for details.
## 0.92.94
* Changes in pinging behaviour.
* See the updated doc comments for the fields of `LocationsPingerInfo` and `PingInfo`, `locations_pinger_start`, `ping_start` for details.
* The pinging timeout is now per connection attempt, NOT for the whole pinging procedure. Applications may need to adjust.
- Changes in pinging behaviour.
- See the updated doc comments for the fields of `LocationsPingerInfo` and `PingInfo`, `locations_pinger_start`, `ping_start` for details.
- The pinging timeout is now per connection attempt, NOT for the whole pinging procedure. Applications may need to adjust.
## 0.92.90
* Changes in pinging and locations API.
* Removed `VpnUpstreamConfig::relay_addresses` and `LocationsPingerInfo::relay_address`.
* Added `VpnLocation::relay_addresses`.
* When a `VpnLocation` is used as part of a `VpnUpstreamConfig`, its relay addresses shall be used
- Changes in pinging and locations API.
- Removed `VpnUpstreamConfig::relay_addresses` and `LocationsPingerInfo::relay_address`.
- Added `VpnLocation::relay_addresses`.
- When a `VpnLocation` is used as part of a `VpnUpstreamConfig`, its relay addresses shall be used
exactly in the same manner as `VpnUpstreamConfig::relay_addresses` were used before.
* The documentation for `ag::locations_pinger_start` has been updated to include a note about how
- The documentation for `ag::locations_pinger_start` has been updated to include a note about how
the location's relay addresses are used by the pinger.
## 0.92.88
* [Feature] Support connecting to endpoints through a set of SNI proxies.
* `VpnUpstreamConfig::relay_addresses` can now be specified when connecting to a location.
- [Feature] Support connecting to endpoints through a set of SNI proxies.
- `VpnUpstreamConfig::relay_addresses` can now be specified when connecting to a location.
The client shall try using one of the relay addresses to connect to an endpoint if it's unavailable
on its normal address. The client shall automatically disqualify relay addresses that don't work.
* `LocationsPingerInfo::relay_address` can now be specified when pinging a location. The pinger shall try
- `LocationsPingerInfo::relay_address` can now be specified when pinging a location. The pinger shall try
to use it if an endpoint is unavailable on its normal address. `PingResult::through_relay` will be
non-zero if the relay is used. The application should try pinging with a different relay address if
there are pinging errors through the relay.
* `LocationsPinger` will now send ClientHello/QUIC Initial to "ping" the endpoints.
* `LocationsPinger::anti_dpi` can now be specified to enable or disable anti-DPI measures during pinging.
- `LocationsPinger` will now send ClientHello/QUIC Initial to "ping" the endpoints.
- `LocationsPinger::anti_dpi` can now be specified to enable or disable anti-DPI measures during pinging.
## 0.92.74
* [Feature] The library now notifies an application about the amount of traffic passed through
- [Feature] The library now notifies an application about the amount of traffic passed through
connections that have been routed through an endpoint.
For this purpose, two events were introduced in `VpnEvent`:
* `VPN_EVENT_TUNNEL_CONNECTION_STATS` - raised only for connections that have been routed through
- `VPN_EVENT_TUNNEL_CONNECTION_STATS` - raised only for connections that have been routed through
an endpoint,
* `VPN_EVENT_TUNNEL_CONNECTION_CLOSED` - raised for any user connection.
- `VPN_EVENT_TUNNEL_CONNECTION_CLOSED` - raised for any user connection.
## 0.92.46
* [Feature] The library now accepts CIDR range in exclusion list.
- [Feature] The library now accepts CIDR range in exclusion list.
## 0.92.28
* Removed `vpn_network_manager_update_tun_interface_dns()` as redundant.
- Removed `vpn_network_manager_update_tun_interface_dns()` as redundant.
## 0.92.23
* The library now accepts a list of DNS upstreams instead of a single one.
* `dns_upsteam` field of `VpnListenerConfig` renamed to `dns_upsteams`.
* `dns_upsteam` field removed from `VpnDnsUpstreamUnavailableEvent`.
- The library now accepts a list of DNS upstreams instead of a single one.
- `dns_upsteam` field of `VpnListenerConfig` renamed to `dns_upsteams`.
- `dns_upsteam` field removed from `VpnDnsUpstreamUnavailableEvent`.
## 0.92.11
* [Feature] `h3://` scheme is now allowed for DNS upstream.
- [Feature] `h3://` scheme is now allowed for DNS upstream.
## 0.91.88
* DNS queries are now routed according to VPN settings. I.e., queries with domains
- DNS queries are now routed according to VPN settings. I.e., queries with domains
matching exclusions are routed directly to the target resolver in the general mode,
but queries not matching exclusions are routed through the endpoint. To do it correctly
the library needs to know the system (see `dns_manager_set_tunnel_interface_servers()`)
and TUN interface DNS servers (see `vpn_network_manager_update_tun_interface_dns()`).
* The library now has one centralized point for setting the outbound network
- The library now has one centralized point for setting the outbound network
interface for I/O operations - `vpn_network_manager_set_outbound_interface()`.
* [Windows] Use the method above instead of the removed `vpn_win_set_bound_if()`.
- [Windows] Use the method above instead of the removed `vpn_win_set_bound_if()`.
## 0.91.82
* [Fix] Introduced an error code indicating that no connection attempts left
- [Fix] Introduced an error code indicating that no connection attempts left
after initial connect() call `VPN_EC_INITIAL_CONNECT_FAILED`.
## 0.91.45
* [Changed] `Location unavailable` semantics:
* It is now considered as a fatal error, i.e. the client goes in the disconnected state.
- [Changed] `Location unavailable` semantics:
- It is now considered as a fatal error, i.e. the client goes in the disconnected state.
It is up to application to refresh a location data and restart the client.
* It is now raised only after the client receives the abandon command.
- It is now raised only after the client receives the abandon command.
It is up to application to detect infinite recovery loop in case there are some
connectivity issues.
* [Changed] [Windows] `vpn_abandon_endpoint()` now takes an endpoint as a parameter
- [Changed] [Windows] `vpn_abandon_endpoint()` now takes an endpoint as a parameter
## 0.91.20
* [Changed] [Windows] Calling `vpn_win_set_bound_if()` now turns off the socket protection instead of
- [Changed] [Windows] Calling `vpn_win_set_bound_if()` now turns off the socket protection instead of
detecting an active network interface by itself. It's up to the application to call the new method
`vpn_win_detect_active_if()` and pass its result to `vpn_win_set_bound_if()` to activate the socket
protection.
## 0.91.10
* [Feature] Added Wintun support. Dll downloaded from www.wintun.net is needed for standalone_client to run under Windows.
- [Feature] Added Wintun support. Dll downloaded from www.wintun.net is needed for standalone_client to run under Windows.
## 0.90.15
* [Feature] Route QUIC connections according to the VPN mode instead of always dropping or redirecting them
- [Feature] Route QUIC connections according to the VPN mode instead of always dropping or redirecting them
## 0.90.13
* [Fix] Fix leaks and memory bugs in `tls_serialize_cert_chain`, `tls_free_serialized_chain`. Add tests.
- [Fix] Fix leaks and memory bugs in `tls_serialize_cert_chain`, `tls_free_serialized_chain`. Add tests.
## 0.90.12
* [Fix] Change signature of some exported functions to be more consistent with the rest and simpler for C# bindings.
- [Fix] Change signature of some exported functions to be more consistent with the rest and simpler for C# bindings.
## 0.90.6
* [Fix] Fix version increment script.
- [Fix] Fix version increment script.
## 0.90.4
* [Feature] VpnLibs is now open-source.
- [Feature] VpnLibs is now open-source.
+92 -16
View File
@@ -14,15 +14,24 @@ ifeq ($(origin MSVC_YEAR), undefined)
endif
BUILD_DIR = build
EXPORT_DIR ?= bin
SETUP_WIZARD_DIR = trusttunnel/setup_wizard
.PHONY: init
## Initialize the development environment (git hooks, etc.)
init:
git config core.hooksPath ./scripts/hooks
.PHONY: bootstrap_deps
## Export all the required conan packages to the local cache
bootstrap_deps:
python3 -m venv env && \
. env/bin/activate && \
pip install -r requirements.txt && \
./scripts/bootstrap_conan_deps.py
.PHONY: build_libs
## Build the libraries
build_libs: bootstrap_deps
.PHONY: setup_cmake
## Setup CMake
setup_cmake: bootstrap_deps
ifeq ($(OS), Windows_NT)
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ^
-DCMAKE_C_FLAGS_DEBUG=/MT ^
@@ -32,12 +41,16 @@ ifeq ($(OS), Windows_NT)
else
mkdir -p $(BUILD_DIR) && cd $(BUILD_DIR) && \
cmake -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) \
-DCMAKE_C_COMPILER="clang" \
-DCMAKE_CXX_COMPILER="clang++" \
-DCMAKE_CXX_FLAGS="-stdlib=libc++" \
-GNinja \
..
-DCMAKE_C_COMPILER="clang" \
-DCMAKE_CXX_COMPILER="clang++" \
-DCMAKE_CXX_FLAGS="-stdlib=libc++" \
-GNinja \
..
endif
.PHONY: build_libs
## Build the libraries
build_libs: setup_cmake
cmake --build $(BUILD_DIR) --target vpnlibs_core
.PHONY: build_trusttunnel_client
@@ -62,15 +75,78 @@ build_and_export_bin: build_trusttunnel_client build_wizard
$(EXPORT_DIR)
@echo "Binaries are stored in $(EXPORT_DIR)"
.PHONY: lint-md
## Lint markdown files.
## `markdownlint-cli` should be installed:
## macOS: `brew install markdownlint-cli`
## Linux: `npm install -g markdownlint-cli`
lint-md:
markdownlint README.md trusttunnel/README.md
.PHONY: clean
## Clean the project
clean:
cmake --build $(BUILD_DIR) --target clean
.PHONY: lint
lint: lint-md lint-rust lint-cpp
## Lint c++ files.
## TODO: enable clang-tidy
.PHONY: lint-cpp
lint-cpp: clang-format
## Check c++ code formatting with clang-format.
.PHONY: clang-format
clang-format:
find . \
-path './third-party' -prune -o \
-name '*.c' -o -name '*.cpp' -o -name '*.h' \
-print | xargs clang-format -n -Werror
## Check c++ code formatting with clang-tidy.
.PHONY: clang-tidy
clang-tidy:
cmake -S . -B $(BUILD_DIR) -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
run-clang-tidy -p $(BUILD_DIR) '^(?!.*(/third-party/)).*\.cpp$$'
## Lint markdown files.
## `markdownlint-cli` should be installed:
## macOS: `brew install markdownlint-cli`
## Linux: `npm install -g markdownlint-cli`
.PHONY: lint-md
lint-md:
markdownlint .
## Check Rust code formatting with rustfmt.
## `rustfmt` should be installed:
## rustup component add rustfmt
.PHONY: lint-rust
lint-rust:
cargo fmt --all --manifest-path $(SETUP_WIZARD_DIR)/Cargo.toml -- --check
## Fix linter issues that are auto-fixable.
.PHONY: lint-fix
lint-fix: lint-fix-rust lint-fix-md lint-fix-cpp
## Auto-fix c++ formatting with clang-format.
.PHONY: lint-fix-cpp
lint-fix-cpp:
find . \
-path './third-party' -prune -o \
-name '*.c' -o -name '*.cpp' -o -name '*.h' \
-print | xargs clang-format -i
## Auto-fix Rust code formatting issues with rustfmt.
.PHONY: lint-fix-rust
lint-fix-rust:
cargo fmt --all --manifest-path $(SETUP_WIZARD_DIR)/Cargo.toml
## Auto-fix markdown files.
.PHONY: lint-fix-md
lint-fix-md:
markdownlint --fix .
.PHONY: test
test: test-cpp test-rust
.PHONY: test-cpp
test-cpp: build_libs
cmake --build $(BUILD_DIR) --target tests
ctest --test-dir $(BUILD_DIR)
.PHONY: test-rust
test-rust:
cargo test --workspace --manifest-path $(SETUP_WIZARD_DIR)/Cargo.toml
+11 -16
View File
@@ -1,3 +1,4 @@
<!-- markdownlint-disable MD041 -->
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.adguardcdn.com/website/github.com/TrustTunnel/logo_dark.svg" width="300px" alt="TrustTunnel" />
@@ -5,8 +6,6 @@
</picture>
</p>
# <p align="center">TrustTunnel Client</p>
<p align="center">Free, fast, open-source and secure client for the TrustTunnel VPN</p>
<p align="center"><a href="https://github.com/TrustTunnel/TrustTunnel">Endpoint</a>
@@ -85,6 +84,14 @@ Once you have obtained the exported endpoint configuration for the client, refer
- Windows (Chocolatey): `choco install cmake`
- LLVM 17 or higher
- macOS: `brew install llvm`
- Add `clang-format` and `clang-tidy` to PATH:
```shell
ln -s /opt/homebrew/opt/llvm/bin/clang-format /opt/homebrew/bin/clang-format
ln -s /opt/homebrew/opt/llvm/bin/clang-tidy /opt/homebrew/bin/clang-tidy
ln -s /opt/homebrew/opt/llvm/bin/run-clang-tidy /opt/homebrew/bin/run-clang-tidy
```
- Linux (Debian/Ubuntu): `apt install llvm clang libc++-dev`
- Windows (Chocolatey): `choco install llvm`
- Conan 2.0.5 or higher
@@ -113,21 +120,9 @@ Once you have obtained the exported endpoint configuration for the client, refer
### Building
#### For python externally managed environments
#### Prepare developer environment
You might get an error where Python will report some missing modules such as
```shell
ModuleNotFoundError: No module named 'yaml'
```
Please use following commands:
```shell
python3 -m venv env
source env/bin/activate
pip3 install -r ./scripts/requirements.txt
```
Run `make init` to prepare the developer environment and set up git hooks.
#### Using Makefile
+2 -2
View File
@@ -1,14 +1,14 @@
#pragma once
#include <cstdint>
#include <functional>
#include <optional>
#include <type_traits>
#include <utility>
#include <functional>
#ifdef __APPLE__
#include <sys/qos.h>
#include <TargetConditionals.h>
#include <sys/qos.h>
#endif // __APPLE__
#include <event2/event.h>
+7 -7
View File
@@ -29,7 +29,7 @@ struct VpnError {
typedef enum {
VPN_UP_HTTP2, // HTTP/2 only.
VPN_UP_HTTP3, // HTTP/3, with mandatory fallback to HTTP/2.
VPN_UP_AUTO, // VpnEndpoint: Try HTTP/2 and HTTP/3 simultaneously, use the first successful connection.
VPN_UP_AUTO, // VpnEndpoint: Try HTTP/2 and HTTP/3 simultaneously, use the first successful connection.
// VpnUpstreamConfig: Use endpoints' preferred protocol.
} VpnUpstreamProtocol;
@@ -59,8 +59,7 @@ struct TcpFlowCtrlInfo {
// May be owning or non-owning depending on context
typedef AG_ARRAY_OF(const char) VpnStr;
#define VPNSTR_INIT(c_string) \
{ c_string, (c_string) ? uint32_t(strlen(c_string)) : 0 }
#define VPNSTR_INIT(c_string) {c_string, (c_string) ? uint32_t(strlen(c_string)) : 0}
// QUIC defaults
static constexpr size_t QUIC_LOCAL_CONN_ID_LEN = 16;
@@ -135,8 +134,8 @@ class VpnPacketsHolder {
public:
VpnPacketsHolder() = default;
explicit VpnPacketsHolder(VpnPackets packets)
: m_packets(packets.data, packets.data + packets.size)
{}
: m_packets(packets.data, packets.data + packets.size) {
}
~VpnPacketsHolder() {
for (auto p : m_packets) {
if (p.destructor) {
@@ -164,6 +163,7 @@ public:
std::swap(m_packets, other.m_packets);
return *this;
}
private:
std::vector<VpnPacket> m_packets;
};
@@ -172,7 +172,7 @@ private:
* Convert milliseconds to timeval structure
*/
static inline struct timeval ms_to_timeval(uint32_t ms) {
struct timeval tv {}; // NOLINT(cppcoreguidelines-pro-type-member-init)
struct timeval tv{}; // NOLINT(cppcoreguidelines-pro-type-member-init)
tv.tv_sec = ms / 1000;
tv.tv_usec = (ms % 1000) * 1000;
return tv;
@@ -304,7 +304,7 @@ uint32_t ntoh_24(uint32_t x);
/**
* Just like `std::remove_if()`, but swaps elements to the tail instead of moving them
*/
template<typename Iterator, typename Predicate>
template <typename Iterator, typename Predicate>
Iterator swap_remove_if(Iterator begin, Iterator end, Predicate p) {
begin = std::find_if(begin, end, p);
if (begin != end) {
+5 -5
View File
@@ -8,11 +8,11 @@ import re
class VpnLibsConan(ConanFile):
name = "vpn-libs"
license = "GPL-3.0-or-later"
author = "AdguardTeam"
url = "https://github.com/AdguardTeam/VpnLibs"
vcs_url = "https://github.com/AdguardTeam/VpnLibs.git"
description = "A VPN client library that provides client network traffic tunnelling to an AdGuard VPN server"
license = "Apache-2.0"
author = "TrustTunnel"
url = "https://github.com/TrustTunnel/TrustTunnelClient"
vcs_url = "https://github.com/TrustTunnel/TrustTunnelClient.git"
description = "TrustTunnel client implementation"
settings = "os", "compiler", "build_type", "arch"
options = {
"with_ghc": [True, False],
+1 -1
View File
@@ -108,7 +108,7 @@ private:
std::string text;
MatchFlagsSet flags;
};
struct DomainEntryMalformed{};
struct DomainEntryMalformed {};
using ParseResult = std::variant<SocketAddress, CidrRange, DomainEntryInfo, DomainEntryMalformed>;
VpnMode m_mode = VPN_MODE_GENERAL;
+1 -1
View File
@@ -23,7 +23,7 @@ enum ServerEvent {
*/
SERVER_EVENT_READ, /**< Called when some data needs to be sent via connection (raised with `ServerReadEvent`) */
SERVER_EVENT_DATA_SENT, /**< Called when some data was sent to client (raised with `ServerDataSentEvent`) */
SERVER_EVENT_HEALTH_CHECK_ERROR, /**< Called when a health check result has failed (raised with `VpnError`) */
SERVER_EVENT_HEALTH_CHECK_ERROR, /**< Called when a health check result has failed (raised with `VpnError`) */
SERVER_EVENT_GET_AVAILABLE_TO_SEND, /**< Called when the upstream wants to know available size for sending (raised
with `ServerAvailableToSendEvent`) */
SERVER_EVENT_ERROR, /**< Called when some error happened on server side (raised with `ServerError`) */
+4 -7
View File
@@ -12,8 +12,8 @@
#include <magic_enum/magic_enum.hpp>
#include "common/net_utils.h"
#include "common/utils.h"
#include "common/socket_address.h"
#include "common/utils.h"
#include "net/tcp_socket.h"
#include "net/udp_socket.h"
#include "vpn/platform.h"
@@ -105,8 +105,7 @@ inline bool operator==(const TunnelAddressPair &lh, const TunnelAddressPair &rh)
if (lh.dst.index() != rh.dst.index()) {
return false;
}
if (const SocketAddress *ld = std::get_if<SocketAddress>(&lh.dst),
*rd = std::get_if<SocketAddress>(&rh.dst);
if (const SocketAddress *ld = std::get_if<SocketAddress>(&lh.dst), *rd = std::get_if<SocketAddress>(&rh.dst);
ld && rd) {
return *ld == *rd;
}
@@ -228,16 +227,14 @@ struct hash<ag::TunnelAddress> {
template <>
struct hash<ag::TunnelAddressPair> {
size_t operator()(const ag::TunnelAddressPair &addr) const {
return size_t(
ag::hash_pair_combine(ag::socket_address_hash(addr.src), hash<ag::TunnelAddress>{}(addr.dst)));
return size_t(ag::hash_pair_combine(ag::socket_address_hash(addr.src), hash<ag::TunnelAddress>{}(addr.dst)));
}
};
template <>
struct hash<ag::SockAddrTag> {
size_t operator()(const ag::SockAddrTag &k) const {
return size_t(
ag::hash_pair_combine(ag::socket_address_hash(k.addr), std::hash<std::string>()(k.appname)));
return size_t(ag::hash_pair_combine(ag::socket_address_hash(k.addr), std::hash<std::string>()(k.appname)));
}
};
+2 -2
View File
@@ -20,9 +20,9 @@
#include "common/socket_address.h"
#include "net/locations_pinger.h"
#include "net/network_manager.h"
#include "net/utils.h"
#include "net/tcp_socket.h"
#include "net/quic_connector.h"
#include "net/tcp_socket.h"
#include "net/utils.h"
#include "vpn/event_loop.h"
#include "vpn/fsm.h"
#include "vpn/internal/client_listener.h"
+27 -26
View File
@@ -48,18 +48,18 @@ typedef enum {
VPN_EC_NOERROR, // Depending on context may mean successful operation status (if returned from `vpn_connect`)
// or an endpoint session is closed without any error (if raised with `VPN_EVENT_STATE_CHANGED`)
VPN_EC_ERROR, // General code for the errors not described below
VPN_EC_INVALID_SETTINGS, // Settings passed for an operation are invalid
VPN_EC_ADDR_IN_USE, // Operation failed because the specified address was in use
VPN_EC_INVALID_STATE, // VPN client instance is in invalid state for the requested operation
VPN_EC_INVALID_SETTINGS, // Settings passed for an operation are invalid
VPN_EC_ADDR_IN_USE, // Operation failed because the specified address was in use
VPN_EC_INVALID_STATE, // VPN client instance is in invalid state for the requested operation
// Recoverable runtime errors. Client may update data from backend and reconnect.
VPN_EC_AUTH_REQUIRED, // Authorization error (in case user credentials are invalid or expired)
VPN_EC_LOCATION_UNAVAILABLE, // None of the endpoints in a location are available
VPN_EC_AUTH_REQUIRED, // Authorization error (in case user credentials are invalid or expired)
VPN_EC_LOCATION_UNAVAILABLE, // None of the endpoints in a location are available
// Unrecoverable runtime errors. Client should NOT reconnect automatically.
VPN_EC_EVENT_LOOP_FAILURE, // Failed to start the IO event loop, or it unexpectedly terminated
VPN_EC_INITIAL_CONNECT_FAILED, // No connection attempts left after initial connect() call
VPN_EC_FATAL_CONNECTIVITY_ERROR, // Fired after ConnectivityError callback with fatal error code (e.g. too many devices)
// In this case client should not reconnect automatically, and instead show user an error
// And properly tear down both tunnel and VPN client.
VPN_EC_EVENT_LOOP_FAILURE, // Failed to start the IO event loop, or it unexpectedly terminated
VPN_EC_INITIAL_CONNECT_FAILED, // No connection attempts left after initial connect() call
VPN_EC_FATAL_CONNECTIVITY_ERROR, // Fired after ConnectivityError callback with fatal error code (e.g. too many
// devices) In this case client should not reconnect automatically, and instead
// show user an error And properly tear down both tunnel and VPN client.
} VpnErrorCode;
typedef struct Vpn Vpn;
@@ -78,7 +78,8 @@ typedef struct {
*/
evutil_socket_t fd;
/**
* For non-fd mode, if specified, then `VPN_EVENT_CLIENT_OUTPUT is not needed - it will be automatically passed to this tunnel.
* For non-fd mode, if specified, then `VPN_EVENT_CLIENT_OUTPUT is not needed - it will be automatically passed to
* this tunnel.
*/
VpnOsTunnel *tunnel;
/** Maximum transfer unit for TCP protocol (if 0, `DEFAULT_MTU_SIZE` will be used) */
@@ -262,8 +263,8 @@ typedef enum {
} VpnEvent;
typedef struct {
X509 *cert; // Certificate to verify
STACK_OF(X509) *chain; // Untrusted chain
X509 *cert; // Certificate to verify
STACK_OF(X509) * chain; // Untrusted chain
/**
* SET BY HANDLER: Outcome of the operation (0 if successful, `VPN_SKIP_VERIFICATION_FLAG` to indicate that
* hostname verification should be skipped)
@@ -315,10 +316,10 @@ typedef struct {
} VpnWaitingRecoveryInfo;
typedef struct {
const VpnEndpoint *endpoint; // the endpoint to which the library is connected
const VpnRelay *relay; // non-null if connected through a relay
VpnUpstreamProtocol protocol; // the protocol used for this connection
const char *kex_group; // name of group function used for key exchange
const VpnEndpoint *endpoint; // the endpoint to which the library is connected
const VpnRelay *relay; // non-null if connected through a relay
VpnUpstreamProtocol protocol; // the protocol used for this connection
const char *kex_group; // name of group function used for key exchange
} VpnConnectedInfo;
typedef struct {
@@ -385,20 +386,20 @@ typedef struct {
} VpnTunnelConnectionClosedEvent;
typedef enum {
VPN_FCA_BYPASS, // Route the connection directly to the destination host
VPN_FCA_TUNNEL, // Route the connection through the VPN endpoint
VPN_FCA_REJECT, // Reject the connection
VPN_FCA_BYPASS, // Route the connection directly to the destination host
VPN_FCA_TUNNEL, // Route the connection through the VPN endpoint
VPN_FCA_REJECT, // Reject the connection
} VpnFinalConnectionAction;
/**
* VPN client connection information
*/
typedef struct {
const SocketAddressStorage *src; // source address of connection
const SocketAddressStorage *dst; // destination address of connection
const char *domain; // destination domain
int proto; // connection protocol
VpnFinalConnectionAction action; // final action
const SocketAddressStorage *src; // source address of connection
const SocketAddressStorage *dst; // destination address of connection
const char *domain; // destination domain
int proto; // connection protocol
VpnFinalConnectionAction action; // final action
} VpnConnectionInfoEvent;
typedef struct {
@@ -523,7 +524,7 @@ typedef struct {
/**
* QoS class and relative priority for threads on iOS platform
*/
VpnQosSettings qos_settings;
VpnQosSettings qos_settings;
#endif // __APPLE__ && TARGET_OS_IPHONE
} VpnSettings;
+1 -1
View File
@@ -15,8 +15,8 @@
#include "common/logger.h"
#include "http_icmp_multiplexer.h"
#include "http_udp_multiplexer.h"
#include "net/udp_socket.h"
#include "net/quic_connector.h"
#include "net/udp_socket.h"
#include "vpn/internal/data_buffer.h"
#include "vpn/internal/server_upstream.h"
#include "vpn/utils.h"
+1 -1
View File
@@ -1,10 +1,10 @@
#pragma once
#include <list>
#include <mutex>
#include <queue>
#include <unordered_map>
#include <vector>
#include <list>
#include "tcpip/tcpip.h"
#include "vpn/internal/client_listener.h"
+3 -3
View File
@@ -34,10 +34,10 @@ enum ClientConnectionState {
};
struct RecoveryInfo {
std::chrono::time_point<std::chrono::steady_clock> start_ts; // session recovery start timestamp
std::chrono::time_point<std::chrono::steady_clock> attempt_start_ts; // last recovery attempt start timestamp
std::chrono::time_point<std::chrono::steady_clock> start_ts; // session recovery start timestamp
std::chrono::time_point<std::chrono::steady_clock> attempt_start_ts; // last recovery attempt start timestamp
Millis attempt_interval{ag::VPN_DEFAULT_INITIAL_RECOVERY_INTERVAL_MS}; // last interval between recovery attempts
Millis to_next{}; // left to next attempt
Millis to_next{}; // left to next attempt
};
struct SelectedEndpointInfo {
+28
View File
@@ -5,6 +5,7 @@ This directory contains the new build system for VPN client and endpoint integra
## Files
### Build Scripts
- `docker_build.sh` - Main orchestration script that handles Docker builds and image creation
- `docker_run_tests.sh` - Test runner script (parameters: main or browser)
- `build_client.sh` - Script to build the VPN client (runs inside Docker container)
@@ -13,6 +14,7 @@ This directory contains the new build system for VPN client and endpoint integra
- `endpoint_run.sh` - Script to run the VPN endpoint (saves PID to /output/vpn_endpoint.pid)
### Test Scripts
- `tests/client_setup.sh` - Script to configure the VPN client with iptables rules and create configuration (port as 4th parameter, supports custom credentials for browser tests)
- `tests/client_run.sh` - Script to run the VPN client (saves PID to /output/vpn_client.pid)
- `tests/main/run.sh` - Main test orchestrator (setup → run endpoint → client → test → cleanup with PID management)
@@ -26,18 +28,21 @@ This directory contains the new build system for VPN client and endpoint integra
## Usage
### Build Docker Image
```bash
# Build the Docker image for testing
./docker_build.sh image
```
### Build VPN Client
```bash
# CONAN_REPO_URL is required for client builds
CONAN_REPO_URL="https://your-conan-repo.com" ./docker_build.sh client
```
### Build VPN Endpoint
```bash
./docker_build.sh endpoint
```
@@ -54,6 +59,7 @@ The build script automatically handles repository cloning:
## Environment Variables
### For All Builds
- `VPN_LIBS_ROOT` - VPN libs source directory (default: ./vpn-libs)
- `VPN_ENDPOINT_ROOT` - VPN endpoint source directory (default: ./vpn-libs-endpoint)
- `OUTPUT_VOLUME` - Output directory for build artifacts (default: ./output)
@@ -61,13 +67,16 @@ The build script automatically handles repository cloning:
- `VPN_ENDPOINT_GIT_URL` - Git URL for VPN endpoint (default: https://github.com/TrustTunnel/TrustTunnel)
### For Client Builds
- `CONAN_REPO_URL` - Conan repository URL for dependencies (**required**)
### For Endpoint Setup (endpoint_setup.sh)
- `ENDPOINT_HOSTNAME` - Hostname for SSL certificate generation (default: endpoint.test)
- `OUTPUT_DIR` - Directory for setup files (default: /output)
### For Browser Tests
- `BAMBOO_VPN_APP_ID` - Required for browser tests - VPN app ID for backend authentication
- `BAMBOO_VPN_TOKEN` - Required for browser tests - VPN token for backend authentication
- `AGVPN_HELPER_URL` - Optional URL to download agvpn_helper if not present in output directory
@@ -75,26 +84,31 @@ The build script automatically handles repository cloning:
## Examples
### Build Docker image
```bash
./docker_build.sh image
```
### Build client with custom Conan repository
```bash
CONAN_REPO_URL="https://your-conan-repo.com" ./docker_build.sh client
```
### Build endpoint (hostname is set during setup phase)
```bash
./docker_build.sh endpoint
```
### Build with custom output directory
```bash
OUTPUT_VOLUME="/tmp/build-output" ./docker_build.sh client
```
### Build with custom Git repositories
```bash
VPN_LIBS_GIT_URL="https://github.com/yourfork/TrustTunnelClient" ./docker_build.sh client
VPN_ENDPOINT_GIT_URL="https://github.com/yourfork/TrustTunnel" ./docker_build.sh endpoint
@@ -109,6 +123,7 @@ For the endpoint, the build process is now separated into distinct phases:
3. **Run**: `./endpoint_run.sh` - Starts the VPN endpoint server
### Running endpoint setup and execution separately
```bash
# Build the endpoint
./docker_build.sh endpoint
@@ -125,13 +140,16 @@ OUTPUT_DIR="./output" LOG_LEVEL="debug" ./endpoint_run.sh
The test system provides automated test execution with endpoint management:
### Test Types
- **Main tests**: `./docker_run_tests.sh main`
- **Browser tests**: `./docker_run_tests.sh browser`
### Test Workflow
#### Main Tests
Each main test run automatically:
1. Sets up the VPN endpoint (certificates, configuration)
2. Starts the endpoint in the background (saves PID to `/output/vpn_endpoint.pid`)
3. Determines endpoint IP addresses
@@ -144,7 +162,9 @@ Each main test run automatically:
7. Stops processes using PID files and cleans up (including network namespace)
#### Browser Tests
Each browser test run automatically:
1. Downloads `agvpn_helper` if not present (using `AGVPN_HELPER_URL` if provided)
2. Fetches real backend location and credentials using `agvpn_helper`
3. Sets up the VPN client with real backend configuration
@@ -160,7 +180,9 @@ Each browser test run automatically:
**Note**: The test container has access to built binaries (`trusttunnel_client`, `trusttunnel_endpoint`) via the mounted `/output` directory.
### Test Parameters
The test runners (`tests/main/run.sh` and `tests/browser/run.sh`) accept optional parameters:
- `protocol` - Protocol to test (default: https)
- `mode` - Test mode: tun or socks (default: tun)
- `socks_port` - SOCKS port when mode=socks (default: 7777)
@@ -169,6 +191,7 @@ The test runners (`tests/main/run.sh` and `tests/browser/run.sh`) accept optiona
These parameters are automatically passed to the underlying `run_tests.sh` scripts along with endpoint connection details.
### Examples
```bash
# Run main tests
./docker_run_tests.sh main
@@ -188,13 +211,16 @@ ENDPOINT_HOSTNAME="test.local" ./docker_run_tests.sh main
Build artifacts will be placed in the `output` directory (or the directory specified by `OUTPUT_VOLUME`):
### Client Build Output
- `trusttunnel_client` - The built VPN client executable
- Additional test scripts (if present in source)
### Endpoint Build Output
- `trusttunnel_endpoint` - The built VPN endpoint executable (from `build_endpoint.sh`)
### Endpoint Setup Output (from `endpoint_setup.sh`)
- `cert.pem` - SSL certificate
- `key.pem` - SSL private key
- `vpn.conf` - VPN configuration file
@@ -228,6 +254,7 @@ The test framework uses PID files for robust process lifecycle management:
The browser tests provide comprehensive network load simulation:
### Features
- **Puppeteer-based**: Uses headless Chrome to simulate real browser traffic
- **Multiple URLs**: Tests against BBC, Google, Guardian, and AdGuard websites
- **Network Disruption**: Simulates network problems and tests VPN reconnection
@@ -236,6 +263,7 @@ The browser tests provide comprehensive network load simulation:
- **Real Backend**: Uses `agvpn_helper` to connect to actual VPN backend infrastructure
### Browser Test Output
- `output1part.json` - Test results from the first 30-minute phase
- `output2part.json` - Test results after network disruption and recovery
- Detailed statistics including request timing, error counts, and reload frequencies
+16 -16
View File
@@ -1,8 +1,8 @@
#pragma once
#include "net/network_manager.h"
#include "net/utils.h"
#include "vpn/event_loop.h"
#include "net/network_manager.h"
namespace ag {
@@ -32,24 +32,24 @@ typedef struct {
#ifdef __MACH__
bool query_all_interfaces; // Query all interfaces to calculate pings. Supported only on Apple platforms.
#endif
VpnUpstreamProtocol main_protocol; // Application-level protocol override for VPN endpoint protocols.
// @see `VpnUpstreamConfig.main_protocol` for full description.
bool anti_dpi; // Enable anti-DPI measures.
bool handoff; // For internal use. Applications should set this parameter to `false`.
// If `true`, pass the connection state with the ping result.
const VpnRelay *relay_parallel; // Ping through this relay in parallel with normal pings.
uint32_t quic_max_idle_timeout_ms; // QUIC connection max idle timeout. Set `0` to use the default.
uint32_t quic_version; // QUIC version. Set `0` to use the default.
VpnUpstreamProtocol main_protocol; // Application-level protocol override for VPN endpoint protocols.
// @see `VpnUpstreamConfig.main_protocol` for full description.
bool anti_dpi; // Enable anti-DPI measures.
bool handoff; // For internal use. Applications should set this parameter to `false`.
// If `true`, pass the connection state with the ping result.
const VpnRelay *relay_parallel; // Ping through this relay in parallel with normal pings.
uint32_t quic_max_idle_timeout_ms; // QUIC connection max idle timeout. Set `0` to use the default.
uint32_t quic_version; // QUIC version. Set `0` to use the default.
} LocationsPingerInfo;
typedef struct {
const char *id; // location id
int ping_ms; // selected endpoint's ping (negative if none of the location endpoints successfully pinged)
const VpnEndpoint *endpoint; // selected endpoint
const VpnRelay *relay; // non-null if the selected endpoint was pinged through a relay
bool is_quic; // Whether the established connection is QUIC
void *conn_state; // For internal use. Applications should ignore this field.
// If `handoff` is `true`, this is the connection state object.
const VpnEndpoint *endpoint; // selected endpoint
const VpnRelay *relay; // non-null if the selected endpoint was pinged through a relay
bool is_quic; // Whether the established connection is QUIC
void *conn_state; // For internal use. Applications should ignore this field.
// If `handoff` is `true`, this is the connection state object.
} LocationsPingerResult;
typedef struct {
@@ -70,8 +70,8 @@ typedef struct {
* @param network_manager network manager
* @return pinger context
*/
LocationsPinger *locations_pinger_start(
const LocationsPingerInfo *info, LocationsPingerHandler handler, VpnEventLoop *ev_loop, VpnNetworkManager *network_manager);
LocationsPinger *locations_pinger_start(const LocationsPingerInfo *info, LocationsPingerHandler handler,
VpnEventLoop *ev_loop, VpnNetworkManager *network_manager);
/**
* Stop pinging
+2 -1
View File
@@ -1,8 +1,8 @@
#pragma once
#include <memory>
#include <string_view>
#include <span>
#include <string_view>
namespace ag {
@@ -15,6 +15,7 @@ using VpnMacDnsSettingsManagerImplPtr = std::unique_ptr<VpnMacDnsSettingsManager
class VpnMacDnsSettingsManager {
VpnMacDnsSettingsManagerImplPtr m_pimpl;
struct ConstructorAccess {};
public:
VpnMacDnsSettingsManager(ConstructorAccess access, std::span<const std::string_view> dns_servers);
~VpnMacDnsSettingsManager();
+2 -1
View File
@@ -260,7 +260,8 @@ void fsystem(std::string_view fmt, Ts &&...args) { // NOLINT(*-missing-std-forwa
}
Result<std::string, ExecError> sys_cmd_with_output(std::string cmd);
template <typename... Ts>
Result<std::string, ExecError> fsystem_with_output(std::string_view fmt, Ts &&...args) { // NOLINT(*-missing-std-forward)
Result<std::string, ExecError> fsystem_with_output(
std::string_view fmt, Ts &&...args) { // NOLINT(*-missing-std-forward)
return sys_cmd_with_output(fmt::vformat(fmt, fmt::make_format_args(args...)).c_str());
}
#endif // defined _WIN32
+2 -2
View File
@@ -32,8 +32,8 @@ enum Socks5ListenerEvent {
SOCKS5L_EVENT_DATA_SENT, /**< Called when some data was sent to client (raised with `socks5l_data_sent_event_t`) */
SOCKS5L_EVENT_CONNECTION_CLOSED, /**< Called when connection is closed by client (raised with
`socks5l_connection_closed_event_t`) */
SOCKS5L_EVENT_PROTECT_SOCKET, /**< Called when socket needs to be protected (raised with `SocketProtectEvent`)
*/
SOCKS5L_EVENT_PROTECT_SOCKET, /**< Called when socket needs to be protected (raised with `SocketProtectEvent`)
*/
};
enum Socks5ConnectionAddressType {
+2 -2
View File
@@ -61,8 +61,8 @@ typedef struct {
SSL *ssl; // SSL context in case of the traffic needs to be encrypted
bool anti_dpi; // Enable anti-DPI protection
bool pause_tls; // Pause the TLS handshake and raise `TCP_SOCKET_EVENT_CONNECTED` after receiving the
// first bytes from server. Continue the handshake by calling `tcp_socket_connect_continue`.
// `TCP_SOCKET_EVENT_CONNECTED` will be raised one more time when the handshake is complete.
// first bytes from server. Continue the handshake by calling `tcp_socket_connect_continue`.
// `TCP_SOCKET_EVENT_CONNECTED` will be raised one more time when the handshake is complete.
} TcpSocketConnectParameters;
/**
+2 -3
View File
@@ -100,7 +100,7 @@ bool tls_verify_cert_ip(X509 *cert, const char *ip);
* @param store trusted CA store (if NULL, `tls_create_ca_store` will be used)
* @return NULL if verified successfully, error message otherwise
*/
const char *tls_verify_cert(X509 *cert, STACK_OF(X509) *chain, X509_STORE *store);
const char *tls_verify_cert(X509 *cert, STACK_OF(X509) * chain, X509_STORE *store);
/**
* Create trusted CA store from system's one
@@ -112,8 +112,7 @@ X509_STORE *tls_create_ca_store();
#define tls_input(t, d, s) (t)->in = {(uint8_t *) (d), size_t(s)}
/** Setup to parse a handshake record. */
#define tls_input_hshake(t, d, s) \
(t)->rec = {(uint8_t *) (d), size_t(s)}, (t)->state = 1
#define tls_input_hshake(t, d, s) (t)->rec = {(uint8_t *) (d), size_t(s)}, (t)->state = 1
/**
* Parse TLS record
+3 -2
View File
@@ -14,6 +14,7 @@ bool hkdf_extract(std::span<uint8_t> dest, std::span<const uint8_t> secret, std:
/**
* HKDF-Expand-Label is wrapper around HKDF-Expand(secret, info) which builds info from several input parameters
*/
bool hkdf_expand_label(std::span<uint8_t> dest, std::span<const uint8_t> secret, std::string_view label, std::span<const uint8_t> context = {});
bool hkdf_expand_label(std::span<uint8_t> dest, std::span<const uint8_t> secret, std::string_view label,
std::span<const uint8_t> context = {});
}
} // namespace ag::tls13_utils
+15 -16
View File
@@ -15,11 +15,11 @@
#include <openssl/x509.h>
#include "common/error.h"
#include <common/net_utils.h>
#include "common/socket_address.h"
#include "common/utils.h"
#include "net/http_header.h"
#include "vpn/utils.h"
#include <common/net_utils.h>
#include <openssl/ssl.h>
namespace ag {
@@ -48,22 +48,22 @@ struct CertVerifyHandler {
struct VpnEndpoint {
SocketAddressStorage address; // endpoint address
const char *name; // endpoint host name (used, for example, for TLS handshake)
const char *remote_id; // if not NULL or empty, used for server TLS certificate verification instead of `name`
AG_ARRAY_OF(uint8_t) additional_data; // additional data about the endpoint
AG_ARRAY_OF(uint8_t) tls_client_random; // custom client random
const char *name; // endpoint host name (used, for example, for TLS handshake)
const char *remote_id; // if not NULL or empty, used for server TLS certificate verification instead of `name`
AG_ARRAY_OF(uint8_t) additional_data; // additional data about the endpoint
AG_ARRAY_OF(uint8_t) tls_client_random; // custom client random
AG_ARRAY_OF(uint8_t) tls_client_random_mask; // mask for custom client random
bool has_ipv6; // Whether IPv6 traffic can be routed through the endpoint
VpnUpstreamProtocol preferred_protocol; // Protocol to use for the endpoint connection.
// @see `VpnUpstreamConfig.main_protocol` for full description.
bool has_ipv6; // Whether IPv6 traffic can be routed through the endpoint
VpnUpstreamProtocol preferred_protocol; // Protocol to use for the endpoint connection.
// @see `VpnUpstreamConfig.main_protocol` for full description.
};
typedef AG_ARRAY_OF(VpnEndpoint) VpnEndpoints;
struct VpnRelay {
SocketAddressStorage address; // relay address
AG_ARRAY_OF(uint8_t) additional_data; // additional data about the relay
AG_ARRAY_OF(uint8_t) tls_client_random; // custom client random
SocketAddressStorage address; // relay address
AG_ARRAY_OF(uint8_t) additional_data; // additional data about the relay
AG_ARRAY_OF(uint8_t) tls_client_random; // custom client random
AG_ARRAY_OF(uint8_t) tls_client_random_mask; // mask for custom client random
};
@@ -72,7 +72,7 @@ typedef AG_ARRAY_OF(VpnRelay) VpnRelays;
struct VpnLocation {
const char *id; // location id
VpnEndpoints endpoints; // location endpoints
VpnRelays relays; // location relays
VpnRelays relays; // location relays
};
struct NameValue {
@@ -122,7 +122,7 @@ struct IcmpEchoRequest {
struct IcmpEchoReply {
/** source address of the reply (essentially equals to `dst` in corresponding `tcpip_icmp_echo_t`) */
SocketAddress peer;
SocketAddress peer;
uint16_t id; /**< an identifier to aid in matching echos and replies */
uint16_t seqno; /**< a sequence number to aid in matching echos and replies */
uint8_t type; /**< a type of the reply message */
@@ -165,7 +165,7 @@ constexpr std::string_view AG_UNFILTERED_DNS_IPS_V6[] = {
};
constexpr auto DPI_COOLDOWN_TIME = Millis{25}; // Time after the first part of the ClientHello is sent
constexpr size_t DPI_SPLIT_SIZE = 1; // Size of the first part of the ClientHello
constexpr size_t DPI_SPLIT_SIZE = 1; // Size of the first part of the ClientHello
/**
* Serializes HTTP headers structure to valid HTTP/1.1 message (request or response)
@@ -393,8 +393,7 @@ struct fmt::formatter<ag::VpnEndpoint> {
template <typename FormatContext>
auto format(const ag::VpnEndpoint &endpoint, FormatContext &ctx) {
return fmt::format_to(
ctx.out(), "name={}, address={}", endpoint.name, ag::SocketAddress(endpoint.address));
return fmt::format_to(ctx.out(), "name={}, address={}", endpoint.name, ag::SocketAddress(endpoint.address));
}
};
+10 -10
View File
@@ -2,9 +2,9 @@
#include <span>
#include "net/network_manager.h"
#include "net/utils.h"
#include "vpn/event_loop.h"
#include "net/network_manager.h"
namespace ag {
@@ -18,20 +18,20 @@ enum PingStatus {
};
struct PingResult {
Ping *ping; // ping pointer (don't delete from callback unless PING_FINISHED is reported)
PingStatus status; // ping status
int socket_error; // has sense if `status` == `PING_SOCKET_ERROR`
const VpnEndpoint *endpoint; // pinged endpoint
int ms; // RTT value
const VpnRelay *relay; // non-null if the endpoint was pinged through a relay
bool is_quic; // Whether the established connection is QUIC
void *conn_state; // Connection object, non-NULL if connection hand-off is enabled
Ping *ping; // ping pointer (don't delete from callback unless PING_FINISHED is reported)
PingStatus status; // ping status
int socket_error; // has sense if `status` == `PING_SOCKET_ERROR`
const VpnEndpoint *endpoint; // pinged endpoint
int ms; // RTT value
const VpnRelay *relay; // non-null if the endpoint was pinged through a relay
bool is_quic; // Whether the established connection is QUIC
void *conn_state; // Connection object, non-NULL if connection hand-off is enabled
};
struct PingInfo {
const char *id = ""; ///< An ID string for correlating log messages
VpnEventLoop *loop = nullptr; ///< Event loop
VpnEventLoop *loop = nullptr; ///< Event loop
VpnNetworkManager *network_manager = nullptr;
std::span<const VpnEndpoint> endpoints; ///< List of endpoints to ping
+1 -2
View File
@@ -1,4 +1,4 @@
# <p align="center">TrustTunnel Client Android adapter</p>
# TrustTunnel Client Android adapter
## Build Instructions
@@ -14,7 +14,6 @@
You can also download SDK and NDK using Android Studio SDK Manager.
### Building
To obtain all required conan dependencies, go to the root of the project and run:
@@ -1,11 +1,11 @@
#pragma once
#include <memory>
#include <cassert>
#include <memory>
#include <pthread.h>
#include <jni.h>
#include <openssl/x509.h>
#include <pthread.h>
/**
* Attaches the current thread, if necessary, and pushes a local reference frame. Reverses that in dtor.
@@ -1,5 +1,8 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can customize the launch screen with your own desired assets by replacing
the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
You can also do it by opening your Flutter project's Xcode project with
`open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project
Navigator and dropping in the desired images.
@@ -7,33 +7,32 @@
#include <memory>
#include "pigeon/native_communication.h"
#include "win32_window.h"
#include "ui_thread_dispatcher.h"
#include "win32_window.h"
// A window that does nothing but host a Flutter view.
class FlutterWindow : public Win32Window, public IUIThreadDispatcher {
public:
// Creates a new FlutterWindow hosting a Flutter view running |project|.
explicit FlutterWindow(const flutter::DartProject& project);
virtual ~FlutterWindow();
public:
// Creates a new FlutterWindow hosting a Flutter view running |project|.
explicit FlutterWindow(const flutter::DartProject &project);
virtual ~FlutterWindow();
void RunOnUIThread(std::function<void()> task) override;
void RunOnUIThread(std::function<void()> task) override;
protected:
// Win32Window:
bool OnCreate() override;
void OnDestroy() override;
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
LPARAM const lparam) noexcept override;
protected:
// Win32Window:
bool OnCreate() override;
void OnDestroy() override;
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override;
private:
// The project to run.
flutter::DartProject project_;
private:
// The project to run.
flutter::DartProject project_;
// The Flutter instance hosted by this window.
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
// The Flutter instance hosted by this window.
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
std::unique_ptr<NativeVpnInterface> native_interface_;
std::unique_ptr<NativeVpnInterface> native_interface_;
};
#endif // RUNNER_FLUTTER_WINDOW_H_
#endif // RUNNER_FLUTTER_WINDOW_H_
@@ -7,9 +7,10 @@
class NativeVpnImpl : public NativeVpnInterface {
public:
NativeVpnImpl(IUIThreadDispatcher *dispatcher, FlutterCallbacks &&callbacks);
std::optional<FlutterError> Start(const std::string& serverName, const std::string &config) override;
std::optional<FlutterError> Start(const std::string &serverName, const std::string &config) override;
std::optional<FlutterError> Stop() override;
void NotifyStateChanged(int state);
private:
ag::Logger m_logger{"NativeVpnImpl"};
@@ -12,113 +12,126 @@
#include <optional>
#include <string>
// Generated class from Pigeon.
class FlutterError {
public:
explicit FlutterError(const std::string& code)
: code_(code) {}
explicit FlutterError(const std::string& code, const std::string& message)
: code_(code), message_(message) {}
explicit FlutterError(const std::string& code, const std::string& message, const flutter::EncodableValue& details)
: code_(code), message_(message), details_(details) {}
public:
explicit FlutterError(const std::string &code)
: code_(code) {
}
explicit FlutterError(const std::string &code, const std::string &message)
: code_(code)
, message_(message) {
}
explicit FlutterError(const std::string &code, const std::string &message, const flutter::EncodableValue &details)
: code_(code)
, message_(message)
, details_(details) {
}
const std::string& code() const { return code_; }
const std::string& message() const { return message_; }
const flutter::EncodableValue& details() const { return details_; }
const std::string &code() const {
return code_;
}
const std::string &message() const {
return message_;
}
const flutter::EncodableValue &details() const {
return details_;
}
private:
std::string code_;
std::string message_;
flutter::EncodableValue details_;
private:
std::string code_;
std::string message_;
flutter::EncodableValue details_;
};
template<class T> class ErrorOr {
public:
ErrorOr(const T& rhs) : v_(rhs) {}
ErrorOr(const T&& rhs) : v_(std::move(rhs)) {}
ErrorOr(const FlutterError& rhs) : v_(rhs) {}
ErrorOr(const FlutterError&& rhs) : v_(std::move(rhs)) {}
template <class T>
class ErrorOr {
public:
ErrorOr(const T &rhs)
: v_(rhs) {
}
ErrorOr(const T &&rhs)
: v_(std::move(rhs)) {
}
ErrorOr(const FlutterError &rhs)
: v_(rhs) {
}
ErrorOr(const FlutterError &&rhs)
: v_(std::move(rhs)) {
}
bool has_error() const { return std::holds_alternative<FlutterError>(v_); }
const T& value() const { return std::get<T>(v_); };
const FlutterError& error() const { return std::get<FlutterError>(v_); };
bool has_error() const {
return std::holds_alternative<FlutterError>(v_);
}
const T &value() const {
return std::get<T>(v_);
};
const FlutterError &error() const {
return std::get<FlutterError>(v_);
};
private:
friend class NativeVpnInterface;
friend class FlutterCallbacks;
ErrorOr() = default;
T TakeValue() && { return std::get<T>(std::move(v_)); }
private:
friend class NativeVpnInterface;
friend class FlutterCallbacks;
ErrorOr() = default;
T TakeValue() && {
return std::get<T>(std::move(v_));
}
std::variant<T, FlutterError> v_;
std::variant<T, FlutterError> v_;
};
class PigeonInternalCodecSerializer : public flutter::StandardCodecSerializer {
public:
PigeonInternalCodecSerializer();
inline static PigeonInternalCodecSerializer& GetInstance() {
static PigeonInternalCodecSerializer sInstance;
return sInstance;
}
public:
PigeonInternalCodecSerializer();
inline static PigeonInternalCodecSerializer &GetInstance() {
static PigeonInternalCodecSerializer sInstance;
return sInstance;
}
void WriteValue(
const flutter::EncodableValue& value,
flutter::ByteStreamWriter* stream) const override;
protected:
flutter::EncodableValue ReadValueOfType(
uint8_t type,
flutter::ByteStreamReader* stream) const override;
void WriteValue(const flutter::EncodableValue &value, flutter::ByteStreamWriter *stream) const override;
protected:
flutter::EncodableValue ReadValueOfType(uint8_t type, flutter::ByteStreamReader *stream) const override;
};
// Generated interface from Pigeon that represents a handler of messages from Flutter.
class NativeVpnInterface {
public:
NativeVpnInterface(const NativeVpnInterface&) = delete;
NativeVpnInterface& operator=(const NativeVpnInterface&) = delete;
virtual ~NativeVpnInterface() {}
virtual std::optional<FlutterError> Start(
const std::string& server_name,
const std::string& config) = 0;
virtual std::optional<FlutterError> Stop() = 0;
public:
NativeVpnInterface(const NativeVpnInterface &) = delete;
NativeVpnInterface &operator=(const NativeVpnInterface &) = delete;
virtual ~NativeVpnInterface() {
}
virtual std::optional<FlutterError> Start(const std::string &server_name, const std::string &config) = 0;
virtual std::optional<FlutterError> Stop() = 0;
// The codec used by NativeVpnInterface.
static const flutter::StandardMessageCodec& GetCodec();
// Sets up an instance of `NativeVpnInterface` to handle messages through the `binary_messenger`.
static void SetUp(
flutter::BinaryMessenger* binary_messenger,
NativeVpnInterface* api);
static void SetUp(
flutter::BinaryMessenger* binary_messenger,
NativeVpnInterface* api,
const std::string& message_channel_suffix);
static flutter::EncodableValue WrapError(std::string_view error_message);
static flutter::EncodableValue WrapError(const FlutterError& error);
protected:
NativeVpnInterface() = default;
// The codec used by NativeVpnInterface.
static const flutter::StandardMessageCodec &GetCodec();
// Sets up an instance of `NativeVpnInterface` to handle messages through the `binary_messenger`.
static void SetUp(flutter::BinaryMessenger *binary_messenger, NativeVpnInterface *api);
static void SetUp(flutter::BinaryMessenger *binary_messenger, NativeVpnInterface *api,
const std::string &message_channel_suffix);
static flutter::EncodableValue WrapError(std::string_view error_message);
static flutter::EncodableValue WrapError(const FlutterError &error);
protected:
NativeVpnInterface() = default;
};
// Generated class from Pigeon that represents Flutter messages that can be called from C++.
class FlutterCallbacks {
public:
FlutterCallbacks(flutter::BinaryMessenger* binary_messenger);
FlutterCallbacks(
flutter::BinaryMessenger* binary_messenger,
const std::string& message_channel_suffix);
static const flutter::StandardMessageCodec& GetCodec();
void OnStateChanged(
int64_t state,
std::function<void(void)>&& on_success,
std::function<void(const FlutterError&)>&& on_error);
void OnConnectionInfo(
const std::string& info,
std::function<void(void)>&& on_success,
std::function<void(const FlutterError&)>&& on_error);
private:
flutter::BinaryMessenger* binary_messenger_;
std::string message_channel_suffix_;
public:
FlutterCallbacks(flutter::BinaryMessenger *binary_messenger);
FlutterCallbacks(flutter::BinaryMessenger *binary_messenger, const std::string &message_channel_suffix);
static const flutter::StandardMessageCodec &GetCodec();
void OnStateChanged(int64_t state, std::function<void(void)> &&on_success,
std::function<void(const FlutterError &)> &&on_error);
void OnConnectionInfo(const std::string &info, std::function<void(void)> &&on_success,
std::function<void(const FlutterError &)> &&on_error);
private:
flutter::BinaryMessenger *binary_messenger_;
std::string message_channel_suffix_;
};
#endif // PIGEON_NATIVE_COMMUNICATION_H_
#endif // PIGEON_NATIVE_COMMUNICATION_H_
+5 -5
View File
@@ -2,15 +2,15 @@
// Microsoft Visual C++ generated include file.
// Used by Runner.rc
//
#define IDI_APP_ICON 101
#define IDI_APP_ICON 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
+2 -2
View File
@@ -10,10 +10,10 @@ void CreateAndAttachConsole();
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
// encoded in UTF-8. Returns an empty std::string on failure.
std::string Utf8FromUtf16(const wchar_t* utf16_string);
std::string Utf8FromUtf16(const wchar_t *utf16_string);
// Gets the command line arguments passed in as a std::vector<std::string>,
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
std::vector<std::string> GetCommandLineArguments();
#endif // RUNNER_UTILS_H_
#endif // RUNNER_UTILS_H_
+68 -68
View File
@@ -11,92 +11,92 @@
// inherited from by classes that wish to specialize with custom
// rendering and input handling
class Win32Window {
public:
struct Point {
unsigned int x;
unsigned int y;
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
};
public:
struct Point {
unsigned int x;
unsigned int y;
Point(unsigned int x, unsigned int y)
: x(x)
, y(y) {
}
};
struct Size {
unsigned int width;
unsigned int height;
Size(unsigned int width, unsigned int height)
: width(width), height(height) {}
};
struct Size {
unsigned int width;
unsigned int height;
Size(unsigned int width, unsigned int height)
: width(width)
, height(height) {
}
};
Win32Window();
virtual ~Win32Window();
Win32Window();
virtual ~Win32Window();
// Creates a win32 window with |title| that is positioned and sized using
// |origin| and |size|. New windows are created on the default monitor. Window
// sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size this function will scale the inputted width and height as
// as appropriate for the default monitor. The window is invisible until
// |Show| is called. Returns true if the window was created successfully.
bool Create(const std::wstring& title, const Point& origin, const Size& size);
// Creates a win32 window with |title| that is positioned and sized using
// |origin| and |size|. New windows are created on the default monitor. Window
// sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size this function will scale the inputted width and height as
// as appropriate for the default monitor. The window is invisible until
// |Show| is called. Returns true if the window was created successfully.
bool Create(const std::wstring &title, const Point &origin, const Size &size);
// Show the current window. Returns true if the window was successfully shown.
bool Show();
// Show the current window. Returns true if the window was successfully shown.
bool Show();
// Release OS resources associated with window.
void Destroy();
// Release OS resources associated with window.
void Destroy();
// Inserts |content| into the window tree.
void SetChildContent(HWND content);
// Inserts |content| into the window tree.
void SetChildContent(HWND content);
// Returns the backing Window handle to enable clients to set icon and other
// window properties. Returns nullptr if the window has been destroyed.
HWND GetHandle();
// Returns the backing Window handle to enable clients to set icon and other
// window properties. Returns nullptr if the window has been destroyed.
HWND GetHandle();
// If true, closing this window will quit the application.
void SetQuitOnClose(bool quit_on_close);
// If true, closing this window will quit the application.
void SetQuitOnClose(bool quit_on_close);
// Return a RECT representing the bounds of the current client area.
RECT GetClientArea();
// Return a RECT representing the bounds of the current client area.
RECT GetClientArea();
protected:
// Processes and route salient window messages for mouse handling,
// size change and DPI. Delegates handling of these to member overloads that
// inheriting classes can handle.
virtual LRESULT MessageHandler(HWND window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
protected:
// Processes and route salient window messages for mouse handling,
// size change and DPI. Delegates handling of these to member overloads that
// inheriting classes can handle.
virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept;
// Called when CreateAndShow is called, allowing subclass window-related
// setup. Subclasses should return false if setup fails.
virtual bool OnCreate();
// Called when CreateAndShow is called, allowing subclass window-related
// setup. Subclasses should return false if setup fails.
virtual bool OnCreate();
// Called when Destroy is called.
virtual void OnDestroy();
// Called when Destroy is called.
virtual void OnDestroy();
private:
friend class WindowClassRegistrar;
private:
friend class WindowClassRegistrar;
// OS callback called by message pump. Handles the WM_NCCREATE message which
// is passed when the non-client area is being created and enables automatic
// non-client DPI scaling so that the non-client area automatically
// responds to changes in DPI. All other messages are handled by
// MessageHandler.
static LRESULT CALLBACK WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// OS callback called by message pump. Handles the WM_NCCREATE message which
// is passed when the non-client area is being created and enables automatic
// non-client DPI scaling so that the non-client area automatically
// responds to changes in DPI. All other messages are handled by
// MessageHandler.
static LRESULT CALLBACK WndProc(
HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept;
// Retrieves a class instance pointer for |window|
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
// Retrieves a class instance pointer for |window|
static Win32Window *GetThisFromHandle(HWND const window) noexcept;
// Update the window frame's theme to match the system theme.
static void UpdateTheme(HWND const window);
// Update the window frame's theme to match the system theme.
static void UpdateTheme(HWND const window);
bool quit_on_close_ = false;
bool quit_on_close_ = false;
// window handle for top level window.
HWND window_handle_ = nullptr;
// window handle for top level window.
HWND window_handle_ = nullptr;
// window handle for hosted content.
HWND child_content_ = nullptr;
// window handle for hosted content.
HWND child_content_ = nullptr;
};
#endif // RUNNER_WIN32_WINDOW_H_
#endif // RUNNER_WIN32_WINDOW_H_
+1
View File
@@ -1,4 +1,5 @@
# Easy wrapper for AdGuard VPI API
- Basically, the `trusttunnel_client` command line application in the form of a library.
- Only two buttons: `start` and `stop`. The first one accepts the configuration in TOML format.
- For tunnel listener to work, `wintun.dll` (architecture matching the `vpn_easy` binary)
+91
View File
@@ -0,0 +1,91 @@
#!/bin/sh
set -e -f -u
# This comment is used to simplify checking local copies of the script. Bump
# this number every time a significant change is made to this script.
#
# AdGuard-Project-Version: 1
# Only show interactive prompts if there a terminal is attached to stdout.
# While this technically doesn't guarantee that reading from /dev/tty works,
# this should work reasonably well on all of our supported development systems
# and in most terminal emulators.
is_tty='0'
if [ -t '1' ]; then
is_tty='1'
fi
readonly is_tty
# prompt is a helper that prompts the user for interactive input if that can be
# done. If there is no terminal attached, it sleeps for two seconds, giving the
# programmer some time to react, and returns with a zero exit code.
prompt() {
if [ "$is_tty" -eq '0' ]; then
sleep 2
return 0
fi
while true; do
printf 'commit anyway? y/[n]: '
read -r ans </dev/tty
case "$ans" in
'y' | 'Y')
break
;;
'' | 'n' | 'N')
exit 1
;;
*)
continue
;;
esac
done
}
# Warn the programmer about unstaged changes and untracked files, but do not
# fail the commit, because those changes might be temporary or for a different
# branch.
#
# shellcheck disable=SC2016
awk_prog='substr($2, 2, 1) != "." { print $9; } $1 == "?" { print $2; }'
readonly awk_prog
unstaged="$(git status --porcelain=2 | awk "$awk_prog")"
readonly unstaged
if [ "$unstaged" != '' ]; then
printf 'WARNING: you have unstaged changes:\n\n%s\n\n' "$unstaged"
prompt
fi
# Warn the programmer about temporary todos and skel FIXMEs, but do not fail the
# commit, because the commit could be in a temporary branch.
temp_todos="$(
git grep -e 'TODO.*!!' -- \
':!./scripts/hooks/pre-commit' \
|| :
)"
readonly temp_todos
if [ "$temp_todos" != '' ]; then
printf 'WARNING: you have temporary todos:\n\n%s\n\n' "$temp_todos"
prompt
fi
verbose="${VERBOSE:-0}"
readonly verbose
if [ "$(git diff --cached --name-only -- '*.md' || :)" != '' ]; then
make VERBOSE="$verbose" lint-md
fi
if [ "$(git diff --cached --name-only -- '*.rs' 'Cargo.toml' 'Cargo.lock' || :)" != '' ]; then
make VERBOSE="$verbose" lint-rust test-rust
fi
if [ "$(git diff --cached --name-only -- '*.cpp' '*.h' || :)" != '' ]; then
make VERBOSE="$verbose" lint-cpp test-cpp
fi
+4 -4
View File
@@ -63,10 +63,10 @@ typedef enum {
} TcpipEvent;
typedef struct {
uint64_t id; /**< generated identifier of request for connection */
int proto; /**< connection protocol */
const SocketAddress *src; /**< source address of connection */
const SocketAddress *dst; /**< destination address of connection */
uint64_t id; /**< generated identifier of request for connection */
int proto; /**< connection protocol */
const SocketAddress *src; /**< source address of connection */
const SocketAddress *dst; /**< destination address of connection */
} TcpipConnectRequestEvent;
typedef struct {
-1
View File
@@ -17,7 +17,6 @@ void libevent_lwip_log_debug(const char *message, ...);
libevent_lwip_log_debug x; \
} while (0)
// Determine endianess
#if defined(_WIN32) && !defined(__clang__)
+1 -1
View File
@@ -3,8 +3,8 @@
#include <list>
#include <memory>
#include "vpn/utils.h"
#include "tcpip/tcpip.h"
#include "vpn/utils.h"
namespace ag {
+9 -8
View File
@@ -1,6 +1,4 @@
# <p align="center">TrustTunnel CLI Client</p>
<p align="center">Simple, fast and secure CLI client for the TrustTunnel VPN</p>
# TrustTunnel CLI Client
TrustTunnel CLI Client is an application built on top of the TrustTunnel Client Libraries.
It provides an easy-to-use interface to configure and connect to an TrustTunnel endpoint.
@@ -45,7 +43,7 @@ For a more customized configuration experience, follow the steps below:
#### Setup Wizard CLI Options
```
```text
Usage: setup_wizard [OPTIONS]
Options:
@@ -65,11 +63,13 @@ Options:
#### Examples
**Interactive mode** (guided setup):
```shell
./setup_wizard
```
**Non-interactive with endpoint config** (recommended):
```shell
./setup_wizard --mode non-interactive \
--endpoint_config <endpoint_config.toml> \
@@ -77,6 +77,7 @@ Options:
```
**Non-interactive with manual parameters**:
```shell
./setup_wizard --mode non-interactive \
--address 192.168.1.100:443 \
@@ -95,7 +96,7 @@ The configuration file uses TOML format. Below are all available settings.
### Top-Level Settings
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| -------- | ---- | ------- | ----------- |
| `loglevel` | string | `"info"` | Logging level: `info`, `debug`, `trace` |
| `vpn_mode` | string | `"general"` | Routing policy: `general` (route all except exclusions) or `selective` (route only exclusions) |
| `killswitch_enabled` | bool | `true` | Block traffic when VPN connection is lost |
@@ -107,7 +108,7 @@ The configuration file uses TOML format. Below are all available settings.
### Endpoint Settings (`[endpoint]`)
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| -------- | ---- | ------- | ----------- |
| `hostname` | string | *required* | Endpoint hostname for TLS session establishment |
| `addresses` | array[string] | *required* | Endpoint IP:port addresses (pinger selects best) |
| `has_ipv6` | bool | `true` | Whether IPv6 traffic can be routed through endpoint |
@@ -123,7 +124,7 @@ The configuration file uses TOML format. Below are all available settings.
### TUN Listener Settings (`[listener.tun]`)
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| -------- | ---- | ------- | ----------- |
| `bound_if` | string | auto-detected | Network interface for VPN client connections (Linux/Windows: auto, macOS: `en0`) |
| `included_routes` | array[string] | `["0.0.0.0/0", "2000::/3"]` | Routes in CIDR notation to set to the virtual interface |
| `excluded_routes` | array[string] | `["0.0.0.0/8", "10.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.168.0.0/16", "224.0.0.0/3"]` | Routes in CIDR notation to exclude from VPN routing |
@@ -132,7 +133,7 @@ The configuration file uses TOML format. Below are all available settings.
### SOCKS Listener Settings (`[listener.socks]`)
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| -------- | ---- | ------- | ----------- |
| `address` | string | `"127.0.0.1:1080"` | IP address and port to bind the SOCKS5 proxy |
| `username` | string | `null` | Username for SOCKS authentication (optional) |
| `password` | string | `null` | Password for SOCKS authentication (optional) |
+4 -2
View File
@@ -4,8 +4,8 @@
#include <memory>
#include <thread>
#include "common/logger.h"
#include "common/autofd.h"
#include "common/logger.h"
#include "config.h"
#include "net/os_tunnel.h"
#include "net/utils.h"
@@ -60,7 +60,9 @@ public:
TrustTunnelClient &operator=(TrustTunnelClient &&c) = delete;
struct AutoSetup {};
struct UseTunnelFd { AutoFd fd; };
struct UseTunnelFd {
AutoFd fd;
};
struct UseProcessPackets {};
using ListenerSettings = std::variant<AutoSetup, UseTunnelFd, UseProcessPackets>;
+22 -15
View File
@@ -1,8 +1,8 @@
use std::fs;
use toml_edit::{Array, Document, Item, Table, value};
use crate::settings::{Listener, Settings};
use crate::template_settings;
use crate::template_settings::ToTomlComment;
use std::fs;
use toml_edit::{value, Array, Document, Item, Table};
pub fn compose_document(file: Option<&str>, settings: &Settings) -> Document {
let doc = match file {
@@ -29,8 +29,8 @@ fn fabricate_template_document() -> Document {
template_settings::ENDPOINT.as_str(),
template_settings::COMMON_LISTENER_TABLE,
)
.parse()
.expect("Couldn't parse fabricated document")
.parse()
.expect("Couldn't parse fabricated document")
}
fn fill_main_table(mut doc: Document, settings: &Settings) -> Document {
@@ -45,7 +45,8 @@ fn fill_main_table(mut doc: Document, settings: &Settings) -> Document {
}
fn fill_endpoint_table(mut doc: Document, settings: &Settings) -> Document {
let endpoint = doc.get_mut("endpoint")
let endpoint = doc
.get_mut("endpoint")
.and_then(Item::as_table_mut)
.expect("Endpoint table not found");
@@ -57,19 +58,22 @@ fn fill_endpoint_table(mut doc: Document, settings: &Settings) -> Document {
endpoint["client_random"] = value(&settings.endpoint.client_random);
endpoint["skip_verification"] = value(settings.endpoint.skip_verification);
endpoint["anti_dpi"] = value(settings.endpoint.anti_dpi);
endpoint["certificate"] = value(
settings.endpoint.certificate.as_deref().unwrap_or_default()
);
endpoint["certificate"] = value(settings.endpoint.certificate.as_deref().unwrap_or_default());
endpoint["upstream_protocol"] = value(&settings.endpoint.upstream_protocol);
endpoint["upstream_fallback_protocol"] = value(
settings.endpoint.upstream_fallback_protocol.as_deref().unwrap_or_default()
settings
.endpoint
.upstream_fallback_protocol
.as_deref()
.unwrap_or_default(),
);
doc
}
fn fill_listener_table(mut doc: Document, settings: &Settings) -> Document {
let mut listener = doc.get_mut("listener")
let mut listener = doc
.get_mut("listener")
.and_then(Item::as_table_mut)
.expect("Listener table not found");
@@ -92,9 +96,10 @@ fn fill_listener_table(mut doc: Document, settings: &Settings) -> Document {
_ => unreachable!(),
},
)
.parse()
.expect("Couldn't parse rebuilt document");
listener = doc.get_mut("listener")
.parse()
.expect("Couldn't parse rebuilt document");
listener = doc
.get_mut("listener")
.and_then(Item::as_table_mut)
.unwrap();
}
@@ -109,7 +114,8 @@ fn fill_listener_table(mut doc: Document, settings: &Settings) -> Document {
}
fn fill_socks_listener_table(table: &mut Table, settings: &Settings) {
let table = table["socks"].as_table_mut()
let table = table["socks"]
.as_table_mut()
.expect("SOCKS listener table not found");
let settings = match &settings.listener {
Listener::Socks(x) => x,
@@ -122,7 +128,8 @@ fn fill_socks_listener_table(table: &mut Table, settings: &Settings) {
}
fn fill_tun_listener_table(table: &mut Table, settings: &Settings) {
let table = table["tun"].as_table_mut()
let table = table["tun"]
.as_table_mut()
.expect("TUN listener table not found");
let settings = match &settings.listener {
Listener::Tun(x) => x,
+52 -42
View File
@@ -1,8 +1,8 @@
use crate::settings::{Endpoint, Settings};
use crate::user_interaction::{ask_for_input, checked_overwrite, select_index};
use std::fs;
use std::ops::Not;
use std::sync::{Mutex, MutexGuard};
use crate::settings::{Endpoint, Settings};
use crate::user_interaction::{ask_for_input, checked_overwrite, select_index};
mod composer;
mod settings;
@@ -44,11 +44,13 @@ pub struct PredefinedParameters {
impl PredefinedParameters {
pub fn new(args: &clap::ArgMatches) -> PredefinedParameters {
PredefinedParameters {
endpoint_addresses: args.get_many::<String>(ENDPOINT_ADDRESS_PARAM_NAME)
endpoint_addresses: args
.get_many::<String>(ENDPOINT_ADDRESS_PARAM_NAME)
.map(Iterator::cloned)
.map(Iterator::collect),
hostname: args.get_one::<String>(HOSTNAME_PARAM_NAME).cloned(),
credentials: args.get_one::<String>(CREDENTIALS_PARAM_NAME)
credentials: args
.get_one::<String>(CREDENTIALS_PARAM_NAME)
.map(|x| x.splitn(2, ':'))
.and_then(|mut x| x.next().zip(x.next()))
.map(|(a, b)| (a.to_string(), b.to_string())),
@@ -154,8 +156,8 @@ Required in non-interactive mode."#),
);
let args = command.clone().get_matches();
*MODE.lock().unwrap() = match args.get_one::<String>(MODE_PARAM_NAME)
*MODE.lock().unwrap() = match args
.get_one::<String>(MODE_PARAM_NAME)
.map(String::as_str)
.unwrap_or(MODE_INTERACTIVE)
{
@@ -165,10 +167,12 @@ Required in non-interactive mode."#),
};
if get_mode() == Mode::NonInteractive {
if !(args.contains_id(ENDPOINT_CONFIG_PARAM_NAME)
|| args.contains_id(HOSTNAME_PARAM_NAME)) {
command.error(clap::error::ErrorKind::MissingRequiredArgument,
r#"Additional arguments required for non-interactive mode
if !(args.contains_id(ENDPOINT_CONFIG_PARAM_NAME) || args.contains_id(HOSTNAME_PARAM_NAME))
{
command
.error(
clap::error::ErrorKind::MissingRequiredArgument,
r#"Additional arguments required for non-interactive mode
Must be provided either:
1. All required options separatelly:
@@ -178,14 +182,15 @@ OR
2. A configuration file generated on endpoint:
--endpoint_config <endpoint_config>
Note: Cannot mix both variants"#).exit();
Note: Cannot mix both variants"#,
)
.exit();
}
}
*PREDEFINED_PARAMS.lock().unwrap() = PredefinedParameters::new(&args);
(get_mode() == Mode::Interactive)
.then(|| { println!("Welcome to the setup wizard")});
(get_mode() == Mode::Interactive).then(|| println!("Welcome to the setup wizard"));
let settings_path = {
#[allow(clippy::large_enum_variant)]
@@ -195,48 +200,48 @@ Note: Cannot mix both variants"#).exit();
MakeFromScratch,
}
let action =
if let Some((path, settings)) = get_mode().eq(&Mode::NonInteractive).not()
.then(|| find_existent_settings::<Settings>("."))
.flatten()
{
let selection = select_index(
format!("Found existing settings: {path}."),
&["Use it", "Modify and overwrite", "Make new from scratch"],
Some(0),
);
match selection {
0 => Action::UseExisting { path },
1 => Action::ModifyAndOverwrite { path, settings },
2 => Action::MakeFromScratch,
_ => unreachable!("{:?}", selection),
}
} else {
Action::MakeFromScratch
};
let action = if let Some((path, settings)) = get_mode()
.eq(&Mode::NonInteractive)
.not()
.then(|| find_existent_settings::<Settings>("."))
.flatten()
{
let selection = select_index(
format!("Found existing settings: {path}."),
&["Use it", "Modify and overwrite", "Make new from scratch"],
Some(0),
);
match selection {
0 => Action::UseExisting { path },
1 => Action::ModifyAndOverwrite { path, settings },
2 => Action::MakeFromScratch,
_ => unreachable!("{:?}", selection),
}
} else {
Action::MakeFromScratch
};
match action {
Action::UseExisting { path } => path,
Action::ModifyAndOverwrite { path, settings } => {
(get_mode() == Mode::Interactive)
.then(|| { println!("Let's build the settings") });
(get_mode() == Mode::Interactive).then(|| println!("Let's build the settings"));
let settings = settings::build(Some(&settings));
println!("The settings are successfully built\n");
let doc = composer::compose_document(Some(&path), &settings);
fs::write(&path, doc.to_string())
.expect("Couldn't write the settings to a file");
fs::write(&path, doc.to_string()).expect("Couldn't write the settings to a file");
path
}
Action::MakeFromScratch => {
(get_mode() == Mode::Interactive)
.then(|| { println!("Let's build the settings") });
(get_mode() == Mode::Interactive).then(|| println!("Let's build the settings"));
let settings = settings::build(None);
println!("The settings are successfully built\n");
let path = ask_for_input::<String>(
"Path to a file to store the settings",
get_predefined_params().settings_file.clone()
get_predefined_params()
.settings_file
.clone()
.or(Some("trusttunnel_client.toml".into())),
);
if checked_overwrite(&path, "Overwrite the existing settings file?") {
@@ -256,10 +261,15 @@ Note: Cannot mix both variants"#).exit();
}
fn find_existent_settings<T: serde::de::DeserializeOwned>(path: &str) -> Option<(String, T)> {
fs::read_dir(path).ok()?
fs::read_dir(path)
.ok()?
.filter_map(Result::ok)
.filter(|entry| entry.metadata()
.map(|meta| meta.is_file()).unwrap_or_default())
.filter(|entry| {
entry
.metadata()
.map(|meta| meta.is_file())
.unwrap_or_default()
})
.filter_map(|entry| entry.file_name().into_string().ok())
.filter_map(|fname| fs::read_to_string(&fname).ok().zip(Some(fname)))
.find_map(|(content, fname)| Some(fname).zip(toml::from_str::<T>(&content).ok()))
+197 -132
View File
@@ -1,11 +1,14 @@
use std::{fs, path};
use crate::user_interaction::{
ask_for_agreement, ask_for_agreement_with_default, ask_for_input, ask_for_password,
checked_overwrite, select_variant,
};
use crate::Mode;
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::BufReader;
use std::ops::Not;
use serde::{Deserialize, Serialize};
use std::{fs, path};
use x509_parser::extensions::GeneralName;
use crate::Mode;
use crate::user_interaction::{ask_for_agreement, ask_for_agreement_with_default, ask_for_input, ask_for_password, checked_overwrite, select_variant};
macro_rules! docgen {
(
@@ -260,7 +263,8 @@ impl Listener {
match self {
Listener::Socks(_) => "socks",
Listener::Tun(_) => "tun",
}.into()
}
.into()
}
}
@@ -272,18 +276,11 @@ impl SocksListener {
impl TunListener {
pub fn default_bound_if() -> String {
if cfg!(target_os = "macos") {
"en0"
} else {
""
}.into()
if cfg!(target_os = "macos") { "en0" } else { "" }.into()
}
pub fn default_included_routes() -> Vec<String> {
vec![
"0.0.0.0/0".into(),
"2000::/3".into(),
]
vec!["0.0.0.0/0".into(), "2000::/3".into()]
}
pub fn default_excluded_routes() -> Vec<String> {
@@ -304,30 +301,39 @@ impl TunListener {
macro_rules! opt_field {
($x:expr, $field:ident) => {
$x.map(|x| &x.$field)
}
};
}
pub fn build(template: Option<&Settings>) -> Settings {
Settings {
loglevel: opt_field!(template, loglevel).cloned()
loglevel: opt_field!(template, loglevel)
.cloned()
.unwrap_or_else(Settings::default_loglevel),
vpn_mode: select_variant(
format!("{}\n", Settings::doc_vpn_mode()),
Settings::available_vpn_modes(),
Settings::available_vpn_modes().iter()
.position(|x| *x == opt_field!(template, vpn_mode).cloned()
Settings::available_vpn_modes().iter().position(|x| {
*x == opt_field!(template, vpn_mode)
.cloned()
.unwrap_or_else(Settings::default_vpn_mode)
.as_str()),
).into(),
killswitch_enabled: opt_field!(template, killswitch_enabled).cloned()
.as_str()
}),
)
.into(),
killswitch_enabled: opt_field!(template, killswitch_enabled)
.cloned()
.unwrap_or_else(Settings::default_killswitch_enabled),
killswitch_allow_ports: opt_field!(template, killswitch_allow_ports).cloned()
killswitch_allow_ports: opt_field!(template, killswitch_allow_ports)
.cloned()
.unwrap_or_else(Settings::default_killswitch_allow_ports),
post_quantum_group_enabled: opt_field!(template, post_quantum_group_enabled).cloned()
post_quantum_group_enabled: opt_field!(template, post_quantum_group_enabled)
.cloned()
.unwrap_or_else(Settings::default_post_quantum_group_enabled),
exclusions: opt_field!(template, exclusions).cloned()
exclusions: opt_field!(template, exclusions)
.cloned()
.unwrap_or_default(),
dns_upstreams: opt_field!(template, dns_upstreams).cloned()
dns_upstreams: opt_field!(template, dns_upstreams)
.cloned()
.unwrap_or_default(),
endpoint: build_endpoint(opt_field!(template, endpoint)),
listener: build_listener(opt_field!(template, listener)),
@@ -336,94 +342,106 @@ pub fn build(template: Option<&Settings>) -> Settings {
fn build_endpoint(template: Option<&Endpoint>) -> Endpoint {
let predefined_params = crate::get_predefined_params().clone();
let endpoint_config: Option<EndpointConfig> = empty_to_none(ask_for_input("Path to endpoint config, empty if no", predefined_params.endpoint_config.or(Some("".to_string()))))
.and_then(|x| {
fs::read_to_string(&x)
.map_err(|e| { panic!("Failed to read endpoint config file:\n{}", e.to_string()) })
.ok()
})
.and_then(|x| {
toml::de::from_str(x.as_str())
.map_err(|e| { panic!("Failed to parse endpoint config:\n{}", e.to_string()) })
.ok()
});
let endpoint_config: Option<EndpointConfig> = empty_to_none(ask_for_input(
"Path to endpoint config, empty if no",
predefined_params.endpoint_config.or(Some("".to_string())),
))
.and_then(|x| {
fs::read_to_string(&x)
.map_err(|e| panic!("Failed to read endpoint config file:\n{}", e.to_string()))
.ok()
})
.and_then(|x| {
toml::de::from_str(x.as_str())
.map_err(|e| panic!("Failed to parse endpoint config:\n{}", e.to_string()))
.ok()
});
let mut x = Endpoint {
addresses: endpoint_config.as_ref()
.and_then(|x| {
x.addresses.clone().into()
})
addresses: endpoint_config
.as_ref()
.and_then(|x| x.addresses.clone().into())
.or_else(|| {
ask_for_input::<String>(
&format!("{}\nMust be delimited by whitespace.\n", Endpoint::doc_addresses()),
predefined_params.endpoint_addresses
&format!(
"{}\nMust be delimited by whitespace.\n",
Endpoint::doc_addresses()
),
predefined_params
.endpoint_addresses
.or(opt_field!(template, addresses).cloned())
.map(|x| x.join(" ")),
)
.split_whitespace()
.map(String::from)
.collect::<Vec<String>>().into()
)
.split_whitespace()
.map(String::from)
.collect::<Vec<String>>()
.into()
})
.unwrap(),
has_ipv6: endpoint_config.as_ref()
.and_then(|x| {
x.has_ipv6.into()
})
has_ipv6: endpoint_config
.as_ref()
.and_then(|x| x.has_ipv6.into())
.or(opt_field!(template, has_ipv6).cloned())
.unwrap_or_else(Endpoint::default_has_ipv6),
username: endpoint_config.as_ref()
.and_then(|x| {
x.username.clone().into()
})
username: endpoint_config
.as_ref()
.and_then(|x| x.username.clone().into())
.or_else(|| {
ask_for_input(
Endpoint::doc_username(),
predefined_params.credentials.clone().unzip().0
.or(opt_field!(template, username).cloned())
)
.into()
predefined_params
.credentials
.clone()
.unzip()
.0
.or(opt_field!(template, username).cloned()),
)
.into()
})
.unwrap(),
password: endpoint_config.as_ref()
.and_then(|x| {
x.password.clone().into()
})
password: endpoint_config
.as_ref()
.and_then(|x| x.password.clone().into())
.or_else(|| {
predefined_params.credentials.unzip().1
.unwrap_or_else(|| opt_field!(template, password).cloned()
.and_then(empty_to_none)
.and_then(|x| ask_for_agreement("Overwrite password?").not().then_some(x))
.unwrap_or_else(|| ask_for_password(Endpoint::doc_password()))
)
predefined_params
.credentials
.unzip()
.1
.unwrap_or_else(|| {
opt_field!(template, password)
.cloned()
.and_then(empty_to_none)
.and_then(|x| {
ask_for_agreement("Overwrite password?").not().then_some(x)
})
.unwrap_or_else(|| ask_for_password(Endpoint::doc_password()))
})
.into()
})
.unwrap(),
client_random: endpoint_config.as_ref()
.and_then(|x| {
x.client_random.clone().into()
})
client_random: endpoint_config
.as_ref()
.and_then(|x| x.client_random.clone().into())
.or(opt_field!(template, client_random).cloned())
.unwrap_or_default(),
skip_verification: endpoint_config.as_ref()
.and_then(|x| {
x.skip_verification.into()
})
skip_verification: endpoint_config
.as_ref()
.and_then(|x| x.skip_verification.into())
.or(opt_field!(template, skip_verification).cloned())
.unwrap_or_else(Endpoint::default_skip_verification),
upstream_protocol: endpoint_config.as_ref()
.and_then(|x| {
x.upstream_protocol.clone().into()
})
upstream_protocol: endpoint_config
.as_ref()
.and_then(|x| x.upstream_protocol.clone().into())
.or(opt_field!(template, upstream_protocol).cloned())
.unwrap_or_else(Endpoint::default_upstream_protocol),
upstream_fallback_protocol: endpoint_config.as_ref()
.and_then(|x| {
x.upstream_fallback_protocol.clone().into()
})
.or(opt_field!(template, upstream_fallback_protocol).cloned().flatten()),
anti_dpi: endpoint_config.as_ref()
.and_then(|x| {
x.anti_dpi.into()
})
upstream_fallback_protocol: endpoint_config
.as_ref()
.and_then(|x| x.upstream_fallback_protocol.clone().into())
.or(opt_field!(template, upstream_fallback_protocol)
.cloned()
.flatten()),
anti_dpi: endpoint_config
.as_ref()
.and_then(|x| x.anti_dpi.into())
.or(opt_field!(template, anti_dpi).cloned())
.unwrap_or_else(Endpoint::default_anti_dpi),
..Default::default()
@@ -435,27 +453,36 @@ fn build_endpoint(template: Option<&Endpoint>) -> Endpoint {
x.certificate = config.certificate.clone().into();
} else {
let (hostname, certificate) = if crate::get_mode() == Mode::NonInteractive {
(predefined_params.hostname.clone(), predefined_params.certificate
.and_then(|x| {
fs::read_to_string(&x).expect("Failed to read certificate").into()
}))
} else if let Some(cert) = opt_field!(template, certificate).cloned().flatten()
.and_then(parse_cert)
.and_then(|x|
ask_for_agreement(&format!("Use an existent certificate? {:?}", x))
.then_some(x)
(
predefined_params.hostname.clone(),
predefined_params.certificate.and_then(|x| {
fs::read_to_string(&x)
.expect("Failed to read certificate")
.into()
}),
)
} else if let Some(cert) = opt_field!(template, certificate)
.cloned()
.flatten()
.and_then(parse_cert)
.and_then(|x| {
ask_for_agreement(&format!("Use an existent certificate? {:?}", x)).then_some(x)
})
{
(Some(cert.common_name), opt_field!(template, certificate).cloned().flatten())
(
Some(cert.common_name),
opt_field!(template, certificate).cloned().flatten(),
)
} else if let Some(cert) = empty_to_none(ask_for_input::<String>(
&format!("{}\nEnter a path to certificate:", Endpoint::doc_certificate()),
&format!(
"{}\nEnter a path to certificate:",
Endpoint::doc_certificate()
),
Some("".into()),
)) {
let contents = fs::read_to_string(& cert).expect("Failed to read certificate");
let contents = fs::read_to_string(&cert).expect("Failed to read certificate");
match parse_cert(contents.clone()) {
Some(parsed) => {
(Some(parsed.common_name), Some(contents))
}
Some(parsed) => (Some(parsed.common_name), Some(contents)),
None => {
panic!("Couldn't parse provided certificate");
}
@@ -466,7 +493,8 @@ fn build_endpoint(template: Option<&Endpoint>) -> Endpoint {
x.hostname = ask_for_input(
Endpoint::doc_hostname(),
predefined_params.hostname
predefined_params
.hostname
.or(opt_field!(template, hostname).cloned())
.or(hostname),
);
@@ -479,9 +507,11 @@ fn build_endpoint(template: Option<&Endpoint>) -> Endpoint {
x.skip_verification = x.certificate.is_none()
&& ask_for_agreement_with_default(
&format!("{}\n", Endpoint::doc_skip_verification()),
opt_field!(template, skip_verification).cloned().unwrap_or_default(),
);
&format!("{}\n", Endpoint::doc_skip_verification()),
opt_field!(template, skip_verification)
.cloned()
.unwrap_or_default(),
);
x
}
@@ -493,10 +523,12 @@ fn build_listener(template: Option<&Listener>) -> Listener {
* tun: TUN device.
"#,
Listener::available_kinds(),
Listener::available_kinds().iter()
.position(|x| *x == template.map(Listener::to_kind_string)
Listener::available_kinds().iter().position(|x| {
*x == template
.map(Listener::to_kind_string)
.unwrap_or_else(Listener::default_kind)
.as_str()),
.as_str()
}),
) {
"socks" => {
let template = template.and_then(|x| match x {
@@ -506,16 +538,29 @@ fn build_listener(template: Option<&Listener>) -> Listener {
Listener::Socks(SocksListener {
address: ask_for_input(
SocksListener::doc_address(),
Some(opt_field!(template, address).cloned()
.unwrap_or_else(SocksListener::default_address)),
Some(
opt_field!(template, address)
.cloned()
.unwrap_or_else(SocksListener::default_address),
),
),
username: empty_to_none(ask_for_input(
SocksListener::doc_username(),
Some(opt_field!(template, username).cloned().flatten().unwrap_or_default()),
Some(
opt_field!(template, username)
.cloned()
.flatten()
.unwrap_or_default(),
),
)),
password: empty_to_none(ask_for_input(
SocksListener::doc_password(),
Some(opt_field!(template, password).cloned().flatten().unwrap_or_default()),
Some(
opt_field!(template, password)
.cloned()
.flatten()
.unwrap_or_default(),
),
)),
})
}
@@ -530,15 +575,21 @@ fn build_listener(template: Option<&Listener>) -> Listener {
} else {
ask_for_input(
TunListener::doc_bound_if(),
Some(opt_field!(template, bound_if).cloned()
.unwrap_or_else(TunListener::default_bound_if)),
Some(
opt_field!(template, bound_if)
.cloned()
.unwrap_or_else(TunListener::default_bound_if),
),
)
},
included_routes: opt_field!(template, included_routes).cloned()
included_routes: opt_field!(template, included_routes)
.cloned()
.unwrap_or_else(TunListener::default_included_routes),
excluded_routes: opt_field!(template, excluded_routes).cloned()
excluded_routes: opt_field!(template, excluded_routes)
.cloned()
.unwrap_or_else(TunListener::default_excluded_routes),
mtu_size: opt_field!(template, mtu_size).cloned()
mtu_size: opt_field!(template, mtu_size)
.cloned()
.unwrap_or_else(TunListener::default_mtu_size),
})
}
@@ -586,9 +637,15 @@ struct Cert {
}
fn lookup_existent_cert() -> Option<Cert> {
fs::read_dir(".").ok()?
fs::read_dir(".")
.ok()?
.filter_map(Result::ok)
.filter(|entry| entry.metadata().map(|meta| meta.is_file()).unwrap_or_default())
.filter(|entry| {
entry
.metadata()
.map(|meta| meta.is_file())
.unwrap_or_default()
})
.filter_map(|entry| entry.path().to_str().map(String::from))
.find_map(parse_cert)
}
@@ -601,16 +658,24 @@ fn parse_cert(contents: String) -> Option<Cert> {
.next()?;
let cert = x509_parser::parse_x509_certificate(&cert.0).ok()?.1;
Some(Cert {
common_name: cert.validity.is_valid()
.then(|| {
let x = cert.subject.to_string();
x.as_str()
.strip_prefix("CN=")
.map(String::from)
.unwrap_or(x)
})?,
alt_names: cert.subject_alternative_name().ok().flatten()
.map(|x| x.value.general_names.iter().map(GeneralName::to_string).collect())
common_name: cert.validity.is_valid().then(|| {
let x = cert.subject.to_string();
x.as_str()
.strip_prefix("CN=")
.map(String::from)
.unwrap_or(x)
})?,
alt_names: cert
.subject_alternative_name()
.ok()
.flatten()
.map(|x| {
x.value
.general_names
.iter()
.map(GeneralName::to_string)
.collect()
})
.unwrap_or_default(),
expiration_date: cert.validity.not_after.to_string(),
})
@@ -1,6 +1,6 @@
use std::iter::Iterator;
use once_cell::sync::Lazy;
use crate::settings::{Endpoint, Settings, SocksListener, TunListener};
use once_cell::sync::Lazy;
use std::iter::Iterator;
#[cfg(target_family = "unix")]
const OS_LINE_ENDING: &str = "\n";
@@ -26,8 +26,9 @@ impl ToTomlComment for String {
}
}
pub static MAIN_TABLE: Lazy<String> = Lazy::new(|| format!(
r#"{}
pub static MAIN_TABLE: Lazy<String> = Lazy::new(|| {
format!(
r#"{}
loglevel = "{}"
{}
@@ -48,21 +49,23 @@ exclusions = []
{}
dns_upstreams = []
"#,
Settings::doc_loglevel().to_toml_comment(),
Settings::default_loglevel(),
Settings::doc_vpn_mode().to_toml_comment(),
Settings::default_vpn_mode(),
Settings::doc_killswitch_enabled().to_toml_comment(),
Settings::default_killswitch_enabled(),
Settings::doc_killswitch_allow_ports().to_toml_comment(),
Settings::doc_post_quantum_group_enabled().to_toml_comment(),
Settings::default_post_quantum_group_enabled(),
Settings::doc_exclusions().to_toml_comment(),
Settings::doc_dns_upstreams().to_toml_comment(),
));
Settings::doc_loglevel().to_toml_comment(),
Settings::default_loglevel(),
Settings::doc_vpn_mode().to_toml_comment(),
Settings::default_vpn_mode(),
Settings::doc_killswitch_enabled().to_toml_comment(),
Settings::default_killswitch_enabled(),
Settings::doc_killswitch_allow_ports().to_toml_comment(),
Settings::doc_post_quantum_group_enabled().to_toml_comment(),
Settings::default_post_quantum_group_enabled(),
Settings::doc_exclusions().to_toml_comment(),
Settings::doc_dns_upstreams().to_toml_comment(),
)
});
pub static ENDPOINT: Lazy<String> = Lazy::new(|| format!(
r#"{}
pub static ENDPOINT: Lazy<String> = Lazy::new(|| {
format!(
r#"{}
[endpoint]
{}
hostname = ""
@@ -87,21 +90,22 @@ upstream_fallback_protocol = ""
{}
anti_dpi = false
"#,
Endpoint::doc().to_toml_comment(),
Endpoint::doc_hostname().to_toml_comment(),
Endpoint::doc_addresses().to_toml_comment(),
Endpoint::doc_has_ipv6().to_toml_comment(),
Endpoint::default_has_ipv6(),
Endpoint::doc_username().to_toml_comment(),
Endpoint::doc_password().to_toml_comment(),
Endpoint::doc_client_random().to_toml_comment(),
Endpoint::doc_skip_verification().to_toml_comment(),
Endpoint::doc_certificate().to_toml_comment(),
Endpoint::doc_upstream_protocol().to_toml_comment(),
Endpoint::default_upstream_protocol(),
Endpoint::doc_upstream_fallback_protocol().to_toml_comment(),
Endpoint::doc_anti_dpi().to_toml_comment(),
));
Endpoint::doc().to_toml_comment(),
Endpoint::doc_hostname().to_toml_comment(),
Endpoint::doc_addresses().to_toml_comment(),
Endpoint::doc_has_ipv6().to_toml_comment(),
Endpoint::default_has_ipv6(),
Endpoint::doc_username().to_toml_comment(),
Endpoint::doc_password().to_toml_comment(),
Endpoint::doc_client_random().to_toml_comment(),
Endpoint::doc_skip_verification().to_toml_comment(),
Endpoint::doc_certificate().to_toml_comment(),
Endpoint::doc_upstream_protocol().to_toml_comment(),
Endpoint::default_upstream_protocol(),
Endpoint::doc_upstream_fallback_protocol().to_toml_comment(),
Endpoint::doc_anti_dpi().to_toml_comment(),
)
});
pub const COMMON_LISTENER_TABLE: &str = r#"
# Defines the way to listen to network traffic by the kind of the nested table.
@@ -111,8 +115,9 @@ pub const COMMON_LISTENER_TABLE: &str = r#"
[listener]
"#;
pub static SOCKS_LISTENER: Lazy<String> = Lazy::new(|| format!(
r#"[listener.socks]
pub static SOCKS_LISTENER: Lazy<String> = Lazy::new(|| {
format!(
r#"[listener.socks]
{}
address = "{}"
{}
@@ -120,14 +125,16 @@ username = ""
{}
password = ""
"#,
SocksListener::doc_address().to_toml_comment(),
SocksListener::default_address(),
SocksListener::doc_username().to_toml_comment(),
SocksListener::doc_password().to_toml_comment(),
));
SocksListener::doc_address().to_toml_comment(),
SocksListener::default_address(),
SocksListener::doc_username().to_toml_comment(),
SocksListener::doc_password().to_toml_comment(),
)
});
pub static TUN_LISTENER: Lazy<String> = Lazy::new(|| format!(
r#"[listener.tun]
pub static TUN_LISTENER: Lazy<String> = Lazy::new(|| {
format!(
r#"[listener.tun]
{}
bound_if = "{}"
{}
@@ -137,20 +144,21 @@ excluded_routes = [{}]
{}
mtu_size = {}
"#,
TunListener::doc_bound_if().to_toml_comment(),
TunListener::default_bound_if(),
TunListener::doc_included_routes().to_toml_comment(),
TunListener::default_included_routes()
.iter()
.map(|x| format!("\"{x}\","))
.collect::<Vec<_>>()
.join(OS_LINE_ENDING),
TunListener::doc_excluded_routes().to_toml_comment(),
TunListener::default_excluded_routes()
.iter()
.map(|x| format!("\"{x}\","))
.collect::<Vec<_>>()
.join(OS_LINE_ENDING),
TunListener::doc_mtu_size().to_toml_comment(),
TunListener::default_mtu_size(),
));
TunListener::doc_bound_if().to_toml_comment(),
TunListener::default_bound_if(),
TunListener::doc_included_routes().to_toml_comment(),
TunListener::default_included_routes()
.iter()
.map(|x| format!("\"{x}\","))
.collect::<Vec<_>>()
.join(OS_LINE_ENDING),
TunListener::doc_excluded_routes().to_toml_comment(),
TunListener::default_excluded_routes()
.iter()
.map(|x| format!("\"{x}\","))
.collect::<Vec<_>>()
.join(OS_LINE_ENDING),
TunListener::doc_mtu_size().to_toml_comment(),
TunListener::default_mtu_size(),
)
});
@@ -1,20 +1,20 @@
use crate::Mode;
use dialoguer::theme::ColorfulTheme;
use dialoguer::{Confirm, Input, Password, Select};
use once_cell::sync::Lazy;
use std::fs;
use std::ops::Deref;
use std::path::Path;
use std::str::FromStr;
use dialoguer::{Confirm, Input, Password, Select};
use dialoguer::theme::ColorfulTheme;
use once_cell::sync::Lazy;
use crate::Mode;
pub static THEME: Lazy<ColorfulTheme> = Lazy::new(ColorfulTheme::default);
/// Ask user to enter a value.
/// If [`default`] is [`Some`], suggest the value in the prompt.
pub fn ask_for_input<T>(message: &str, default: Option<T>) -> T
where
T: Clone + Default + FromStr + ToString,
<T as FromStr>::Err: ToString,
where
T: Clone + Default + FromStr + ToString,
<T as FromStr>::Err: ToString,
{
if crate::get_mode() == Mode::NonInteractive {
return default.expect("Expecting a user input in non-interactive mode");
@@ -25,18 +25,24 @@ pub fn ask_for_input<T>(message: &str, default: Option<T>) -> T
.with_prompt(message)
.show_default(default.is_some())
.default(default.unwrap_or_default())
.interact().unwrap()
.interact()
.unwrap()
} else {
Input::<T>::with_theme(THEME.deref())
.with_prompt(message)
.interact().unwrap()
.interact()
.unwrap()
}
}
/// Ask if one wants to do something (yes/no).
/// No by default.
pub fn ask_for_agreement(message: &str) -> bool {
assert_ne!(crate::get_mode(), Mode::NonInteractive, "Expecting a user input in non-interactive mode");
assert_ne!(
crate::get_mode(),
Mode::NonInteractive,
"Expecting a user input in non-interactive mode"
);
ask_for_agreement_with_default(message, false)
}
@@ -57,7 +63,11 @@ pub fn ask_for_agreement_with_default(message: &str, default: bool) -> bool {
/// Ask user to enter a password in a secure way
pub fn ask_for_password(message: &str) -> String {
assert_ne!(crate::get_mode(), Mode::NonInteractive, "Expecting a user input in non-interactive mode");
assert_ne!(
crate::get_mode(),
Mode::NonInteractive,
"Expecting a user input in non-interactive mode"
);
Password::with_theme(THEME.deref())
.with_prompt(message)
.interact()
@@ -67,14 +77,19 @@ pub fn ask_for_password(message: &str) -> String {
/// Check if a file exists and if it does, ask if one wants to overwrite it
pub fn checked_overwrite(path: &str, message: &str) -> bool {
crate::get_mode() == Mode::NonInteractive
|| !fs::metadata(Path::new(&path)).as_ref()
.map(fs::Metadata::is_file)
.unwrap_or_default()
|| !fs::metadata(Path::new(&path))
.as_ref()
.map(fs::Metadata::is_file)
.unwrap_or_default()
|| ask_for_agreement(message)
}
/// Ask user to select a variant. Returns index of the selected variant.
pub fn select_index<S: Into<String>>(prompt: S, variants: &[&str], default: Option<usize>) -> usize {
pub fn select_index<S: Into<String>>(
prompt: S,
variants: &[&str],
default: Option<usize>,
) -> usize {
if crate::get_mode() == Mode::NonInteractive {
return default.expect("Expecting a user input in non-interactive mode");
}
@@ -84,13 +99,15 @@ pub fn select_index<S: Into<String>>(prompt: S, variants: &[&str], default: Opti
.items(variants)
.report(true)
.default(default.unwrap_or_default())
.interact_opt().expect("Interaction failure")
.interact_opt()
.expect("Interaction failure")
.expect("None selected")
}
/// Ask user to select a variant. Returns the selected variant.
pub fn select_variant<'a, S>(prompt: S, variants: &[&'a str], default: Option<usize>) -> &'a str
where S: Into<String>
where
S: Into<String>,
{
variants[select_index(prompt, variants, default)]
}
+4 -4
View File
@@ -1,18 +1,18 @@
#pragma once
#include <mach/mach_port.h>
#include <mach/mach_interface.h>
#include <mach/mach_init.h>
#include <mach/mach_interface.h>
#include <mach/mach_port.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <IOKit/IOMessage.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <CoreFoundation/CoreFoundation.h>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <thread>
#include <condition_variable>
class AppleSleepNotifier {
public: