Pull request 121: Clippy and github actions

Squashed commit of the following:

commit 6eae1e962a27b2c3bcb6362f53bb1d7d92a66983
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Fri Dec 26 11:46:12 2025 +0400

    Run lint on both macos and linux

commit 94254caec3ea166db80c6b3f4004b4126605a1b7
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 22:44:10 2025 +0400

    Fix note again by adding lint hint

commit 5a67ae358a5676a22e85798683674607d2788a51
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 22:42:02 2025 +0400

    Fix note

commit 937b178302244fe237d06b6f38ba0f29db6e0d7e
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 22:39:45 2025 +0400

    Fix README

commit 769c5d9ebdc03e8500f9fc00d7f2b6f316924557
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 22:39:15 2025 +0400

    Cargo update

commit 1e932e4037c2b9ffc4b12f398f1ef14c32b5481e
Merge: dcf6a53 2041edc
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 22:37:24 2025 +0400

    Merge remote-tracking branch 'origin/master' into feature/TRUST-235

commit dcf6a53410e59411a3e05f798ed4be7f7c9994ce
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 22:24:56 2025 +0400

    Get rid of rustls-pemfile and update sentry

commit cb2e26e47d4612d65ae990ec887875bb1ac94456
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 19:08:23 2025 +0400

    Fix tests

commit a3cde3fdf16edfe2e2a574b8d729c2b9d59daf84
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 18:33:46 2025 +0400

    Fix vulnerabilities

commit 35cb9c699a0ddf2eb344c7c475be3c36a26dbf83
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 17:13:18 2025 +0400

    Don't install cargo-audit manually

commit 71a5411ac4fe31fc08c3bacb83d327bf6b7ab8c3
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 17:07:30 2025 +0400

    Install stable rust for cargo-audit

commit b7f38a90054cda39d72760b0ebc3ce295fba95d2
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 16:54:13 2025 +0400

    Fix yaml

commit fbbe78f68b2987280874f23d4ed05ef75ed42f46
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 16:53:15 2025 +0400

    Try to lock cargo-audit version

commit 08f31734b49c70d9dc03c7977ac6182198d1cbde
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 16:46:06 2025 +0400

    Update audit workflow

commit c202f186cd1610439a13928fc1fabac88e83097b
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 16:30:55 2025 +0400

    Install rust tools and better rust cache

commit eccf2fa91efcc4c6e5684960e368892bc68e67cd
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 16:30:25 2025 +0400

    Name for job

commit dccc19f13180e767b8390c8ea32fde4285c0cab8
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 16:30:17 2025 +0400

    Update checkout step version

commit edbb4404bf6fc1927f0184433df9982767a9c762
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 16:30:02 2025 +0400

    Run lint only on linux

commit b59ed893fa55edf030f9ffee2e442c8b947fa28f
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 16:09:43 2025 +0400

    Lint in the same workflow as testing to avoid rebuilds

commit 8d8ecd51859c825d0437361f8c51bde6b46994bc
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Thu Dec 25 15:27:23 2025 +0400

    More clippy fixes

... and 6 more commits
This commit is contained in:
Andrey Yakushin 2025-12-26 12:45:09 +03:00
parent 2041edc1bf
commit 783908b315
41 changed files with 1643 additions and 1151 deletions

View file

@ -1,30 +0,0 @@
name: Clippy check
on:
push:
branches:
- "*"
paths-ignore:
- '**/README.md'
- 'bamboo-specs/**'
- 'scripts/**'
pull_request:
types: [opened, reopened, synchronize]
workflow_dispatch:
jobs:
clippy-check:
runs-on: ubuntu-latest
if: "!startsWith(github.event.head_commit.message, 'skipci:')"
steps:
- name: Checkout source code
uses: actions/checkout@v3
- name: Install clippy
run: rustup component add clippy
- name: Run clippy
uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features

View file

@ -1,67 +0,0 @@
name: Deploy
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
jobs:
pack-sources:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Prepare changelog
id: changelog
run: |
LAST_TAG="${GITHUB_REF#refs/*/}"
PREV_TAG=$(git describe --tags --abbrev=0 ${LAST_TAG}^)
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "CHANGELOG<<$EOF" >> "$GITHUB_ENV"
./scripts/ci/extract_changelog.py "$PREV_TAG" "$LAST_TAG" >> "$GITHUB_ENV"
echo "$EOF" >> "$GITHUB_ENV"
- name: Release
uses: softprops/action-gh-release@v1
with:
body: ${{ env.CHANGELOG }}
# TODO: check with public repository
# notify:
# needs: pack-sources
#
# # Secrets are not passed to workflows that are triggered by a pull request
# # from a fork.
# #
# # Use always() to signal to the runner that this job must run even if the
# # previous ones failed.
# if:
# ${{
# always() &&
# github.repository_owner == 'AdguardTeam' &&
# (
# github.event_name == 'push' ||
# github.event.pull_request.head.repo.full_name == github.repository
# )
# }}
# runs-on: ubuntu-latest
# steps:
# - name: Conclusion
# uses: technote-space/workflow-conclusion-action@v1
# - name: Send Slack notification
# uses: 8398a7/action-slack@v3
# with:
# status: ${{ env.WORKFLOW_CONCLUSION }}
# fields: repo, message, commit, author, workflow
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

