summaryrefslogtreecommitdiffhomepage
path: root/talpid-tunnel-config-client/src/socket.rs
blob: f7e48a6f8f271456758d721f618054590a1c61ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//! A TCP stream with a low MSS set. This prevents incorrectly configured MTU from causing
//! fragmentation/packet loss. This is only supported on non-Windows targets.
//!
//! On windows this solution does not work. So on Windows we instead temporarily lower the MTU
//! while negotiating the ephemeral peer. This code lives in `talpid-wireguard/src/ephemeral.rs`
//! These two solutions to the same problem should probably be refactored to end up closer
//! to each other.

use std::io;
use std::net::SocketAddr;
use tokio::net::TcpSocket as StdTcpSocket;
use tokio::net::TcpStream;

#[cfg(unix)]
mod sys {
    use super::*;

    pub use libc::{setsockopt, socklen_t, IPPROTO_TCP, TCP_MAXSEG};
    pub use std::os::fd::{AsRawFd, RawFd};

    /// MTU to set on the tunnel config client socket. We want a low value to prevent fragmentation.
    /// Especially on Android, we've found that the real MTU is often lower than the default MTU, and
    /// we cannot lower it further. This causes the outer packets to be dropped. Also, MTU detection
    /// will likely occur after the PQ handshake, so we cannot assume that the MTU is already
    /// correctly configured.
    /// This is set to the lowest possible IPv4 MTU.
    const CONFIG_CLIENT_MTU: u16 = 576;

    pub struct TcpSocket {
        socket: StdTcpSocket,
    }

    impl TcpSocket {
        pub fn new() -> io::Result<Self> {
            let socket = StdTcpSocket::new_v4()?;
            try_set_tcp_sock_mtu(socket.as_raw_fd());
            Ok(Self { socket })
        }

        pub async fn connect(self, addr: SocketAddr) -> io::Result<TcpStream> {
            self.socket.connect(addr).await
        }
    }

    fn try_set_tcp_sock_mtu(sock: RawFd) {
        let mss = desired_mss();
        log::debug!("Tunnel config TCP socket MSS: {mss}");

        let result = unsafe {
            setsockopt(
                sock,
                IPPROTO_TCP,
                TCP_MAXSEG,
                &mss as *const _ as _,
                socklen_t::try_from(std::mem::size_of_val(&mss)).unwrap(),
            )
        };
        if result != 0 {
            log::error!(
                "Failed to set MSS on tunnel config TCP socket: {}",
                std::io::Error::last_os_error()
            );
        }
    }

    const fn desired_mss() -> u32 {
        const IPV4_HEADER_SIZE: u16 = 20;
        const MAX_TCP_HEADER_SIZE: u16 = 60;
        let mtu = CONFIG_CLIENT_MTU.saturating_sub(IPV4_HEADER_SIZE);
        mtu.saturating_sub(MAX_TCP_HEADER_SIZE) as u32
    }
}

#[cfg(windows)]
mod sys {
    use super::*;

    pub struct TcpSocket {
        socket: StdTcpSocket,
    }

    impl TcpSocket {
        pub fn new() -> io::Result<Self> {
            Ok(Self {
                socket: StdTcpSocket::new_v4()?,
            })
        }

        pub async fn connect(self, addr: SocketAddr) -> io::Result<TcpStream> {
            self.socket.connect(addr).await
        }
    }
}

pub use sys::*;