Skip to content

FIX: response source address on [::]-bound servers are wrong#629

Merged
jkralik merged 16 commits into
plgd-dev:masterfrom
Theoyor:fix/wildcard-address-wrong-srcaddr
Apr 2, 2026
Merged

FIX: response source address on [::]-bound servers are wrong#629
jkralik merged 16 commits into
plgd-dev:masterfrom
Theoyor:fix/wildcard-address-wrong-srcaddr

Conversation

@Theoyor

@Theoyor Theoyor commented Oct 20, 2025

Copy link
Copy Markdown
Contributor

When there are multiple addresses (a,b) on an interface and the coap-server is bound to [::] a client bound to a, sending to b received the response from a. This causes severe issues.

Fixed in this PR handing over the control message to the response and inverting src and destination. I can not add a unit test for this case, as multiple addresses would need to be created in the test environment.

@jkralik can you have a look?

Summary by CodeRabbit

  • Refactor

    • Centralized attachment and reuse of control information (interface index and local address) for incoming and outgoing UDP messages.
    • Server now tracks connections per remote/local address pair, improving per-address connection handling and response routing.
  • Chores

    • Added IDE launch configuration for local development.
    • Expanded ignore rules to include IDE files.

@coderabbitai

coderabbitai Bot commented Oct 20, 2025

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Client Conn stores interface index and local IP and centralizes attaching control information to outgoing messages. Server now tracks connections per (remote, local) UDP address by propagating laddr through creation and lookup paths. Tests and editor configs updated accordingly.

Changes

Cohort / File(s) Summary
UDP Client: control info
udp/client/conn.go
Added atomic localAddr and interfaceIndex fields on Conn. Introduced upsertControlInformation(msg *pool.Message) to attach ControlMessage (IfIndex, Src) to outgoing messages. Replaced upsertInterfaceToMessage usages and centralized control info handling; capture incoming IfIndex and local destination for later sends.
UDP Server: per-(raddr,laddr) Conn management
udp/server/server.go
Refactored APIs and flows to accept explicit local UDP address (laddr) through getConn, getOrCreateConn, and NewConn. Connection lookup/creation keys now include both remote and local addresses; per-address blockwise/connection creation logic adjusted.
Tests
udp/server_test.go
Updated NewConn call sites to pass the listening connection's local address (l.LocalAddr().(*net.UDPAddr)) to match new signature.
Editor / IDE configs
.gitignore, .vscode/launch.json
Added .idea/ to .gitignore. Added a VSCode launch configuration for running examples/observe/server.

Sequence Diagram

sequenceDiagram
    participant Client as Client
    participant Network as Network
    participant Server as Server
    participant Conn as Conn\n(per raddr,laddr)

    Client->>Network: UDP request
    Network->>Server: Deliver packet + ControlMessage
    Server->>Server: extract IfIndex and dest from ControlMessage
    Server->>Conn: getOrCreateConn(raddr, laddr)
    Conn->>Conn: store interfaceIndex (atomic) and localAddr (atomic)
    Server->>Conn: ProcessReceivedMessage
    Conn->>Conn: cache interface/local info
    Server->>Conn: Prepare response
    Conn->>Conn: upsertControlInformation(msg) — attach IfIndex/Src
    Conn->>Network: Send response with control information
    Network->>Client: UDP response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

bug

Suggested reviewers

  • jkralik
  • jlevesy

Poem

🐰 I hopped through packets, soft and spry,

I tucked an IfIndex where it lies,
Kept the local IP close and near,
Now responses find the path clear,
Hooray — connections hum with cheer!

🚥 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 directly and clearly describes the main change: fixing the response source address on wildcard IPv6-bound servers, which is exactly what the PR implements.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

CodeRabbit can use TruffleHog to scan for secrets in your code with verification capabilities.

Add a TruffleHog config file (e.g. trufflehog-config.yml, trufflehog.yml) to your project to customize detectors and scanning behavior. The tool runs only when a config file is present.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f73dcbd and a145b7c.

📒 Files selected for processing (2)
  • net/connUDP.go (1 hunks)
  • udp/client/conn.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
