Skip to content

Add experimental outbound UDP to cloudflare:sockets - #8140

Draft
deregtd wants to merge 6 commits into
cloudflare:mainfrom
deregtd:ddr/outbound-udp
Draft

deregtd wants to merge 6 commits into
cloudflare:mainfrom
deregtd:ddr/outbound-udp

Conversation

@deregtd

@deregtd deregtd commented Nov 2, 2026 •

Copy link
Copy Markdown

User stories affected or enabled by this change

Workers and Durable Objects can send and receive outbound UDP datagrams, including send-only applications that write and close without waiting for a reply.

For example, a Worker can open a fixed UDP destination, write one Uint8Array per datagram, and read response datagrams when its protocol needs them. Successful writes mean local backend submission, not peer acknowledgement or delivery.

APIs/interfaces created, changed, or removed

  • Add experimental connect(address, { protocol: 'udp' }) under outbound_udp_sockets; TCP remains the default.
  • Expose value-mode readable/writable streams of Uint8Array datagrams to one fixed destination.
  • Add a routed UDP-open event, capability datagram RPC, and a standalone Rust network-service implementation with DNS and PeerFilter handling.
  • Change only experimental SocketOptions type output. Existing Datagram ingress remains unchanged without the flag; opting in uses Uint8Array values.

The internal embedding companion is Edgeworker !15871.

Architecture choices and reasoning

UDP has no connection handshake. Await each packet's backend result so immediate write errors reach the caller, and retain normal WritableStream failure behavior. Integrate Durable Object output gates; unsupported embedding backends reject explicitly.

The standalone implementation accepts payloads up to 65,507 bytes, which is neither a network delivery guarantee nor an embedding limit. Edgeworker's companion has a separate 1,500-byte backend cap. Stable type snapshots remain unchanged. Type generation includes runtime libraries and fails on startup errors instead of accepting an empty snapshot.

Validation and launch boundary

Recorded qualification includes five repeated Worker/DO collector E2E runs, C++ socket tests, ingress regressions, both stream implementations, TypeScript usage, output gates, policy, snapshot/cleanup, and delayed RPC cases. Local collectors verified empty/binary/sliced 9,000-byte payloads and close/reply behavior. External tests recorded 20 DNS responses and 60 send-only receipts; external large-payload delivery is not claimed because path limits also affect native UDP.

This remains an experimental API/backend draft. See docs/outbound-udp.md; review the embedding and production rollout separately.

Gate connect(address, { protocol: 'udp' }) and Uint8Array value streams
behind outbound_udp_sockets. Preserve the existing TCP defaults and
legacy UDP ingress behavior for Workers without the flag. Stable type
snapshots remain unchanged; the new options are experimental only.

Route UDP setup and datagrams through owning RPC capabilities, respect
Durable Object output gates, and implement the standalone Rust network
backend with destination resolution and PeerFilter checks. Preserve
empty packets, sliced buffers, byte snapshots and close draining.

Include runtime libraries in type generation and reject startup failure.
Add collector E2E and API/type coverage for both stream implementations.
Relevant suites pass five runs, including local 9000-byte packets.
External DNS and send-only receipt checks pass at smaller payloads;
network paths and embedding backends can impose tighter size limits.
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@deregtd

deregtd commented Nov 2, 2026

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Nov 2, 2026
@deregtd deregtd changed the title Add experimental connected UDP to cloudflare:sockets Add experimental outbound UDP to cloudflare:sockets Nov 2, 2026
@codecov-commenter

codecov-commenter commented Nov 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 38.61940% with 329 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.83%. Comparing base (1a5332d) to head (802ba90).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
src/workerd/api/sockets.c++ 36.65% 130 Missing and 10 partials ⚠️
src/workerd/server/tests/udp-egress/index.mjs 0.00% 81 Missing ⚠️
src/workerd/server/server/services/udp.rs 0.00% 47 Missing ⚠️
...c/workerd/server/factory/worker-factory-listen.c++ 0.00% 21 Missing ⚠️
src/workerd/api/sockets.h 27.77% 11 Missing and 2 partials ⚠️
src/workerd/api/sockets-test.c++ 92.80% 0 Missing and 9 partials ⚠️
src/workerd/server/server/bridge.rs 0.00% 5 Missing ⚠️
src/workerd/server/tests/udp-connect/index.mjs 0.00% 4 Missing ⚠️
src/workerd/server/factory/worker-factory-rpc.c++ 0.00% 3 Missing ⚠️
...kerd/server/tests/udp-connect-ts-streams/index.mjs 0.00% 3 Missing ⚠️
... and 2 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8140      +/-   ##
==========================================
- Coverage   68.85%   68.83%   -0.03%     
==========================================
  Files         745      748       +3     
  Lines      194951   195462     +511     
  Branches    30272    30327      +55     
==========================================
+ Hits       134234   134542     +308     
- Misses      42936    43123     +187     
- Partials    17781    17797      +16     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Fork pull requests cannot use Bazelflare's OIDC credentials and run
their tools on the GitHub runner. The pinned x64 clang-tidy binary
requires glibc 2.38, so the Ubuntu 22.04 fallback fails before checks
can execute.

Use the standard Ubuntu 24.04 runner for this fallback. Preserve the
configured runners for trusted builds and other architectures, and
keep required job names unchanged.

Validated the reusable workflow and its callers with actionlint.
Fork jobs do not receive BAZEL_CACHE_KEY, but still attempt to upload
local results. The cache rejects these uploads, and an interrupted HTTP
response can crash Bazel with PrematureChannelClosureException (exit 37).

Disable uploads when the credential is absent, retaining public cache
reads. Authenticated jobs keep their existing behavior, and bazelflare
continues to use its own cache and executor configuration.

Validate the workflow with actionlint and exercise all eight combinations
of cache credentials, OIDC availability and requested execution backend.
See bazelbuild/bazel#32092.
Use an acknowledged RPC call for outbound datagrams so each write
waits for the backend result. Streaming calls can otherwise report
success before transport validation rejects the packet.

Preserve the existing streaming path for ingress UDP. Add a delayed
transport failure regression test and document local send completion.
Keep the compatibility flags allocated upstream and move the draft
outbound UDP flag to ordinal 204. This restores a mergeable PR so
the standard CI workflows can test its merge revision.

Upstream now selects Ubuntu 24 for Linux jobs, so drop the draft's
older runner override. Keep the read-only cache setting for forks.
Explain per-datagram limits and distinguish the native backend's
ceiling from embedding limits. Document how a rejected write errors
the writable stream and how applications can validate before writing
to keep a socket reusable. Preserve the original write error during
cleanup in the send-only example.
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