Skip to content

feat(wind-tuic): add SimpleUdpChannel to replace AbstractUdpSocket#5

Closed
Itsusinn wants to merge 3 commits intomainfrom
feature/simple-udp-channel
Closed

feat(wind-tuic): add SimpleUdpChannel to replace AbstractUdpSocket#5
Itsusinn wants to merge 3 commits intomainfrom
feature/simple-udp-channel

Conversation

@Itsusinn
Copy link
Member

Summary

  • Adds SimpleUdpPacket, SimpleUdpChannel, and SimpleUdpChannelTx types in a new wind_tuic::simple_udp module
  • Adds TuicOutbound::handle_udp_simple() which wires up a crossfire-based channel so callers receive reassembled UDP packets without implementing the AbstractUdpSocket trait
  • Exposes pub mod simple_udp from wind_tuic::lib

Motivation

AbstractUdpSocket requires implementors to provide a full socket abstraction (poll-based send/recv), which is more complex than most integration use-cases need. A simple bounded async channel of UdpPacket values is easier to wire into existing async runtimes and proxy stacks.

Changes

  • crates/wind-tuic/src/simple_udp.rs — new module with channel types
  • crates/wind-tuic/src/outbound.rshandle_udp_simple() method; fixed BbrConfigBbr3Config
  • crates/wind-tuic/src/lib.rs — expose simple_udp module
  • crates/wind-tuic/src/proto/udp_stream.rs — fixed frag_total type coercion (u64::from(...))
  • crates/wind-core/src/udp.rs — define UdpPoller trait unconditionally (removed #[cfg(feature = "quic")] guard that caused missing-trait errors)

This commit introduces a simplified UDP abstraction to replace the complex
AbstractUdpSocket trait from wind-core. The new implementation provides:

- SimpleUdpPacket: Basic UDP packet structure (source, target, payload)
- SimpleUdpChannel: Bidirectional channel for packet transmission
- SimpleUdpChannelTx: Transmitter side (supports Clone)
- handle_udp_simple(): New method in TuicOutbound for easy UDP handling

Benefits:
- Much simpler API compared to AbstractUdpSocket trait
- Uses standard crossfire channels instead of custom traits
- Easier integration with existing async code
- Reduced complexity for UDP relay implementations

The implementation maintains compatibility with existing UdpStream while
providing a more ergonomic interface for UDP packet handling.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a simplified UDP integration surface for wind-tuic by adding channel-based UDP packet types, and wires TUIC outbound UDP handling toward that new abstraction. It also includes several formatting/cleanup tweaks, a UDP fragmentation fix, and CI/workspace manifest adjustments.

Changes:

  • Add wind_tuic::simple_udp module (SimpleUdpPacket, SimpleUdpChannel, SimpleUdpChannelTx) and export it from wind-tuic.
  • Add TuicOutbound::handle_udp_simple() intended to bridge TUIC UDP reassembly into a bounded async channel.
  • Adjust QUIC/UDP plumbing and manifests (e.g., UdpPoller availability, quinn config), plus minor proto formatting and a frag_total type coercion fix.

Reviewed changes

Copilot reviewed 14 out of 19 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
crates/wind/Cargo.toml Switches from workspace-inherited package metadata/dependency versions to explicit values / path-only deps.
crates/wind-tuic/src/task.rs Whitespace-only formatting change.
crates/wind-tuic/src/simple_udp.rs Adds new simplified UDP channel + packet types.
crates/wind-tuic/src/proto/udp_stream.rs Fixes fragment count comparison type (u64::from(...)) and various formatting cleanups.
crates/wind-tuic/src/proto/tests.rs Formatting-only change in test struct literal.
crates/wind-tuic/src/proto/mod.rs Improves decode helper chaining/formatting (and keeps “incomplete” error context).
crates/wind-tuic/src/proto/header.rs Formatting-only changes and expanded ensure! calls.
crates/wind-tuic/src/proto/error.rs Formatting-only changes to error fields.
crates/wind-tuic/src/proto/cmd.rs Formatting-only changes to command fields/decoding/tests.
crates/wind-tuic/src/proto/addr.rs Formatting-only changes and expanded ensure! call.
crates/wind-tuic/src/outbound.rs Adds handle_udp_simple(), changes congestion controller config, minor formatting adjustments.
crates/wind-tuic/src/lib.rs Exposes pub mod simple_udp;.
crates/wind-tuic/src/inbound.rs Formatting-only changes (whitespace/alignment).
crates/wind-tuic/Cargo.toml Moves to explicit package metadata, switches quinn dependency to workspace = true and adds qlog.
crates/wind-socks/Cargo.toml Moves to explicit package metadata; path-only dep on wind-core.
crates/wind-core/src/udp.rs Makes UdpPoller trait unconditional (removes feature guards) and formatting cleanups.
crates/wind-core/Cargo.toml Moves to explicit package metadata; switches quinn to workspace = true.
.github/workflows/ci.yml Adds CI via reusable workflow.
.github/target.toml Adds CI target matrix configuration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +483 to +487
self.ctx.tasks.spawn(async move {
loop {
tokio::select! {
_ = cancel.cancelled() => {
break;
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spawned task exits on cancellation / channel close but doesn’t perform any cleanup: udp_session entry removal, fragment GC ticking, or connection.drop_udp(assoc_id)/udp_stream.close(). This can leak UDP associations and fragment state over time; mirror the cleanup/GC behavior from handle_udp for this path as well.

Copilot uses AI. Check for mistakes.
Comment on lines +494 to +498
wind_core::types::TargetAddr::IPv4(ip, port) => SocketAddr::from((*ip, *port)),
wind_core::types::TargetAddr::IPv6(ip, port) => SocketAddr::from((*ip, *port)),
_ => continue,
};

Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conversion drops TargetAddr::Domain packets (_ => continue) with no logging or error propagation. If TUIC can deliver UDP packets with domain targets, they’ll be silently lost; either support domains in SimpleUdpPacket (e.g., use TargetAddr instead of SocketAddr) or log/return an explicit error so callers can diagnose it.

Suggested change
wind_core::types::TargetAddr::IPv4(ip, port) => SocketAddr::from((*ip, *port)),
wind_core::types::TargetAddr::IPv6(ip, port) => SocketAddr::from((*ip, *port)),
_ => continue,
};
wind_core::types::TargetAddr::IPv4(ip, port) => {
Some(SocketAddr::from((*ip, *port)))
}
wind_core::types::TargetAddr::IPv6(ip, port) => {
Some(SocketAddr::from((*ip, *port)))
}
wind_core::types::TargetAddr::Domain(domain, port) => {
warn!(
target: "[OUT]",
"Dropping UDP packet with domain target in simple channel: {}:{}",
domain,
port
);
None
}
};
let Some(target) = target else {
continue;
};

Copilot uses AI. Check for mistakes.
tokio = { version = "1", default-features = false, features = ["net"] }
tokio-util = { version = "0.7", features = ["codec"] }
quinn = { version = "0.11", default-features = false, features = ["runtime-tokio"]}
quinn = { workspace = true, default-features = false, features = ["runtime-tokio", "qlog"] }
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

workspace = true for quinn requires a [workspace.dependencies] quinn = ... entry in the root Cargo.toml. The current root manifest has no workspace dependencies section, so this will not resolve and will break builds; either add the workspace dependency or specify a concrete version here.

Suggested change
quinn = { workspace = true, default-features = false, features = ["runtime-tokio", "qlog"] }
quinn = { version = "0.11", default-features = false, features = ["runtime-tokio", "qlog"] }

Copilot uses AI. Check for mistakes.
tokio-util = { version = "0.7", features = ["rt"] }

quinn = { version = "0.11", default-features = false, optional = true }
quinn = { workspace = true, default-features = false, optional = true }
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quinn = { workspace = true, ... } won’t resolve unless the root Cargo.toml defines quinn under [workspace.dependencies]. Since the workspace manifest currently only has [workspace.package], this change will cause a dependency resolution error; add the workspace dependency or pin a version in this crate.

Suggested change
quinn = { workspace = true, default-features = false, optional = true }
quinn = { version = "0.11", default-features = false, optional = true }

Copilot uses AI. Check for mistakes.

jobs:
build:
uses: rust-proxy/workflows/.github/workflows/rust-build.yml@main
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reusable workflow is referenced by a moving ref (@main). For reproducible CI and to avoid unexpected breakages, pin this to an immutable tag or commit SHA (and update deliberately when needed).

Suggested change
uses: rust-proxy/workflows/.github/workflows/rust-build.yml@main
uses: rust-proxy/workflows/.github/workflows/rust-build.yml@0123456789abcdef0123456789abcdef01234567

Copilot uses AI. Check for mistakes.
let mut transport_config = quinn::TransportConfig::default();
transport_config
.congestion_controller_factory(Arc::new(quinn::congestion::BbrConfig::default()))
.congestion_controller_factory(Arc::new(quinn::congestion::Bbr3Config::default()))
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bbr3Config may not exist in the currently locked quinn version (Cargo.lock shows quinn 0.11.9). This will fail to compile unless the workspace is updated to a quinn release that provides Bbr3Config, or this is switched back to BbrConfig/another supported congestion controller.

Suggested change
.congestion_controller_factory(Arc::new(quinn::congestion::Bbr3Config::default()))
.congestion_controller_factory(Arc::new(quinn::congestion::BbrConfig::default()))

Copilot uses AI. Check for mistakes.
Comment on lines +463 to +466
pub async fn handle_udp_simple(
&self,
assoc_id: u16,
) -> Result<(crate::simple_udp::SimpleUdpChannel, crate::simple_udp::SimpleUdpChannelTx), Error> {
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle_udp_simple takes an assoc_id from the caller, but handle_udp allocates one internally via udp_assoc_counter. Requiring the caller to provide a unique association ID makes misuse (collisions / overwriting udp_session) much more likely; consider allocating the ID inside this method (or returning the chosen ID) to keep the API safe and consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +472 to +478
let connection = self.connection.clone();
let (channel, channel_tx) = SimpleUdpChannel::new(128);

// Use crossfire channel compatible with UdpStream
let (wind_tx, wind_rx) = crossfire::mpmc::bounded_async::<wind_core::udp::UdpPacket>(128);
let udp_stream = Arc::new(UdpStream::new(connection.clone(), assoc_id, wind_tx));
self.udp_session.insert(assoc_id, udp_stream.clone()).await;
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle_udp_simple creates SimpleUdpChannel/SimpleUdpChannelTx, but there is no task that drains packets sent by the caller (via channel.to_remote_tx) and forwards them to udp_stream.send_packet(...). As written, local→remote UDP traffic has no path to the TUIC connection, so the channel is effectively receive-only.

Copilot uses AI. Check for mistakes.
- Add periodic GC tick (collect_garbage) to evict stale fragment state
- Add to_remote_rx arm: forward caller-sent packets to the TUIC connection
- Remove assoc from udp_session cache on task exit
- Call connection.drop_udp(assoc_id) on exit to send Dissociate to peer
- Log channel-close and cancellation events consistently
@Itsusinn
Copy link
Member Author

@Itsusinn Itsusinn closed this Mar 24, 2026
@Itsusinn Itsusinn deleted the feature/simple-udp-channel branch March 24, 2026 06:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants