{"record":{"id":"abe5983077125662","repo":"rathole-org/rathole","slug":"expect-udp-traffic-please-check-the-configuration","errorCode":null,"errorMessage":"Expect UDP traffic. Please check the configuration.","messagePattern":"Expect UDP traffic\\. Please check the configuration\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/client.rs","lineNumber":228,"sourceCode":"\n    Ok(conn)\n}\n\nasync fn run_data_channel<T: Transport>(args: Arc<RunDataChannelArgs<T>>) -> Result<()> {\n    // Do the handshake\n    let mut conn = do_data_channel_handshake(args.clone()).await?;\n\n    // Forward\n    match read_data_cmd(&mut conn).await? {\n        DataChannelCmd::StartForwardTcp => {\n            if args.service.service_type != ServiceType::Tcp {\n                bail!(\"Expect TCP traffic. Please check the configuration.\")\n            }\n            run_data_channel_for_tcp::<T>(conn, &args.service.local_addr).await?;\n        }\n        DataChannelCmd::StartForwardUdp => {\n            if args.service.service_type != ServiceType::Udp {\n                bail!(\"Expect UDP traffic. Please check the configuration.\")\n            }\n            run_data_channel_for_udp::<T>(conn, &args.service.local_addr, args.service.prefer_ipv6).await?;\n        }\n    }\n    Ok(())\n}\n\n// Simply copying back and forth for TCP\n#[instrument(skip(conn))]\nasync fn run_data_channel_for_tcp<T: Transport>(\n    mut conn: T::Stream,\n    local_addr: &str,\n) -> Result<()> {\n    debug!(\"New data channel starts forwarding\");\n\n    let mut local = TcpStream::connect(local_addr)\n        .await\n        .with_context(|| format!(\"Failed to connect to {}\", local_addr))?;","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/rathole-org/rathole/blob/a292f7ed5402f840415fc6a53827da2f34337856/src/client.rs#L210-L246","documentation":"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.","triggerScenarios":"Server sends DataChannelCmd::StartForwardUdp while the client's service entry has service_type != ServiceType::Udp (i.e. it is TCP).","commonSituations":"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.","solutions":["Align the service type to udp on both the client and server configs","Confirm the local service really listens on UDP before declaring type = \"udp\"","Restart both client and server after editing configs"],"exampleFix":"// before (server.toml)\n[server.services.ssh]\nbind_addr = \"0.0.0.0:6022\"\ntype = \"udp\"\n\n// after\n[server.services.ssh]\nbind_addr = \"0.0.0.0:6022\"\ntype = \"tcp\"   # match the client's tcp local service","handlingStrategy":"validation","validationCode":"// Confirm the local service answers on UDP before declaring type = \"udp\":\nimport socket\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.settimeout(2)\ntry:\n    s.sendto(b'ping', ('127.0.0.1', 53)); s.recvfrom(512); print('UDP OK')\nexcept OSError as e:\n    print('Not a UDP service:', e)\nfinally:\n    s.close()","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Set type = \"udp\" on BOTH client and server for UDP services","Audit both TOML files together whenever service types change","Restart both ends after changing service type so the tunnel renegotiates"],"tags":["rust","configuration","udp","service-type-mismatch"],"backgroundTag":"invalid-config-value","analyzedSha":"a292f7ed5402f840415fc6a53827da2f34337856","analyzedAt":"2026-09-07T09:56:55.739Z","contentChangedAt":"2026-09-07T09:56:55.739Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}