22
.github/workflows/md-lint.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: Lint Markdown
on:
push:
branches:
- "*"
paths:
- '**.md'
- '.markdown-lint.json'
pull_request:
types: [opened, reopened, synchronize]
workflow_dispatch:
jobs:
markdown-lint:
runs-on: ubuntu-latest
if: "!startsWith(github.event.head_commit.message, 'skipci:')"
steps:
- uses: actions/checkout@v4
- name: Markdown Lint
uses: DavidAnson/markdownlint-cli2-action@v22
with:
globs: '**/*.md'

View file

@ -1,7 +1,7 @@
name: Run tests
name: Run tests and lint
env:
RUST_CHANNEL: 1.67
RUST_CHANNEL: 1.85
on:
push:
@ -17,6 +17,7 @@ on:
jobs:
tests:
name: Test & Lint
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
@ -24,19 +25,21 @@ jobs:
if: "!startsWith(github.event.head_commit.message, 'skipci:')"
steps:
- name: Checkout source code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Cargo cache
uses: actions/cache@v3
id: cargo-cache
- name: Install Rust ${{ env.RUST_CHANNEL }}
uses: dtolnay/rust-toolchain@master
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
toolchain: ${{ env.RUST_CHANNEL }}
components: rustfmt, clippy
- name: Rust Cache
uses: Swatinem/rust-cache@v2
- name: Run tests
run: |
cargo test --workspace
- name: Lint Rust
run: |
make lint-rust

View file

@ -16,10 +16,18 @@ jobs:
permissions:
checks: write # for rustsec/audit-check to create check
contents: read # for actions/checkout to fetch code
env:
# This OVERRIDES the rust-toolchain.toml file for this job only
# because cargo-audit requires newer Rust version
RUSTUP_TOOLCHAIN: stable
runs-on: ubuntu-latest
if: "!startsWith(github.event.head_commit.message, 'skipci:')"
steps:
- uses: actions/checkout@v3
- uses: rustsec/audit-check@v1.4.1
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- uses: actions-rust-lang/audit@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

2331
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -75,6 +75,7 @@ lint-md:
.PHONY: lint-rust
lint-rust:
cargo fmt --all -- --check
cargo clippy -- -D warnings
## Fix linter issues that are auto-fixable.
.PHONY: lint-fix
@ -83,6 +84,7 @@ lint-fix: lint-fix-rust lint-fix-md
## Auto-fix Rust code formatting issues with rustfmt.
.PHONY: lint-fix-rust
lint-fix-rust:
cargo clippy --fix --allow-dirty
cargo fmt --all
## Auto-fix markdown files.

View file

