Pull request 194: TRUST-116 add deterministic source port selection for udp nat in vpnlibs endpoint
Some checks failed
Lint Markdown / markdown-lint (push) Has been cancelled
Run tests and lint / Test & Lint (push) Has been cancelled
Run tests and lint / Test & Lint-1 (push) Has been cancelled

Squashed commit of the following:

commit 08dfcfd9bd
Merge: 2652f1c a89bf60
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Fri Apr 17 21:33:46 2026 +0500

    Merge remote-tracking branch 'origin/dev-1.1' into TRUST-116-add-deterministic-source-port-selection-for-udp-nat-in-vpnlibs-endpoint
    
    # Conflicts:
    #	CHANGELOG.md

commit 2652f1c947
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Fri Apr 17 21:29:52 2026 +0500

    Revert "Merge branch 'master' into TRUST-116-add-deterministic-source-port-selection-for-udp-nat-in-vpnlibs-endpoint"
    
    This reverts commit 5c07c29c40, reversing
    changes made to c2947015d1.

commit 5c07c29c40
Merge: c294701 f73bb77
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Fri Apr 17 21:04:30 2026 +0500

    Merge branch 'master' into TRUST-116-add-deterministic-source-port-selection-for-udp-nat-in-vpnlibs-endpoint

commit c2947015d1
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Wed Apr 8 19:30:21 2026 +0500

    Remove redundant test

commit 22942df074
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Wed Apr 8 19:26:16 2026 +0500

    Update CHANGELOG

commit db336ae2bf
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Wed Apr 8 19:20:37 2026 +0500

    Preserve client source port on outgoing UDP sockets

commit bf1f5e9e3a
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Wed Apr 8 19:18:47 2026 +0500

    Add unit tests for make_udp_socket_with_preferred_port

commit 826630e032
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Wed Apr 8 19:16:56 2026 +0500

    Add make_udp_socket_with_preferred_port to net_utils

commit f73bb77800
Author: Bamboo <Bamboo>
Date:   Mon Mar 16 14:16:15 2026 +0000

    skipci: Automatic version increment by Bamboo

commit 6c6655bcf2
Author: Aleksei Zhavoronkov <a.zhavoronkov@adguard.com>
Date:   Mon Mar 16 13:47:11 2026 +0000

    Pull request 184: Fix linter and sync gh and bamboo md linters
    
    Squashed commit of the following:
    
    commit 315a5cbdc6c9cba5ed02cd0b82863a026d6b429b
    Author: Zhavoronkov Aleksei <a.zhavoronkov@adguard.com>
    Date:   Mon Mar 16 15:01:42 2026 +0300
    
        return
    
    commit ee70cd9ba2730b5152b98918bf5526b6ddfefe02
    Author: Zhavoronkov Aleksei <a.zhavoronkov@adguard.com>
    Date:   Mon Mar 16 14:53:54 2026 +0300
    
        Fix
    
    commit f00f1003574c7e8aefee0c20cc316befc5a0396d
    Author: Zhavoronkov Aleksei <a.zhavoronkov@adguard.com>
    Date:   Mon Mar 16 14:52:55 2026 +0300
    
        Use another action
    
    commit fb771935f946b0c73a6e83c846dc95fb219c0906
    Author: Zhavoronkov Aleksei <a.zhavoronkov@adguard.com>
    Date:   Mon Mar 16 14:48:11 2026 +0300
    
        Fix linter and sync gh and bamboo md linters

commit 7adca494d4
Author: Bamboo <Bamboo>
Date:   Mon Mar 16 08:17:56 2026 +0000

    skipci: Automatic version increment by Bamboo

commit 2f4de67487
Author: Aleksei Zhavoronkov <a.zhavoronkov@adguard.com>
Date:   Mon Mar 16 08:17:15 2026 +0000

    Pull request 183: Rename PR template and fix its path
    
    Squashed commit of the following:
    
    commit 8db6e2b64f2635b6c2fed40a66a3212875ab44bc
    Author: Zhavoronkov Aleksei <a.zhavoronkov@adguard.com>
    Date:   Mon Mar 16 11:04:13 2026 +0300
    
        Rename PR template and fix its path

commit 95a1f0c07f
Author: Bamboo <Bamboo>
Date:   Mon Mar 16 07:37:39 2026 +0000

    skipci: Automatic version increment by Bamboo

commit c1a5c0b45e
Author: Aleksei Zhavoronkov <a.zhavoronkov@adguard.com>
Date:   Mon Mar 16 07:37:22 2026 +0000

    Pull request 182: Add github PR template
    
    Squashed commit of the following:
    
    commit dd488f21de
    Author: Zhavoronkov Aleksei <a.zhavoronkov@adguard.com>
    Date:   Mon Mar 16 09:54:32 2026 +0300
    
        Add github PR template

commit 28ba5a6f6c
Author: Bamboo <Bamboo>
Date:   Mon Mar 16 06:22:12 2026 +0000

    skipci: Automatic version increment by Bamboo

