Skip to content

Don't reset shared receivers from a UDP tunnel that was never started - #2543

Merged
orignal merged 1 commit into
PurpleI2P:opensslfrom
pobregat0:udp-tunnel-not-started
Sep 2, 2026
Merged

Don't reset shared receivers from a UDP tunnel that was never started#2543
orignal merged 1 commit into
PurpleI2P:opensslfrom
pobregat0:udp-tunnel-not-started

Conversation

@pobregat0

Copy link
Copy Markdown
Contributor

A configuration reload silently stops UDP tunnels from delivering anything. The tunnel keeps running, the destination stays, nothing is recreated - and every datagram that arrives is dropped.

Mechanism: ClientContext builds the tunnel object first and only then discovers that the same forward already exists (libi2pd_client/ClientContext.cpp:911 for a server forward, :740 for a client one). The extra object is discarded, its destructor runs Stop (), and Stop () calls ResetReceiver on the datagram destination - which is shared with the live tunnel. From that moment the live tunnel has no receiver.

The server log says exactly that: Datagram: no receiver for port 0, 65 times after the reload.

Reproduction, a scenario that checks what must still work after a reload - a page through the http proxy, a datagram through a udp tunnel pair, and a SAM answer:

before the reload:        page 1, datagram 1, sam 1
after the first reload:   page 1, datagram 0, sam 1
after the second reload:  page 1, datagram 0, sam 1

A control run of the same scenario without reloads keeps all three at 1 in all three checks, so it is the reload and not elapsed time or the network. In the logs the client keeps sending (its sequence number grows, acks time out) while the server's session shows nothing received.

With this change, on the same scenario and the same bench:

before the reload:        page 1, datagram 1, sam 1
after the first reload:   page 1, datagram 1, sam 1
after the second reload:  page 1, datagram 1, sam 1

and no receiver for port never appears in the log. Zero sanitizer reports in both runs.

The fix keeps a tunnel from tearing down shared state it never set up: Start () marks the tunnel started, and Stop () returns early if it was not. The client tunnel gets the same guard - its Stop () resets the receiver in the same way.

make has no new warnings, make -C tests passes, g++ -std=c++17 -fsyntax-only on the changed file passes.

@orignal
orignal merged commit cef23b0 into PurpleI2P:openssl Sep 2, 2026
15 checks passed
@pobregat0

pobregat0 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Confirmed once more on the current head 920226d, without this branch:

before the reload:       page 1, datagram 1, sam 1
after the first reload:  page 1, datagram 0, sam 1
after the second reload: page 1, datagram 0, sam 1

with zero sanitizer reports, so the breakage is silent - nothing crashes, the tunnel object is alive and the destination is up, only the receiver is gone.

@pobregat0

Copy link
Copy Markdown
Contributor Author

I checked the neighbouring tunnels for the same shape. Only the UDP tunnels define a destructor that calls Stop (); I2PServerTunnel and I2PClientTunnel have none, so a duplicate object discarded by a reload never detaches their acceptor. I2PServerTunnel::DetachAcceptor already guards the shared default acceptor with m_IsDefaultAcceptor for the same reason. So this is the only place with the pattern, and the guard belongs where this change puts it.

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