rathole-org/rathole · error

Expect TCP traffic. Please check the configuration.

Error message

Expect TCP traffic. Please check the configuration.

What it means

The client's data channel completed the handshake and received a StartForwardTcp command from the server, but the local service configured for this channel is not of type TCP. run_data_channel bails out because forwarding TCP traffic to a non-TCP local service would be wrong. This indicates a mismatch between the client's and server's service type configuration.

Solutions

  1. Make the service type consistent: set the service to TCP on both client and server configs (or use udp on both)
  2. Check that the local_addr actually speaks TCP before declaring it a TCP service
  3. Restart both ends after fixing the config so the control channel re-negotiates

Example fix

// before (client.toml)
[client.services.dns]
local_addr = "127.0.0.1:53"   # inferred udp

// after
[client.services.dns]
local_addr = "127.0.0.1:53"
type = "tcp"                   # match the server's service type
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the local service listens on TCP before declaring it as a tcp service:
import socket
s = socket.socket(); s.settimeout(2)
try:
    s.connect(('127.0.0.1', 22)); print('TCP OK')
except OSError as e:
    print('Not a TCP service:', e)
finally:
    s.close()

Prevention

When it happens

Trigger: Server sends DataChannelCmd::StartForwardTcp while the client's service entry has service_type != ServiceType::Tcp (i.e. it is UDP), detected in run_data_channel after do_data_channel_handshake.

Common situations: The same service name is declared as UDP on the client but TCP on the server; copy-pasting a config where the remote side exposes a TCP port but the local service (e.g. a DNS or game server) is UDP.

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/3257b423dd235024. Report an issue: GitHub.

Appendix: source

Thrown at src/client.rs:222

    // Send nonce
    let v: &[u8; HASH_WIDTH_IN_BYTES] = args.session_key[..].try_into().unwrap();
    let hello = Hello::DataChannelHello(CURRENT_PROTO_VERSION, v.to_owned());
    conn.write_all(&bincode::serialize(&hello).unwrap()).await?;
    conn.flush().await?;

    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,

View on GitHub (pinned to a292f7ed54)