udp-over-tcp

repository·main·Indexed 20 days ago

https://github.com/mullvad/udp-over-tcp

A utility and Rust library for tunneling UDP datagrams over TCP streams to bypass network restrictions. It provides two primary components: udp2tcp, which forwards incoming UDP datagrams over a TCP stream for a single peer, and tcp2udp, a server that accepts multiple TCP connections and translates them into UDP datagrams. The project uses a simple framing protocol where each datagram is prefixed with a 16-bit unsigned integer specifying its length.

Tokens
4.2K
Snippets
14
Records
19
Agent score
66%

What's inside udp-over-tcp

  1. Overview of udp-over-tcp

    main

    The udp-over-tcp project provides a library and binaries for tunneling UDP datagrams over a TCP stream. This is useful for protocols that require UDP in environments where only TCP traffic is permitted.

    The project consists of two primary components:

    1. udp2tcp: Forwards incoming UDP datagrams over a TCP stream. The return TCP stream is translated back into datagrams and sent out via UDP. It is designed to handle traffic from a single peer (the UDP socket connects to the peer address of the first incoming datagram). It can be used as a standalone binary or a Rust library.

    2. tcp2udp: A server component that accepts incoming TCP connections and translates the stream into UDP datagrams forwarded to a destination specified during setup. A single tcp2udp server can service multiple udp2tcp clients by creating a new UDP socket for each incoming TCP connection.

  2. The udp-over-tcp wire protocol

    main
    The data format within the TCP stream is a simple framing protocol. Each UDP datagram is preceded by a 16-bit unsigned integer in big-endian byte order, which specifies the length of the following datagram.
  3. How udp-over-tcp works: the two components

    main

    The udp-over-tcp library provides two distinct mechanisms for tunneling UDP datagrams over a TCP stream, depending on whether you need to forward traffic from a client or act as a server:

    1. udp2tcp (Client-side/Forwarder): Forwards incoming UDP datagrams over a TCP stream. The incoming TCP stream is translated back into datagrams and sent out via UDP.

      • Limitation: A single Udp2Tcp instance handles traffic from a single peer only. The UDP socket is connected to the peer address of the first incoming datagram.
      • Usage: Can be used as a standalone binary or integrated into Rust programs.
    2. tcp2udp (Server-side): Accepts incoming TCP connections and translates the incoming stream into UDP datagrams, forwarding them to a destination specified during setup.

      • Capability: A single tcp2udp server can service many udp2tcp clients because it creates a new UDP socket for each incoming TCP connection.
      • Usage: Designed primarily as a standalone executable for servers, but can also be used as a Rust library.
    # Example of running the tcp2udp server
    # This listens on 10.0.0.1:5001/TCP and forwards to 127.0.0.1:51820/UDP
    RUST_LOG=debug tcp2udp \
        --tcp-listen 10.0.0.0:5001 \
        --udp-forward 127.0.0.1:51820
  4. Use Udp2Tcp as a Rust library

    main

    You can integrate udp2tcp into a Rust application using the Udp2Tcp struct. This component binds to a local UDP address, connects to a target TCP address, and forwards traffic between them. The UDP socket will automatically connect to the address of the first incoming datagram received.

    Key methods:

    • Udp2Tcp::new(udp_listen_addr, tcp_forward_addr, options): Initializes the forwarder.
    • local_udp_addr(): Returns the actual local UDP address the socket is bound to (useful if port 0 was used to request a random port).
    • run(): Starts the forwarding loop and runs until the TCP connection closes or an error occurs.
    let udp_listen_addr = "127.0.0.1:0".parse().unwrap();
    let tcp_forward_addr = "1.2.3.4:9000".parse().unwrap();
    
    // Create a UDP -> TCP forwarder. This will connect the TCP socket
    // to `tcp_forward_addr`
    let udp2tcp = udp_over_tcp::Udp2Tcp::new(
        udp_listen_addr,
        tcp_forward_addr,
        udp_over_tcp::TcpOptions::default(),
    )
    .await?;
    
    // Read out which address the UDP actually bound to. Useful if you specified port
    // zero to get a random port from the OS.
    let local_udp_addr = udp2tcp.local_udp_addr()?;
    
    spin_up_some_udp_thing(local_udp_addr);
    
    // Run the forwarder until the TCP socket disconnects or an error happens.
    udp2tcp.run().await?;
  5. Run the tcp2udp server binary

    main

    Use the tcp2udp binary to create a server that listens for TCP connections and forwards the resulting stream to a specific UDP destination.

    Environment Variables:

    • RUST_LOG: Sets the logging level (requires the env_logger feature to be enabled during build). Example: RUST_LOG=debug.
    • REDACT_LOGS=1: When set, redacts peer IP addresses from the logs to protect sensitive user data while keeping logging active.
    # Listen on 10.0.0.1:5001/TCP and forward to 127.0.0.1:51820/UDP
    RUST_LOG=debug tcp2udp \
        --tcp-listen 10.0.0.0:5001 \
        --udp-forward 127.0.0.1:51820
  6. Access raw TCP socket descriptors from Udp2Tcp

    main

    Depending on your target platform, you can access the underlying raw socket used for the TCP forwarding connection:

    • Unix: Use remote_tcp_fd() to get the RawFd.
    • Windows: Use remote_tcp_socket() to get the RawSocket.
    #[cfg(unix)]
    let fd = udp2tcp.remote_tcp_fd();
    
    #[cfg(windows)]
    let socket = udp2tcp.remote_tcp_socket();
  7. Configure a `tcp2udp` session with `Options`

    main

    The Options struct defines how the tcp2udp server should listen for incoming TCP traffic and where to forward it via UDP. Because Options is non_exhaustive, you should always instantiate it using Options::new and then modify its public fields as needed.

    Fields

    • tcp_listen_addrs: A Vec<SocketAddr> containing the IP and TCP port(s) to listen on. Supports multiple sockets.
    • udp_forward_addr: The SocketAddr (IP and UDP port) where all traffic will be forwarded.
    • udp_bind_ip: (Optional) The local IP address to bind the outgoing UDP socket to. If not set, it defaults to 0.0.0.0 for IPv4 or :: for IPv6.
    • tcp_options: Configuration for the underlying TCP sockets (e.pass nodelay, recv_timeout).
    • statsd_host: (If statsd feature is enabled) The SocketAddr of the host to send metrics to.
    use std::net::{IpAddr, Ipv4Addr, SocketAddrV4, SocketAddr};
    use udp_over_tcp::tcp2udp::Options;
    
    let mut options = Options::new(
        // Listen on 127.0.0.1:1234/TCP
        vec![SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 1234))],
        // Forward to 192.0.2.15:5001/UDP
        SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 15), 5001)),
    );
    
    // Bind the local UDP socket to the loopback interface
    options.udp_bind_ip = Some(IpAddr::V4(Ipv4Addr::LOCALHOST));
  8. Integrate Udp2Tcp into a Rust program

    main

    To use udp2tcp as a library, use the Udp2Tcp::new method. This creates a forwarder that connects a TCP socket to a remote address and binds a local UDP socket. The UDP socket will automatically connect to the address of the first incoming datagram it receives.

    Steps:

    1. Define your local UDP listen address (use :0 for a random port) and the remote TCP target address.
    2. Initialize Udp2Tcp with TcpOptions::default().
    3. Retrieve the actual local UDP address using .local_udp_addr()?.
    4. Start the forwarder with .run().await?.
    let udp_listen_addr = "127.0.0.1:0".parse().unwrap();
    let tcp_forward_addr = "1.2.3.4:9000".parse().unwrap();
    
    // Create a UDP -> TCP forwarder
    let udp2tcp = udp_over_tcp::Udp2Tcp::new(
        udp_listen_addr,
        tcp_forward_addr,
        udp_over_tcp::TcpOptions::default(),
    )
    .await?;
    
    // Read out which address the UDP actually bound to
    let local_udp_addr = udp2tcp.local_udp_addr()?;
    
    // Run the forwarder until the TCP socket disconnects or an error happens
    udp2tcp.run().await?;
  9. Configure TCP options with TcpOptions

    main
    The TcpOptions struct allows you to configure the behavior of the underlying TCP stream. It is passed to the Udp2Tcp::new constructor. Errors during application of these options are returned as ApplyTcpOptionsError.
  10. Use Udp2Tcp to forward UDP traffic over TCP

    main

    The Udp2Tcp struct allows you to listen on a local UDP address and forward incoming datagrams to a remote TCP address.

    To use it, follow these steps:

    1. Initialize the instance using Udp2Tcp::new with the desired UDP listen address, the remote TCP forward address, and a TcpOptions configuration.
    2. Call .run() on the instance to start the forwarding process.

    Note that Udp2Tcp::new only sets up the sockets; it does not start forwarding traffic. The run method is responsible for waiting for the first UDP datagram to identify the peer, connecting to the remote TCP address, and then entering the forwarding loop. The loop continues until the TCP socket is closed or an error occurs.

    use std::net::SocketAddr;
    use mullvad::udp_over_tcp::Udp2Tcp;
    // Assuming TcpOptions is available from the crate
    
    let udp_addr: SocketAddr = "[::1]:0".parse().unwrap(); // Port 0 lets OS pick a port
    let tcp_forward_addr: SocketAddr = "[::1]:8080".parse().unwrap();
    let options = crate::TcpOptions::default(); // Use appropriate constructor
    
    let udp2tcp = Udp2Tcp::new(udp_addr, tcp_forward_addr, options).await?;
    
    // If you used port 0, check the actual bound address
    let actual_addr = udp2tcp.local_udp_addr()?;
    println!("Listening on UDP: {}", actual_addr);
    
    // Start forwarding
    udp2tcp.run().await?;
  11. Run the `tcp2udp` server with `run()`

    main

    The run function starts the tcp2udp server. It sets up listening sockets for every address provided in Options::tcp_listen_addrs.

    • If binding a listening socket fails, run returns a Tcp2UdpError immediately.
    • Once running, the function continues indefinitely to accept incoming connections and forward them to UDP.
    • Errors occurring during individual connection processing are logged but do not stop the server.
    // Assuming options is already configured
    // This function returns Result<Infallible, Tcp2UdpError>
    // It will run indefinitely unless it encounters a fatal error during setup.
    run(options).await.unwrap();
  12. Get the local UDP address from Udp2Tcp

    main

    If you initialized Udp2Tcp::new with a port of 0 in the udp_listen_addr, the operating system will assign a random available port. You can retrieve the actual address and port the instance is bound to by calling local_udp_addr().

    let actual_addr = udp2tcp.local_udp_addr()?;