commit 531f0f7026
Author: Aleksei Zhavoronkov <a.zhavoronkov@adguard.com>
Date:   Mon Mar 16 06:21:56 2026 +0000

    Pull request 167: Print message about the QR code for mobile clients
    
    Squashed commit of the following:
    
    commit c5cec4ea78
    Author: Zhavoronkov Aleksei <a.zhavoronkov@adguard.com>
    Date:   Fri Mar 13 11:52:00 2026 +0300
    
        Add blank line before qr message
    
    commit cba915c846
    Author: Zhavoronkov Aleksei <a.zhavoronkov@adguard.com>
    Date:   Fri Mar 13 11:45:02 2026 +0300
    
        Also print page URL with QR-code
This commit is contained in:
Ilia Zhirov 2026-04-22 18:01:52 +00:00
parent a89bf60aaf
commit 52a8a3fc67
3 changed files with 64 additions and 4 deletions

View file

@ -1,10 +1,10 @@
# CHANGELOG
- [Feature] Preserve client source port on outgoing UDP connections. The endpoint now attempts to bind outgoing UDP sockets to the same source port the client application originally used, falling back to an OS-assigned ephemeral port when unavailable. This reduces NAT rebinding issues for protocols sensitive to source port changes.
- [Feature] Added destination port filtering to rules config
- Added `[inbound]` section for client filtering
- Added `[outbound]` section for destination filtering
- Rules in legacy configs are treated as `[inbound]`
- [Feature] SIGHUP credential reload support
- Credentials can now be reloaded without restarting the endpoint via `systemctl reload` or SIGHUP
- Added `ExecReload` directive to systemd service template

View file

@ -86,6 +86,25 @@ pub(crate) fn make_udp_socket(is_v4: bool) -> io::Result<UdpSocket> {
}
}
/// Try to bind a UDP socket to `preferred_port`. If the port is unavailable,
/// fall back to an OS-assigned ephemeral port.
pub(crate) fn make_udp_socket_with_preferred_port(
is_v4: bool,
preferred_port: u16,
) -> io::Result<UdpSocket> {
let bind_addr = if is_v4 {
SocketAddr::from((Ipv4Addr::UNSPECIFIED, preferred_port))
} else {
SocketAddr::from((Ipv6Addr::UNSPECIFIED, preferred_port))
};
match UdpSocket::bind(bind_addr) {
Ok(socket) => Ok(socket),
Err(_) if preferred_port != 0 => make_udp_socket(is_v4),
Err(e) => Err(e),
}
}
/// https://www.rfc-editor.org/rfc/rfc9000.html#section-16
pub(crate) const fn varint_len(x: usize) -> usize {
if x <= 63 {
@ -946,4 +965,44 @@ mod tests {
let compat: IpAddr = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0xc0a8, 0x0101));
assert_eq!(super::unmap_ipv6(compat), IpAddr::from([192, 168, 1, 1]));
}
#[test]
fn preferred_port_binds_when_free() {
use super::make_udp_socket_with_preferred_port;
let socket = make_udp_socket_with_preferred_port(true, 0).unwrap();
let port = socket.local_addr().unwrap().port();
// Port 0 means OS picks an ephemeral port, so the actual port must be non-zero.
assert_ne!(port, 0);
drop(socket);
// Bind to a specific port that we just confirmed is available.
let socket = make_udp_socket_with_preferred_port(true, port).unwrap();
assert_eq!(socket.local_addr().unwrap().port(), port);
}
#[test]
fn preferred_port_falls_back_when_taken() {
use super::make_udp_socket_with_preferred_port;
// Occupy a port.
let holder = make_udp_socket_with_preferred_port(true, 0).unwrap();
let occupied_port = holder.local_addr().unwrap().port();
// Request the same port -- should fall back to an ephemeral port.
let socket = make_udp_socket_with_preferred_port(true, occupied_port).unwrap();
assert_ne!(socket.local_addr().unwrap().port(), occupied_port);
}
#[test]
fn preferred_port_ipv6() {
use super::make_udp_socket_with_preferred_port;
let socket = make_udp_socket_with_preferred_port(false, 0).unwrap();
let port = socket.local_addr().unwrap().port();
drop(socket);
let socket = make_udp_socket_with_preferred_port(false, port).unwrap();
assert_eq!(socket.local_addr().unwrap().port(), port);
}
}

View file

@ -206,7 +206,7 @@ impl forwarder::UdpDatagramPipeShared for MultiplexerShared {
}
let metrics_guard = self.context.metrics.clone().outbound_udp_socket_counter();
e.insert(Connection {
socket: Arc::new(make_udp_socket(&meta.destination)?),
socket: Arc::new(make_udp_socket(&meta.destination, meta.source.port())?),
being_listened: false,
_metrics_guard: metrics_guard,
});
@ -289,8 +289,9 @@ impl datagram_pipe::Sink for MultiplexerSink {
}
}
fn make_udp_socket(peer: &SocketAddr) -> io::Result<UdpSocket> {
let socket = net_utils::make_udp_socket(peer.is_ipv4())?;
fn make_udp_socket(peer: &SocketAddr, preferred_src_port: u16) -> io::Result<UdpSocket> {
let socket =
net_utils::make_udp_socket_with_preferred_port(peer.is_ipv4(), preferred_src_port)?;
socket.connect(peer)?;
socket.set_nonblocking(true)?;
UdpSocket::from_std(socket)