rathole-org/rathole · error

Expect UDP traffic. Please check the configuration.

Error message

Expect UDP traffic. Please check the configuration.

What it means

Mirror of the TCP case: the server sent DataChannelCmd::StartForwardUdp but the client's configured service is not UDP (it is TCP). run_data_channel bails because it cannot forward UDP datagrams through a TCP-configured local service. This always reflects a client/server config mismatch.

Solutions

  1. Align the service type to udp on both the client and server configs
  2. Confirm the local service really listens on UDP before declaring type = "udp"
  3. Restart both client and server after editing configs

Example fix

// before (server.toml)
[server.services.ssh]
bind_addr = "0.0.0.0:6022"
type = "udp"

// after
[server.services.ssh]
bind_addr = "0.0.0.0:6022"
type = "tcp"   # match the client's tcp local service
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the local service answers on UDP before declaring type = "udp":
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.settimeout(2)
try:
    s.sendto(b'ping', ('127.0.0.1', 53)); s.recvfrom(512); print('UDP OK')
except OSError as e:
    print('Not a UDP service:', e)
finally:
    s.close()

Prevention

When it happens

Trigger: Server sends DataChannelCmd::StartForwardUdp while the client's service entry has service_type != ServiceType::Udp (i.e. it is TCP).

Common situations: Exposing an SSH (TCP) service but the server-side service is declared with type = "udp"; typo'd or divergent TOML between the two ends of the tunnel.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of rathole-org/rathole@a292f7ed54 (2026-09-07). Data as JSON: /api/errors/abe5983077125662. Report an issue: GitHub.

Appendix: source

Thrown at src/client.rs:228

    Ok(conn)
}

async fn run_data_channel<T: Transport>(args: Arc<RunDataChannelArgs<T>>) -> Result<()> {
    // Do the handshake
    let mut conn = do_data_channel_handshake(args.clone()).await?;

    // Forward
    match read_data_cmd(&mut conn).await? {
        DataChannelCmd::StartForwardTcp => {
            if args.service.service_type != ServiceType::Tcp {
                bail!("Expect TCP traffic. Please check the configuration.")
            }
            run_data_channel_for_tcp::<T>(conn, &args.service.local_addr).await?;
        }
        DataChannelCmd::StartForwardUdp => {
            if args.service.service_type != ServiceType::Udp {
                bail!("Expect UDP traffic. Please check the configuration.")
            }
            run_data_channel_for_udp::<T>(conn, &args.service.local_addr, args.service.prefer_ipv6).await?;
        }
    }
    Ok(())
}

// Simply copying back and forth for TCP
#[instrument(skip(conn))]
async fn run_data_channel_for_tcp<T: Transport>(
    mut conn: T::Stream,
    local_addr: &str,
) -> Result<()> {
    debug!("New data channel starts forwarding");

    let mut local = TcpStream::connect(local_addr)
        .await
        .with_context(|| format!("Failed to connect to {}", local_addr))?;

View on GitHub (pinned to a292f7ed54)