@ -287,6 +287,7 @@ cat /opt/trusttunnel_client/trusttunnel_client.toml
>
> See the [TrustTunnel CLI Client README](https://github.com/TrustTunnel/TrustTunnelClient/blob/master/trusttunnel/README.md#configuration-reference) for all available options.
<!-- markdownlint-disable MD028 -->
> [!NOTE]
> After editing the config, restart the client for the changes to take effect.

View file

@ -9,12 +9,12 @@ name = "trusttunnel_endpoint"
doctest = false
[dependencies]
clap = "4.3.8"
clap = "4.5"
console-subscriber = { version = "0.1.9", optional = true }
log = "0.4.19"
nix = { version = "0.28.0", features = ["resource"] }
sentry = { version = "0.31.5", default-features = false, features = ["backtrace", "panic", "reqwest", "rustls", "contexts"] }
tokio = { version = "1.28.2", features = ["rt-multi-thread", "signal"] }
sentry = { version = "0.46.0", default-features = false, features = ["backtrace", "panic", "reqwest", "rustls", "contexts"] }
tokio = { version = "1.42", features = ["rt-multi-thread", "signal"] }
toml = { version = "0.7.4", default-features = false, features = ["parse"] }
trusttunnel = { version = "0.1", path = "../lib" }

View file

@ -191,7 +191,7 @@ fn main() {
.collect();
let client_config = client_config::build(
&username,
username,
addresses,
settings.get_clients(),
&tls_hosts_settings,
@ -258,6 +258,7 @@ fn main() {
}
};
#[allow(clippy::await_holding_lock)]
let interrupt_task = async move {
tokio::signal::ctrl_c().await.unwrap();
shutdown.lock().unwrap().submit();

View file

@ -30,14 +30,14 @@ libc = "0.2.147"
log = "0.4.19"
macros = { version = "0.1.0", path = "../macros", optional = true }
once_cell = "1.18.0"
prometheus = { version = "0.13.3", features = ["process"] }
prometheus = { version = "0.14", features = ["process"] }
quiche = { version = "0.24.5", features = ["qlog", "boringssl-boring-crate"] }
ring = "0.16.20"
ring = "0.17.12"
rustls = { version = "0.21.2", features = ["logging"] }
rustls-pemfile = "1.0.2"
rustls-pki-types = "1.13.2"
serde = "1.0.164"
smallvec = "1.10.0"
tokio = { version = "1.28.2", features = ["net", "rt", "sync", "time", "macros", "rt-multi-thread"] }
tokio = { version = "1.42", features = ["net", "rt", "sync", "time", "macros", "rt-multi-thread"] }
tokio-rustls = "0.24.1"
toml_edit = "0.19.10"
boring = "4"

View file

@ -28,7 +28,7 @@ pub trait Authenticator: Send + Sync {
fn authenticate(&self, source: &Source<'_>, log_id: &log_utils::IdChain<u64>) -> Status;
}
impl<'a> Source<'a> {
impl Source<'_> {
pub fn into_owned(self) -> Source<'static> {
match self {
Source::Sni(x) => Source::Sni(Cow::Owned(x.into_owned())),

View file

@ -22,7 +22,7 @@ pub struct RegistryBasedAuthenticator {
}
impl RegistryBasedAuthenticator {
pub fn new(clients: &Vec<Client>) -> Self {
pub fn new(clients: &[Client]) -> Self {
Self {
clients: clients
.iter()

View file

@ -8,7 +8,7 @@ use toml_edit::{value, Document};
pub fn build(
client: &String,
addresses: Vec<SocketAddr>,
username: &Vec<registry_based::Client>,
username: &[registry_based::Client],
hostsettings: &TlsHostsSettings,
) -> ClientConfig {
let user = username
@ -23,7 +23,7 @@ pub fn build(
ClientConfig {
hostname: host.hostname.clone(),
addresses: addresses,
addresses,
has_ipv6: true, // Hardcoded to true, client could change this himself
username: user.username.clone(),
password: user.password.clone(),

View file

@ -1,8 +1,6 @@
use crate::forwarder::Forwarder;
use crate::forwarder::{Forwarder, IcmpMultiplexer, UdpMultiplexer};
use crate::tcp_forwarder::TcpForwarder;
use crate::{
authentication, core, datagram_pipe, downstream, forwarder, log_utils, tunnel, udp_forwarder,
};
use crate::{authentication, core, forwarder, log_utils, tunnel, udp_forwarder};
use async_trait::async_trait;
use std::io;
use std::net::IpAddr;
@ -46,23 +44,14 @@ impl Forwarder for DirectForwarder {
&self,
id: log_utils::IdChain<u64>,
_: forwarder::UdpMultiplexerMeta,
) -> io::Result<(
Arc<dyn forwarder::UdpDatagramPipeShared>,
Box<dyn datagram_pipe::Source<Output = forwarder::UdpDatagramReadStatus>>,
Box<dyn datagram_pipe::Sink<Input = downstream::UdpDatagram>>,
)> {
) -> io::Result<UdpMultiplexer> {
udp_forwarder::make_multiplexer(id)
}
fn make_icmp_datagram_multiplexer(
&self,
id: log_utils::IdChain<u64>,
) -> io::Result<
Option<(
Box<dyn datagram_pipe::Source<Output = forwarder::IcmpDatagram>>,
Box<dyn datagram_pipe::Sink<Input = downstream::IcmpDatagram>>,
)>,
> {
) -> io::Result<Option<IcmpMultiplexer>> {
self.context
.icmp_forwarder
.as_ref()

View file

@ -99,6 +99,17 @@ pub(crate) enum UdpDatagramReadStatus {
UdpClose(UdpDatagramMeta, io::Error),
}
pub(crate) type UdpMultiplexer = (
Arc<dyn UdpDatagramPipeShared>,
Box<dyn datagram_pipe::Source<Output = UdpDatagramReadStatus>>,
Box<dyn datagram_pipe::Sink<Input = downstream::UdpDatagram>>,
);
pub(crate) type IcmpMultiplexer = (
Box<dyn datagram_pipe::Source<Output = IcmpDatagram>>,
Box<dyn datagram_pipe::Sink<Input = downstream::IcmpDatagram>>,
);
/// An abstract interface for a traffic forwarder implementation
pub(crate) trait Forwarder: Send {
/// Create a TCP connector object
@ -112,22 +123,13 @@ pub(crate) trait Forwarder: Send {
&self,
id: log_utils::IdChain<u64>,
meta: UdpMultiplexerMeta,
) -> io::Result<(
Arc<dyn UdpDatagramPipeShared>,
Box<dyn datagram_pipe::Source<Output = UdpDatagramReadStatus>>,
Box<dyn datagram_pipe::Sink<Input = downstream::UdpDatagram>>,
)>;
) -> io::Result<UdpMultiplexer>;
/// Create an ICMP datagram multiplexer
fn make_icmp_datagram_multiplexer(
&self,
id: log_utils::IdChain<u64>,
) -> io::Result<
Option<(
Box<dyn datagram_pipe::Source<Output = IcmpDatagram>>,
Box<dyn datagram_pipe::Sink<Input = downstream::IcmpDatagram>>,
)>,
>;
) -> io::Result<Option<IcmpMultiplexer>>;
}
impl UdpDatagramMeta {

View file

@ -196,9 +196,7 @@ impl pipe::Source for RequestStream {
match self.rx.data().await {
None => Ok(pipe::Data::Eof),
Some(Ok(chunk)) => Ok(pipe::Data::Chunk(chunk)),
Some(Err(e)) if e.reason().map_or(true, |r| r == Reason::NO_ERROR) => {
Ok(pipe::Data::Eof)
}
Some(Err(e)) if e.reason().is_none_or(|r| r == Reason::NO_ERROR) => Ok(pipe::Data::Eof),
Some(Err(e)) => Err(h2_to_io_error(e)),
}
}
@ -252,7 +250,7 @@ pub struct WaitWritable<'a> {
stream: &'a mut SendStream<Bytes>,
}
impl<'a> std::future::Future for WaitWritable<'a> {
impl std::future::Future for WaitWritable<'_> {
type Output = io::Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
@ -313,7 +311,7 @@ impl http_codec::DroppingSink for RespondStream {
fn h2_to_io_error(e: h2::Error) -> io::Error {
let reason = e.reason();
if reason.as_ref().map_or(true, |r| *r == Reason::NO_ERROR) {
if reason.as_ref().is_none_or(|r| *r == Reason::NO_ERROR) {
return io::Error::from(ErrorKind::UnexpectedEof);
}

View file

@ -72,6 +72,6 @@ impl HttpDemux {
.reverse_proxy
.as_ref()
.map(|x| x.path_mask.as_str())
.map_or(false, |x| request.uri.path().starts_with(x))
.is_some_and(|x| request.uri.path().starts_with(x))
}
}

View file

@ -512,6 +512,7 @@ impl ForwardedStreamSink {
}
impl SinkWaitingResponse {
#[allow(clippy::type_complexity)]
fn parse_response(
&mut self,
data: Bytes,
@ -615,7 +616,7 @@ impl SinkWaitingResponse {
if self.request_method == http::Method::HEAD
|| response
.code
.map_or(false, |x| (100..200).contains(&x) || x == 204 || x == 304)
.is_some_and(|x| (100..200).contains(&x) || x == 204 || x == 304)
{
body_length = Some(BodyLength::Determined(0));
}

View file

@ -3,6 +3,7 @@ extern "C" {
fn set_icmpv6_filter(fd: libc::c_int) -> libc::c_int;
}
use crate::forwarder::IcmpMultiplexer;
use crate::settings::Settings;
use crate::{datagram_pipe, downstream, forwarder, icmp_utils, log_utils, net_utils, utils};
use async_trait::async_trait;
@ -79,13 +80,7 @@ impl IcmpForwarder {
}
}
pub fn make_multiplexer(
&self,
id: log_utils::IdChain<u64>,
) -> io::Result<(
Box<dyn datagram_pipe::Source<Output = forwarder::IcmpDatagram>>,
Box<dyn datagram_pipe::Sink<Input = downstream::IcmpDatagram>>,
)> {
pub fn make_multiplexer(&self, id: log_utils::IdChain<u64>) -> io::Result<IcmpMultiplexer> {
let (tx, rx) = mpsc::channel(
self.shared
.core_settings
@ -421,9 +416,8 @@ impl RawPacketStream {
return Err(io::Error::last_os_error());
}
let socket = AsyncFd::new(fd).map_err(|e| {
let socket = AsyncFd::new(fd).inspect_err(|_| {
libc::close(fd);
e
})?;
Ok(Self { inner: socket })

View file

@ -360,13 +360,14 @@ pub(crate) fn rfc1071_checksum(bytes: &[u8]) -> u16 {
/// - the unspecified address (see [`Ipv4Addr::is_unspecified()`]), and the whole
/// `0.0.0.0/8` block
/// - addresses reserved for future protocols, except
/// `192.0.0.9/32` and `192.0.0.10/32` which are globally routable
/// `192.0.0.9/32` and `192.0.0.10/32` which are globally routable
/// - addresses reserved for future use (see [IETF RFC 1112])
/// - addresses reserved for networking devices benchmarking (see [IETF RFC 2544])
///
/// [ipv4-sr]: https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
///
/// @todo: replace with [`Ipv4Addr::is_global`] when it becomes stable
#[allow(clippy::nonminimal_bool)]
pub(crate) const fn is_global_ipv4(ip: &Ipv4Addr) -> bool {
let octets = ip.octets();
// check if this address is 192.0.0.9 or 192.0.0.10. These addresses are the only two
@ -414,15 +415,15 @@ pub(crate) const fn is_global_ipv4(ip: &Ipv4Addr) -> bool {
pub(crate) const fn is_unicast_global_ipv6(ip: &Ipv6Addr) -> bool {
let segments = ip.segments();
!ip.is_multicast()
&& !ip.is_loopback()
!(ip.is_multicast()
|| ip.is_loopback()
// unicast address with link-local scope [RFC 4291]
&& (segments[0] & 0xffc0) != 0xfe80
|| (segments[0] & 0xffc0) == 0xfe80
// unique local address (`fc00::/7`) [IETF RFC 4193]
&& (segments[0] & 0xfe00) != 0xfc00
&& !ip.is_unspecified()
|| (segments[0] & 0xfe00) == 0xfc00
|| ip.is_unspecified()
// address reserved for documentation (`2001:db8::/32`) [IETF RFC 3849]
&& !((segments[0] == 0x2001) && (segments[1] == 0xdb8))
|| (segments[0] == 0x2001) && (segments[1] == 0xdb8))
}
/// Returns [`true`] if the address appears to be globally routable.

View file

@ -180,7 +180,7 @@ impl QuicMultiplexer {
Some(m) => Some(Event::UdpSend(m)),
None => return Err(io::Error::new(ErrorKind::Other, "Message receiving channel closed unexpectedly")),
},
_ = &mut wait_timeout, if self.closest_deadline.map_or(false, |x| x > Instant::now()) => None,
_ = &mut wait_timeout, if self.closest_deadline.is_some_and(|x| x > Instant::now()) => None,
}
};
@ -567,6 +567,7 @@ impl QuicMultiplexer {
})
}
#[allow(clippy::type_complexity)]
fn on_new_connection(
&mut self,
peer: &SocketAddr,
@ -691,7 +692,7 @@ impl QuicMultiplexer {
) {
let deadline = Instant::now() + duration;
self.deadlines.insert(conn_id, deadline);
if self.closest_deadline.map_or(true, |x| x > deadline) {
if self.closest_deadline.is_none_or(|x| x > deadline) {
self.closest_deadline = Some(deadline);
}
}

View file

@ -29,7 +29,7 @@ pub struct Rule {
}
/// Rules configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct RulesConfig {
/// List of filter rules
#[serde(default)]
@ -150,12 +150,6 @@ impl RulesEngine {
}
}
impl Default for RulesConfig {
fn default() -> Self {
Self { rule: vec![] }
}
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -1289,7 +1289,7 @@ where
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
impl serde::de::Visitor<'_> for Visitor {
type Value = u64;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
@ -1324,7 +1324,7 @@ where
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
impl serde::de::Visitor<'_> for Visitor {
type Value = String;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
@ -1382,7 +1382,7 @@ where
.map(|(idx, x)| {
let username = demangle_toml_string(x["username"].to_string());
let password = demangle_toml_string(x["password"].to_string());
if username.is_empty() {
return Err(serde::de::Error::custom(format!(
"Client #{}: username cannot be empty",
@ -1395,7 +1395,7 @@ where
idx + 1
)));
}
Ok(Client { username, password })
})
.collect::<Result<Vec<_>, _>>()?;

View file

@ -120,6 +120,7 @@ impl Display for NotificationError {
}
#[cfg(test)]
#[allow(clippy::await_holding_lock)]
mod tests {
use crate::shutdown::Shutdown;
use std::time::Duration;

View file

@ -113,7 +113,7 @@ pub(crate) enum ExtendedAuthenticationValue<'this> {
SniAuth,
}
impl<'this> ExtendedAuthenticationValue<'this> {
impl ExtendedAuthenticationValue<'_> {
const fn type_code(&self) -> u8 {
match self {
Self::Domain(_) => 0x01,
@ -178,7 +178,7 @@ pub(crate) enum Authentication<'this> {
Extended(Vec<ExtendedAuthenticationValue<'this>>),
}
impl<'this> Authentication<'this> {
impl Authentication<'_> {
const fn to_method(&self) -> AuthenticationMethod {
match self {
Self::UsernamePassword(..) => AuthenticationMethod::UsernamePassword,
@ -211,7 +211,7 @@ pub(crate) enum Address<'this> {
DomainName(Cow<'this, str>),
}
impl<'this> Address<'this> {
impl Address<'_> {
const fn address_type(&self) -> u8 {
match self {
Self::IpAddress(IpAddr::V4(_)) => ADDRESS_TYPE_IP_V4,
@ -229,7 +229,7 @@ pub(crate) enum Request<'this> {
UdpAssociate,
}
impl<'this> Request<'this> {
impl Request<'_> {
const fn command_code(&self) -> u8 {
match self {
Self::Connect(..) => 0x01,

View file

@ -1,4 +1,4 @@
use crate::forwarder::Forwarder;
use crate::forwarder::{Forwarder, IcmpMultiplexer, UdpMultiplexer};
use crate::settings::{ForwardProtocolSettings, Settings, Socks5ForwarderSettings};
use crate::tcp_forwarder::TcpForwarder;
use crate::{
@ -157,11 +157,7 @@ impl Forwarder for Socks5Forwarder {
&self,
id: log_utils::IdChain<u64>,
meta: forwarder::UdpMultiplexerMeta,
) -> io::Result<(
Arc<dyn forwarder::UdpDatagramPipeShared>,
Box<dyn datagram_pipe::Source<Output = forwarder::UdpDatagramReadStatus>>,
Box<dyn datagram_pipe::Sink<Input = downstream::UdpDatagram>>,
)> {
) -> io::Result<UdpMultiplexer> {
let (tx, rx) = mpsc::channel(1);
let shared = Arc::new(DatagramTransceiverShared {
core_settings: self.context.settings.clone(),
@ -202,12 +198,7 @@ impl Forwarder for Socks5Forwarder {
fn make_icmp_datagram_multiplexer(
&self,
id: log_utils::IdChain<u64>,
) -> io::Result<
Option<(
Box<dyn datagram_pipe::Source<Output = forwarder::IcmpDatagram>>,
Box<dyn datagram_pipe::Sink<Input = downstream::IcmpDatagram>>,
)>,
> {
) -> io::Result<Option<IcmpMultiplexer>> {
self.context
.icmp_forwarder
.as_ref()

View file

@ -1,3 +1,4 @@
use crate::forwarder::UdpMultiplexer;
use crate::{datagram_pipe, downstream, forwarder, log_id, log_utils, net_utils};
use async_trait::async_trait;
use bytes::Bytes;
@ -45,13 +46,7 @@ enum PollStatus {
SocketError(SocketError),
}
pub(crate) fn make_multiplexer(
id: log_utils::IdChain<u64>,
) -> io::Result<(
Arc<dyn forwarder::UdpDatagramPipeShared>,
Box<dyn datagram_pipe::Source<Output = forwarder::UdpDatagramReadStatus>>,
Box<dyn datagram_pipe::Sink<Input = downstream::UdpDatagram>>,
)> {
pub(crate) fn make_multiplexer(id: log_utils::IdChain<u64>) -> io::Result<UdpMultiplexer> {
let shared = Arc::new(MultiplexerShared {
connections: Mutex::new(Default::default()),
});

View file

@ -208,6 +208,7 @@ impl UdpConnection {
}
impl<F: Fn(pipe::SimplexDirection, usize) + Send + Sync> DuplexPipe<F> {
#[allow(clippy::type_complexity)]
pub fn new(
(source1, sink1): (
Box<dyn datagram_pipe::Source<Output = downstream::UdpDatagram>>,

View file

@ -1,8 +1,9 @@
use rustls::{Certificate, PrivateKey};
use rustls_pemfile::{certs, pkcs8_private_keys};
use rustls_pki_types::pem::PemObject;
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
use std::fs::File;
use std::io;
use std::io::{BufReader, ErrorKind};
use std::io::{BufReader, ErrorKind, Read};
pub fn hex_dump(buf: &[u8]) -> String {
buf.iter()
@ -71,20 +72,39 @@ impl<L, R> Either<L, R> {
}
pub fn load_certs(filename: &str) -> io::Result<Vec<Certificate>> {
certs(&mut BufReader::new(File::open(filename)?))
let mut reader = BufReader::new(File::open(filename)?);
let mut pem_data = String::new();
reader.read_to_string(&mut pem_data).map_err(|e| {
io::Error::new(
ErrorKind::InvalidInput,
format!("Failed to read file: {}", e),
)
})?;
CertificateDer::pem_slice_iter(pem_data.as_bytes())
.collect::<Result<Vec<_>, _>>()
.map_err(|e| io::Error::new(ErrorKind::InvalidInput, format!("Invalid cert: {}", e)))
.map(|mut certs| certs.drain(..).map(Certificate).collect())
.map(|certs| {
certs
.into_iter()
.map(|c| Certificate(c.into_owned().to_vec()))
.collect()
})
}
pub fn load_private_key(filename: &str) -> io::Result<PrivateKey> {
pkcs8_private_keys(&mut BufReader::new(File::open(filename)?))
let mut reader = BufReader::new(File::open(filename)?);
let mut pem_data = String::new();
reader.read_to_string(&mut pem_data).map_err(|e| {
io::Error::new(
ErrorKind::InvalidInput,
format!("Failed to read file: {}", e),
)
})?;
PrivateKeyDer::from_pem_slice(pem_data.as_bytes())
.map_err(|e| io::Error::new(ErrorKind::InvalidInput, format!("Invalid key: {}", e)))
.and_then(|keys| {
keys.first()
.cloned()
.ok_or_else(|| io::Error::new(ErrorKind::Other, "No keys found"))
})
.map(PrivateKey)
.map(|key| PrivateKey(key.secret_der().to_vec()))
}
pub trait IterJoin {

View file

@ -204,6 +204,7 @@ async fn do_connect_request(
fn make_socks_server_harness() -> (SocketAddr, impl Future<Output = Vec<u8>>) {
let server = std::net::TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap();
let _ = server.set_nonblocking(true);
let server_addr = server.local_addr().unwrap();
let task = async move {

View file

@ -339,7 +339,7 @@ impl Http3Session {
.map(|x| x.to_str().unwrap().parse::<usize>().unwrap())
});
let mut content = BytesMut::with_capacity(content_length.unwrap_or_default());
while content_length.map_or(true, |x| content.len() < x) {
while content_length.is_none_or(|x| content.len() < x) {
let mut buffer = [0; 64 * 1024];
match self.recv(&mut buffer).await {
0 => break,
@ -416,7 +416,7 @@ impl Http3Session {
pub async fn recv_response(&mut self) -> http::response::Parts {
self.recv_response_with_method(&http::Method::GET).await
}
async fn recv_response_with_method(&mut self, method: &http::Method) -> http::response::Parts {
Self::read_out_socket(&self.socket, &mut self.quic_conn);
Self::flush_quic_data(&self.socket, &mut self.quic_conn);
@ -434,16 +434,19 @@ impl Http3Session {
let response = response.body(()).unwrap().into_parts().0;
info!("Received response: {:?}", response);
// Track if this is a CONNECT tunnel - don't send FIN for tunnels
if method == &http::Method::CONNECT {
if method == http::Method::CONNECT {
self.is_tunnel = true;
}
if !self.is_tunnel {
loop {
let stream_id = self.stream_id();
match self.h3_conn.send_body(&mut self.quic_conn, stream_id, &[], true) {
match self
.h3_conn
.send_body(&mut self.quic_conn, stream_id, &[], true)
{
Ok(_) => break,
Err(h3::Error::Done) => {
Self::flush_quic_data(&self.socket, &mut self.quic_conn);
@ -455,7 +458,11 @@ impl Http3Session {
Self::read_out_socket(&self.socket, &mut self.quic_conn);
}
// If stream/connection already closed, that's fine
Err(h3::Error::TransportError(_) | h3::Error::StreamBlocked | h3::Error::IdError) => {
Err(
h3::Error::TransportError(_)
| h3::Error::StreamBlocked
| h3::Error::IdError,
) => {
break;
}
Err(e) => panic!("Failed to finish stream: {}", e),
@ -463,7 +470,7 @@ impl Http3Session {
}
Self::flush_quic_data(&self.socket, &mut self.quic_conn);
}
return response;
}
h3::Event::Finished => {
@ -530,7 +537,10 @@ impl Http3Session {
if !self.is_tunnel {
loop {
let stream_id = self.stream_id();
match self.h3_conn.send_body(&mut self.quic_conn, stream_id, &[], true) {
match self
.h3_conn
.send_body(&mut self.quic_conn, stream_id, &[], true)
{
Ok(_) => break,
Err(h3::Error::Done) => {
Self::flush_quic_data(&self.socket, &mut self.quic_conn);
@ -542,7 +552,11 @@ impl Http3Session {
Self::read_out_socket(&self.socket, &mut self.quic_conn);
}
// If stream/connection already closed
Err(h3::Error::TransportError(_) | h3::Error::StreamBlocked | h3::Error::IdError) => {
Err(
h3::Error::TransportError(_)
| h3::Error::StreamBlocked
| h3::Error::IdError,
) => {
break;
}
Err(e) => panic!("Failed to finish stream: {}", e),

View file

@ -165,6 +165,7 @@ async fn run_endpoint(endpoint_address: &SocketAddr, proxy_address: &SocketAddr)
fn run_proxy() -> (SocketAddr, impl Future<Output = ()>) {
let server = std::net::TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap();
let _ = server.set_nonblocking(true);
let server_addr = server.local_addr().unwrap();
(server_addr, async move {
let (socket, peer) = TcpListener::from_std(server)

View file

@ -443,6 +443,7 @@ async fn make_h2_tunnel(
fn run_tcp_server(is_download: bool) -> SocketAddr {
let server = std::net::TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap();
let _ = server.set_nonblocking(true);
let server_addr = server.local_addr().unwrap();
thread::spawn(move || {
@ -484,6 +485,7 @@ fn run_tcp_server(is_download: bool) -> SocketAddr {
fn run_udp_server(is_download: bool) -> SocketAddr {
let server = std::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).unwrap();
let _ = server.set_nonblocking(true);
let server_addr = server.local_addr().unwrap();
thread::spawn(move || {

View file

@ -10,14 +10,14 @@ path = "setup_wizard/main.rs"
[dependencies]
chrono = { version = "0.4.26", default-features = false, features = ["clock"] }
clap = "4.3.8"
clap = "4.5"
dialoguer = "0.10.4"
hex = "0.4"
ipnet = "2.9"
lazy_static = "1.4.0"
log = "0.4.19"
once_cell = "1.18.0"
rcgen = "0.10.0"
rcgen = "0.13"
serde = "1.0.164"
toml = "0.7.4"
toml_edit = "0.19.10"

View file

@ -232,28 +232,30 @@ impl AcmeClient {
// 4. Generate key pair and CSR
println!("Generating certificate signing request...");
let mut params = CertificateParams::new(vec![domain.to_string()]);
let key_pair = rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)
.map_err(|e| AcmeError::CsrGeneration(e.to_string()))?;
let mut params = CertificateParams::new(vec![domain.to_string()])
.map_err(|e| AcmeError::CsrGeneration(e.to_string()))?;
params.distinguished_name = DistinguishedName::new();
params.distinguished_name.push(DnType::CommonName, domain);
params.alg = &rcgen::PKCS_ECDSA_P256_SHA256;
let cert = rcgen::Certificate::from_params(params)
let csr = params
.serialize_request(&key_pair)
.map_err(|e| AcmeError::CsrGeneration(e.to_string()))?;
let csr_der = cert
.serialize_request_der()
.map_err(|e| AcmeError::CsrGeneration(e.to_string()))?;
let csr_der = csr.der().as_ref();
// 5. Finalize order
if order.state().status == OrderStatus::Ready {
order
.finalize(&csr_der)
.finalize(csr_der)
.await
.map_err(|e| AcmeError::Finalization(e.to_string()))?;
println!("✓ Order finalized");
}
let key_pem = cert.serialize_private_key_pem();
let key_pem = key_pair.serialize_pem();
// 6. Wait for certificate
println!("Waiting for certificate...");

View file

@ -26,9 +26,7 @@ async fn handle_request(
) -> Result<Response<Full<Bytes>>, Infallible> {
let path = req.uri().path();
if path.starts_with(ACME_CHALLENGE_PATH_PREFIX) {
let request_token = &path[ACME_CHALLENGE_PATH_PREFIX.len()..];
if let Some(request_token) = path.strip_prefix(ACME_CHALLENGE_PATH_PREFIX) {
if request_token == challenge_data.token {
println!(
" ✓ Serving ACME challenge response for token: {}",

View file

@ -177,7 +177,7 @@ fn generate_rules_toml_content(rules_config: &trusttunnel::rules::RulesConfig) -
// Serialize the actual rules (usually empty)
if !rules_config.rule.is_empty() {
content.push_str(&toml::ser::to_string(rules_config).unwrap());
content.push_str("\n");
content.push('\n');
}
content

View file

@ -174,7 +174,7 @@ Required in non-interactive mode."#,
.map(|x| x.splitn(2, ':'))
.and_then(|mut x| x.next().zip(x.next()))
.map(|(a, b)| (a.to_string(), b.to_string()))
.and_then(|(username, password)| {
.map(|(username, password)| {
if username.is_empty() {
eprintln!("Error: Username cannot be empty");
std::process::exit(1);
@ -183,7 +183,7 @@ Required in non-interactive mode."#,
eprintln!("Error: Password cannot be empty");
std::process::exit(1);
}
Some((username, password))
(username, password)
}),
hostname: args.get_one::<String>(HOSTNAME_PARAM_NAME).cloned(),
library_settings_file: args
@ -287,10 +287,7 @@ fn print_setup_complete_summary(
" • {} - Main endpoint settings",
lib_settings_path
);
println!(
" • {} - TLS host configuration",
hosts_settings_path
);
println!("{} - TLS host configuration", hosts_settings_path);
println!(
" • {} - User credentials",
library_settings::DEFAULT_CREDENTIALS_PATH

View file

@ -68,7 +68,7 @@ fn add_ip_rule(rules: &mut Vec<Rule>) {
);
// Validate CIDR format
if let Err(_) = cidr.parse::<ipnet::IpNet>() {
if cidr.parse::<ipnet::IpNet>().is_err() {
warn!("Invalid CIDR format. Skipping rule.");
return;
}
@ -137,7 +137,7 @@ fn add_combined_rule(rules: &mut Vec<Rule>) {
);
// Validate CIDR format
if let Err(_) = cidr.parse::<ipnet::IpNet>() {
if cidr.parse::<ipnet::IpNet>().is_err() {
warn!("Invalid CIDR format. Skipping rule.");
return;
}

View file

@ -229,8 +229,10 @@ fn generate_cert() -> Option<Cert> {
});
(name.clone(), vec![name.clone(), format!("*.{}", name)])
};
let mut params = rcgen::CertificateParams::new(alt_names.clone());
params.alg = &rcgen::PKCS_ECDSA_P256_SHA256;
let key_pair = rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)
.expect("Failed to generate key pair");
let mut params = rcgen::CertificateParams::new(alt_names.clone()).unwrap();
let now = chrono::Local::now();
let end_date = now
.checked_add_days(chrono::Days::new(DEFAULT_CERTIFICATE_DURATION_DAYS))
@ -245,7 +247,9 @@ fn generate_cert() -> Option<Cert> {
.distinguished_name
.push(DnType::CommonName, &common_name);
let cert = rcgen::Certificate::from_params(params).unwrap();
let cert = params
.self_signed(&key_pair)
.expect("Failed to generate self-signed cert");
let cert_path = format!("{DEFAULT_CERTIFICATE_FOLDER}/cert.pem");
if !checked_overwrite(&cert_path, "Overwrite the existing certificate file?") {
return None;
@ -258,22 +262,20 @@ fn generate_cert() -> Option<Cert> {
fs::create_dir_all(Path::new(&cert_path).parent().unwrap())
.expect("Couldn't create certificate directory path");
fs::write(&cert_path, cert.serialize_pem().unwrap())
.expect("Couldn't write the certificate into a file");
fs::write(&cert_path, cert.pem()).expect("Couldn't write the certificate into a file");
println!("The generated certificate is stored in file: {}", cert_path);
fs::create_dir_all(Path::new(&key_path).parent().unwrap())
.expect("Couldn't create private key directory path");
if key_path != cert_path {
fs::write(key_path.clone(), cert.serialize_private_key_pem())
fs::write(key_path.clone(), key_pair.serialize_pem())
.expect("Couldn't write the private key into a file");
} else {
fs::OpenOptions::new()
.write(true)
.append(true)
.open(key_path.clone())
.expect("Couldn't open a file for writing the private key")
.write_all(cert.serialize_private_key_pem().as_bytes())
.write_all(key_pair.serialize_pem().as_bytes())
.expect("Couldn't write the private key into a file");
}
println!("The generated private key is stored in file: {}", key_path);
@ -498,7 +500,7 @@ fn ask_for_alternative_snis() -> Vec<String> {
if crate::get_mode() == Mode::NonInteractive {
return vec![];
}
if !ask_for_agreement("Do you want to configure alternative SNIs?") {
return vec![];
}