udp/client/conn.go (1)
net/connUDP.go (1)
  • ControlMessage (27-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Fuzzing
  • GitHub Check: test-for-fork (windows-latest)
🔇 Additional comments (1)
net/connUDP.go (1)

60-65: LGTM! Clean implementation of address swap.

The nil check prevents panics and the tuple assignment correctly swaps the source and destination addresses in a single operation.

Comment thread udp/client/conn.go Outdated
Comment thread udp/client/conn.go Outdated
@projectgus

Copy link
Copy Markdown

@Theoyor FWIW I can confirm this fixes the described issue for me, thanks for submitting it.

Unfortunately Observer notifications also have the same problem, and they're not fixed by this PR because they use a different code path to build the outgoing message.

I tried storing *r.ControlMessage() from each original observe request, applying .FlipSrcDest() and then each time we send a new notification message I call m.SetControlMessage(&obs.controlMessage) before sending it. The ControlMessage contents on the outgoing message look correct but the UDP packet is sent the same as if SetControlMessage was not set. I haven't had time to dig further.

It feels to me like this part of the ControlMessage handling could be encapsulated in net.UDPConn somehow, as then it would apply to all outgoing packets associated with a single "connection", but I'm not really sure.

@Theoyor Theoyor force-pushed the fix/wildcard-address-wrong-srcaddr branch from a145b7c to 3054a3c Compare March 2, 2026 13:21

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@udp/server/server.go`:
- Around line 267-340: The connection key includes laddr but the created
client/session ignores laddr so outbound packets may use a wildcard source;
update getOrCreateConn to propagate the provided laddr into the created
session/client (e.g., pass laddr into NewSession or immediately set the
resulting client.Conn local/source address field that client/conn.go reads such
as Conn.localAddr) so Server.NewConn(..., laddr) actually controls the source
selection for outbound packets when calling NewSession and
client.NewConnWithOpts; ensure the change touches getOrCreateConn, NewSession
call site, and/or initialization of client.Conn so laddr is used consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0ac462fc-9916-4127-9ac0-25e8e35098f7

📥 Commits

Reviewing files that changed from the base of the PR and between 3054a3c and 545f3db.

📒 Files selected for processing (4)
  • .vscode/launch.json
  • udp/client/conn.go
  • udp/server/server.go
  • udp/server_test.go

Comment thread udp/server/server.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@udp/client/conn.go`:
- Around line 865-879: upsertControlInformation currently takes the address of a
stack-allocated cm and passes it to msg.UpsertControlMessage which stores the
pointer (via Message.SetControlMessage), causing a use-after-free; fix by
allocating the ControlMessage on the heap instead of a local variable (construct
and pass &coapNet.ControlMessage{IfIndex: ifIndex, Src: *localAddr} or
equivalent) so msg.UpsertControlMessage receives a heap pointer and no dangling
pointer is stored in Message.controlMessage.
- Around line 833-837: The code currently stores a pointer to the pooled
ControlMessage's destination (cc.localAddr.Store(&cm.Dst)), which becomes
invalid once req is released; instead allocate and store a copy of the IP bytes:
when handling req.ControlMessage() in the block that sets cc.localAddr and
cc.interfaceIndex, create a new net.IP (or copy the slice contents into a
freshly allocated slice) from cm.Dst and store that copy (not &cm.Dst) so later
dereferences of localAddr (used at the dereference site around where *localAddr
is read) reference stable, non-pooled memory.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 54c41dee-62c4-4df5-bec7-638f9192ea70

📥 Commits

Reviewing files that changed from the base of the PR and between 545f3db and 1042950.

📒 Files selected for processing (1)
  • udp/client/conn.go

Comment thread udp/client/conn.go Outdated
Comment thread udp/client/conn.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@udp/client/conn.go`:
- Around line 834-839: The code currently unconditionally writes
cc.interfaceIndex.Store(int64(req.ControlMessage().GetIfIndex())) and may store
a zero-length localAddr when a control message exists but cm.Dst is empty;
update the logic to first fetch cm := req.ControlMessage() and only if cm != nil
call cc.interfaceIndex.Store(int64(cm.GetIfIndex())), and only call
cc.localAddr.Store(&dst) when cm.Dst is non-empty (len(cm.Dst) > 0) after
copying it; do not change interfaceIndex or localAddr when the control message
is absent so previously learned metadata is preserved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 504dca97-0318-492a-9c38-da5a3ce70fba

📥 Commits

Reviewing files that changed from the base of the PR and between 1042950 and d944f01.

📒 Files selected for processing (1)
  • udp/client/conn.go

Comment thread udp/client/conn.go Outdated
@Theoyor

Theoyor commented Mar 10, 2026

Copy link
Copy Markdown
Contributor Author

Hi @jkralik, I had to work on other stuff for some time, but I had time to get back to this topic now. I reimplemented a solution that would work for observing as well (@projectgus). The idea is that connections are now also keyed by local address using the Dst field of an incoming control message if present. If it is a server initiated connection, the client has no assumptions on the content so the listeners LocalAddress is used (which could possibly be [::] or 0.0.0.0).

The goal of that is to be able to save the local address in the client.Conn instances for that exchange. And as it is keyed that way, there shouldn't be any collisions/misassignments. The same was done for the interface as this also shouldn't change in one exchange. Both are upserted in the same control message (if they contain actual values) so that they don't block each other as in the first proposal and also don't overwrite purposefully set CMs.

I tested the changes manually and locally both with libcoap and in our companies go project for standard request response, observe and multicast. That worked.

The unit tests are also working apart from the lint and the windows tests which I saw failed for other PRs as well so I guess it is fine as is.

I would be happy to get a review.

@jkralik jkralik force-pushed the fix/wildcard-address-wrong-srcaddr branch from 8f2823b to 7eb30ba Compare March 17, 2026 13:27
@jkralik

jkralik commented Mar 20, 2026

Copy link
Copy Markdown
Member

Hi @Theoyor, I’ve reviewed your changes and pushed a few additional commits. Could you please retest it? If everything works for you, I’ll go ahead and merge it 🙂

@Theoyor

Theoyor commented Apr 2, 2026

Copy link
Copy Markdown
Contributor Author

Hi @jkralik I tested again and it works well. Thanks for your additions and review. Would be lovely, if you merge it. Thank you!

@jkralik jkralik merged commit 2d71bd2 into plgd-dev:master Apr 2, 2026
12 checks passed
@jkralik

jkralik commented Apr 2, 2026

Copy link
Copy Markdown
Member

@Theoyor Thank you for your contribution 😃

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