Skip to content

Conversation

@denysvitali
Copy link

@denysvitali denysvitali commented Jan 22, 2026

When using UDP on Fly.io, packets arrive at a public IP but the socket is bound to an internal private address. This commit ensures that responses are sent from the same IP that the client sent to, which is critical for correct routing.

The fix:

  1. Store the original destination IP (from ControlMessage.Dst) in the session
  2. Set ControlMessage.Src to that IP when sending responses

This ensures that when a client sends to Fly.io's public IP, the response is sent from that same IP, not from the internal private address.

See this report as well by @lyubchev.

I was able to get this working after using this patch.

Summary by CodeRabbit

  • Bug Fixes
    • Improved UDP packet routing by tracking original destination IPs so incoming packets and outgoing replies use the correct source/destination addresses.
  • Tests
    • Optimized test string assembly for stability and performance.
  • Chores
    • Added additional linting rules to enforce variable naming consistency across selected packages.

✏️ Tip: You can customize this high-level summary in your review settings.

When using UDP on Fly.io, packets arrive at a public IP but the socket is
bound to an internal private address. This commit ensures that responses
are sent from the same IP that the client sent to, which is critical for
correct routing.

The fix:
1. Store the original destination IP (from ControlMessage.Dst) in the session
2. Set ControlMessage.Src to that IP when sending responses

This ensures that when a client sends to Fly.io's public IP,
the response is sent from that same IP, not from the internal private address.
@denysvitali denysvitali marked this pull request as ready for review January 22, 2026 18:50
@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

📝 Walkthrough

Walkthrough

Threads the original destination IP (extracted from UDP control messages) through server connection management into sessions so outbound writes can source the original destination IP. Also adds lint rules, switches a test to use strings.Builder, and updates a client NewSession call site.

Changes

Cohort / File(s) Summary
Server: connection parameter threading
udp/server/server.go
Extract originalDstIP from UDP control message in Serve; extended getConn / getOrCreateConn to accept originalDstIP and pass it through connection creation logic
Session: store & use original destination IP
udp/server/session.go
Added originalDstIP net.IP field; updated NewSession signature; WriteMessage ensures control message Src is set to originalDstIP when applicable so responses originate from original destination
Client callsite update
udp/client.go
Inserted nil placeholder argument into server.NewSession call to satisfy new parameter ordering (no originalDstIP for outbound clients)
Tests: string building optimization
message/codes/codes_test.go
Replaced string concatenation with strings.Builder for assembling numeric code array; added strings import
Lint config changes
.golangci.yml
Added revive-based var-naming checks for selected package/file patterns (additive lint rules)

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server
    participant ConnMgr as ConnManager
    participant Session
    participant Network

    Client->>Server: UDP packet arrives (with control msg)
    Server->>Server: extract cm.Dst as originalDstIP
    Server->>ConnMgr: getConn(raddr, originalDstIP)
    ConnMgr->>ConnMgr: getOrCreateConn(..., originalDstIP)
    ConnMgr->>Session: NewSession(..., originalDstIP)
    Session->>Network: WriteMessage(..., ctlMsg with Src=originalDstIP)
    Network-->>Client: packet sent (source set to originalDstIP)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

bug

Suggested reviewers

  • jkralik

Poem

🐰 A packet hopped in with a secret to keep,
I sniffed out its home from the control heap,
I threaded it through conn and session with care,
Now replies wear the source that was waiting there,
Hooray — roundtrip hops handled neat and sweet! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: storing and using the original destination IP for UDP responses on Fly.io, which is the core fix across the modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
udp/server/session.go (1)

38-56: Fix compilation errors in NewSession calls - signature mismatch.

The change to NewSession in udp/server/session.go adds an originalDstIP parameter, but the caller in udp/client.go (lines 88–95) passes parameters in the wrong order and with type mismatches:

  • Expected: ctx, doneCtx, connection, raddr, originalDstIP, maxMessageSize, mtu, closeSocket
  • Passed: cfg.Ctx, context.Background(), l, addr, cfg.MaxMessageSize, cfg.MTU, cfg.CloseSocket

This causes: maxMessageSize (uint32) passed where originalDstIP (net.IP) is expected, and subsequent parameters are misaligned. The code will not compile.

To preserve backward compatibility for external callers, consider the suggested approach: maintain the old signature with originalDstIP optional, and add a new NewSessionWithOriginalDstIP function for callers that need to specify it. Then update internal callers like udp/client.go to either use the new function or pass nil for originalDstIP.

@jkralik
Copy link
Member

jkralik commented Jan 27, 2026

Hi @denysvitali, thank you for contributing! From my side, the changes look good, but could you please also fix the build issues?

go: downloading github.com/pion/dtls/v3 v3.0.7
go: downloading golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e
go: downloading go.uber.org/atomic v1.11.0
go: downloading github.com/dsnet/golib/memfile v1.0.0
go: downloading golang.org/x/sync v0.11.0
go: downloading golang.org/x/net v0.35.0
go: downloading github.com/pion/logging v0.2.4
go: downloading github.com/pion/transport/v3 v3.0.7
go: downloading golang.org/x/sys v0.30.0
go: downloading golang.org/x/crypto v0.33.0
# github.com/plgd-dev/go-coap/v3/udp
Error: udp/client.go:96:3: not enough arguments in call to server.NewSession
	have (context.Context, context.Context, *"github.com/plgd-dev/go-coap/v3/net".UDPConn, *"net".UDPAddr, uint32, uint16, bool)
	want (context.Context, context.Context, *"github.com/plgd-dev/go-coap/v3/net".UDPConn, *"net".UDPAddr, "net".IP, uint32, uint16, bool)
Error: Process completed with exit code 1.

Copy link
Member

@jkralik jkralik left a comment

Choose a reason for hiding this comment

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

Pls, fix build and tests.

Add nil argument for originalDstIP parameter in NewSession call in
udp/client.go that was missed in the previous commit. Also fix
golangci-lint issues: use strings.Builder for loop concatenation
and add exclusions for intentional stdlib-shadowing package names.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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.

3 participants