Skip to content

test(transport): add ipv6 coverage to integration tests#3461

Open
rishishanbhag wants to merge 14 commits intolibp2p:masterfrom
rishishanbhag:add-ipv6-transport-tests
Open

test(transport): add ipv6 coverage to integration tests#3461
rishishanbhag wants to merge 14 commits intolibp2p:masterfrom
rishishanbhag:add-ipv6-transport-tests

Conversation

@rishishanbhag
Copy link
Copy Markdown

@rishishanbhag rishishanbhag commented Feb 11, 2026

Closes #2510

Extends the transport integration test suite to validate behavior over IPv6 loopback addresses.
What this adds

IPv6 variants for:

  • TCP (Noise / TLS / Yamux)
  • WebSocket
  • QUIC
  • WebTransport

Tests bind to the IPv6 loopback address (::1) to verify transport functionality.

Introduces a helper (skipIfNoIPv6) that:

  • Attempts to bind to ::1
  • Skips IPv6-specific tests if the host environment does not support IPv6
  • Prevents CI failures on systems without IPv6 enabled

Scope

  • Test-only changes
  • No modifications to production code
  • Existing IPv4 behavior remains unchanged

Extends the transport integration test suite to validate behavior over IPv6 loopback addresses for TCP, WebSocket, QUIC, and WebTransport. Includes a helper to skip IPv6 tests if the host environment does not support IPv6 binding.
@rishishanbhag
Copy link
Copy Markdown
Author

@MarcoPolo

The “Run transport interoperability tests” check is currently failing with a permission denied error while attempting to create the /srv/cache directory during the Docker/BuildKit setup phase. It appears to fail before any of the Go tests are executed.

All other CI jobs, including the standard Go test matrix on macOS and Ubuntu (Go 1.24 and 1.25), are passing successfully.

Since this PR only adds IPv6 variants to the transport integration tests and does not modify interop configuration, Docker setup, or workflow files, I wanted to check whether this could be an infrastructure or workflow issue rather than something introduced here.

Please let me know if you’d like me to dig deeper into this.

@MarcoPolo
Copy link
Copy Markdown
Collaborator

Yes, that's an unrelated failure.

@rishishanbhag
Copy link
Copy Markdown
Author

Yes, that's an unrelated failure.

@MarcoPolo since the failing check is unrelated, would you mind taking a look at the PR when you get a chance?

@MarcoPolo
Copy link
Copy Markdown
Collaborator

Is there a reason you skipped WebRTC?

@rishishanbhag
Copy link
Copy Markdown
Author

@MarcoPolo I noticed that the existing WebRTC transport test only binds to an IPv4 webrtc-direct address and there isn’t currently an IPv6 variant in the matrix. Also, there are a few explicit skips around WebRTC due to loopback/ICE behavior and SCTP stream ID limits. Given that, I held off adding an IPv6 WebRTC case without first confirming expected behavior on CI hosts.

Is IPv6 loopback expected to work for WebRTC in CI, and would you like me to add coverage for it as well?

@MarcoPolo
Copy link
Copy Markdown
Collaborator

Is IPv6 loopback expected to work for WebRTC in CI, and would you like me to add coverage for it as well?

If the other cases work in CI, I would expect this to also work. And, if not, to have a clear explanation as to why it doesn't work.

On the topic of missing a local IPv6 interface. Did you actually run into this issue somewhere, or is this pre-emptive?

Add WebRTC - IP6 to the transport test matrix using
/ip6/::1/udp/0/webrtc-direct.

The test passes on Linux (CI) but is unstable on Windows loopback,
so it is skipped there to avoid false negatives.
@rishishanbhag
Copy link
Copy Markdown
Author

@MarcoPolo

I went ahead and added an explicit WebRTC – IP6 variant to the transport matrix (/ip6/::1/udp/0/webrtc-direct) so that IPv6 loopback is exercised the same way as the other transports.

What I observed while testing:

  • On Linux (Docker, matching CI environment) the WebRTC IPv6 test passes consistently.
  • On Windows, the IPv6 WebRTC case intermittently fails with peerconnection opening timed out, even though other IPv6 transports (TCP, QUIC, etc.) work fine.
  • The failure appears related to loopback/ICE behavior on Windows rather than the libp2p transport logic itself.

To validate this wasn’t pre-emptive:

  • I reproduced the failure locally on Windows.
  • I re-ran the test inside a Linux container (golang:1.24-bullseye) and it passed reliably, which aligns with CI expectations.

Given that CI runs on Linux and behaves correctly, I:

  • Kept the IPv6 WebRTC coverage enabled.
  • Added a Windows-specific skip for WebRTC - IP6 to avoid false negatives on Windows development machines.

If you’d prefer a different approach (e.g., no OS-specific skip and let Windows fail), I’m happy to adjust.

@rishishanbhag
Copy link
Copy Markdown
Author

@MarcoPolo could you please take a look at the PR when you get a chance?

@rishishanbhag
Copy link
Copy Markdown
Author

Hi @sukunrt ,since you’ve worked recently on transport changes, could you please take a look when you have time?

@MarcoPolo
Copy link
Copy Markdown
Collaborator

Why does Windows fail? This should just be sending and receiving UDP packets under the hood. Can you capture the failure in a wireshark trace so we can understand the issue?

@rishishanbhag
Copy link
Copy Markdown
Author

Hey @MarcoPolo , On Windows, STUN packets are not transmitted at all. The call to wsasendto fails with “The requested address is not valid in its context” when attempting to send from link-local/global IPv6 candidates (e.g., fe80::...) to ::1. Wireshark confirms that no IPv6 UDP packets are emitted on loopback during the failure. This appears to be a scope mismatch issue rather than a STUN timeout.

logs.txt
final_trace_libp2p.zip

@rishishanbhag
Copy link
Copy Markdown
Author

Hey @MarcoPolo , On Windows, STUN packets are not transmitted at all. The call to wsasendto fails with “The requested address is not valid in its context” when attempting to send from link-local/global IPv6 candidates (e.g., fe80::...) to ::1. Wireshark confirms that no IPv6 UDP packets are emitted on loopback during the failure. This appears to be a scope mismatch issue rather than a STUN timeout.

logs.txt

final_trace_libp2p.zip

@MarcoPolo could you please take a look when you get a chance?

@MarcoPolo
Copy link
Copy Markdown
Collaborator

thanks for diving into this. Is there any changes you can make to the libp2p host setup that make this work in windows?

@rishishanbhag
Copy link
Copy Markdown
Author

thanks for diving into this. Is there any changes you can make to the libp2p host setup that make this work in windows?

Hey @MarcoPolo, i tried and made some changes which does work in windows

On Windows the IPv6 WebRTC test was failing because of how the socket was set up, not because of STUN itself:

  1. What was broken (before)
  • The listener was on ::1, but the dialer’s ICE code opened sockets on all IPv6 interfaces (Wi‑Fi, VMware, global v6, etc.).

  • Windows doesn’t allow “send from non‑loopback to loopback”, so every STUN packet from fe80::… or 2405:… to ::1 failed with wsasendto: The requested address is not valid in its context. Nothing ever reached loopback, so ICE timed out.

  1. What we changed

When dialing a /ip6/::1/.../webrtc-direct address, we now:

  • Open one UDP socket bound to ::1 (loopback) and give that socket to Pion via its UDPMux hook.
  • Tell Pion to only gather loopback candidates for this connection.

Result / how we verified it

  • TestPing/WebRTC_-_IP6 now passes on Windows.
  • A Wireshark capture on the loopback adapter with udp and ipv6 and dst == ::1 shows STUN + DTLS packets going from ::1 to ::1, instead of the previous failed fe80::… → ::1 attempts.

This keeps behavior for non‑loopback dials unchanged; the special casing only applies when the remote address is on loopback.
webrtc_patch.zip

@MarcoPolo
Copy link
Copy Markdown
Collaborator

Why are you reverting the pion upgrade?

@rishishanbhag
Copy link
Copy Markdown
Author

rishishanbhag commented Mar 5, 2026

@MarcoPolo

Why are you reverting the pion upgrade?

I reverted the Pion upgrade earlier while debugging the Windows IPv6 issue, but it wasn’t needed for the fix. I’ve re-applied the Pion upgrade (commit “Reapply webrtc: upgrade pion deps (#3469)”) and confirmed that TestPing/WebRTC_-_IP6 still passes on Windows with it. So we can keep the upgrade; the Windows fix is compatible with it.

@rishishanbhag
Copy link
Copy Markdown
Author

@MarcoPolo can you please take a look at the recent commit whenever you get time

@MarcoPolo
Copy link
Copy Markdown
Collaborator

Please backout the windows workaround changes you made to non-test code. You can skip the windows test and leave a comment explaining why windows fails this test (essentially what you discovered above).

Remove the Windows loopback WebRTC dial workaround (UDPMux, IP filter,
dialMux lifecycle) from webrtc transport/connection/listener per review.

Add skipWebRTCIPv6OnWindows and call it from every transportsToTest
subtest that can run WebRTC - IP6 (transport_test, rcmgr, gating,
deadline) so Windows CI skips with an explanation; Linux still runs
the case.
@rishishanbhag
Copy link
Copy Markdown
Author

@MarcoPolo I have reverted all Windows-specific behavior from non-test WebRTC code:

  1. The dial path no longer binds a loopback UDPMux, no longer sets SetICEUDPMux / SetIPFilter for loopback, and connection no longer holds or closes a dialMux, with listener calling newConnection with the original argument list again, so production matches the usual upstream dial/listen behavior.
  2. To keep CI green where that scenario is broken, we added a small helper skipWebRTCIPv6OnWindows next to the other transport test helpers and invoke it at the start of every subtest that iterates transportsToTest and can hit WebRTC - IP6, including transport_test.go, rcmgr_test.go, gating_test.go, and deadline_test.go (we did not add it where the whole test already skips on Windows or only runs IPv4 WebRTC).
  • Why it doesn’t work on Windows without the old workaround:
    The listener uses /ip6/::1/udp/.../webrtc-direct, while Pion’s ICE stack still gathers link-local and global IPv6 candidates; when the stack tries to send STUN/ICE traffic from those interface-scoped addresses toward ::1, Windows’ UDP stack returns wsasendto: The requested address is not valid in its context—a scope / destination mismatch—so packets often never show up on the loopback interface in captures and the peer connection times out or fails; Linux loopback IPv6 behavior does not hit this the same way, so we only skip on Windows and keep full coverage on Linux CI.

@rishishanbhag
Copy link
Copy Markdown
Author

@MarcoPolo please take a look at the PR whenever you get a chance.

go-test:
uses: ./.github/workflows/go-test-template.yml
with:
go-versions: '["1.25.x", "1.26.x"]'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Back these changes out

}

func TestPing(t *testing.T) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

backout this unnecessary change

@@ -1,6 +1,6 @@
module github.com/libp2p/go-libp2p/test-plans/m/v2

go 1.25.7
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

back this out

- Restore go-versions to ["1.25.x", "1.26.x"] in go-test.yml
- Restore go 1.25.7 in go.mod and test-plans/go.mod
- Remove unnecessary blank line after func TestPing
- Generalise skipWebRTCIPv6OnWindows to cover any "- IP6" transport
  (WebRTC and WebTransport).
}

// skipWebRTCIPv6OnWindows skips IPv6 loopback integration scenarios on Windows for
// transports whose name ends in "- IP6" (WebRTC - IP6, WebTransport - IP6).
Copy link
Copy Markdown
Collaborator

@MarcoPolo MarcoPolo Mar 26, 2026

Choose a reason for hiding this comment

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

Why skip the other transports on windows and not just webrtc?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

call this function skipWindows, and move the comment below to inside the host generator for ipv6 webrtc.

Name: "WebTransport - IP6",
HostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {
skipIfNoIPv6(t)
skipWebRTCIPv6OnWindows(t, "WebTransport - IP6")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why is this in the webtransport case?

Updated the function name from skipWebRTCIPv6OnWindows to skipWindows for clarity and consistency. Adjusted all relevant test cases to use the new function name, ensuring that the functionality remains intact while improving code readability.
@rishishanbhag rishishanbhag requested a review from MarcoPolo March 27, 2026 05:08

func skipWindows(t *testing.T, transportName string) {
t.Helper()
if transportName == "WebRTC - IP6" && runtime.GOOS == "windows" {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we don't need the transportName param. It's just skip windows helper

func TestPing(t *testing.T) {
for _, tc := range transportsToTest {
t.Run(tc.Name, func(t *testing.T) {
skipWindows(t, tc.Name)
Copy link
Copy Markdown
Collaborator

@MarcoPolo MarcoPolo Mar 27, 2026

Choose a reason for hiding this comment

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

We don't need this or any other skipWindows calls inside the tests anymore. We can just call it from the host generator.

@MarcoPolo
Copy link
Copy Markdown
Collaborator

@rishishanbhag , this is close. Please make the requested changes and do a final self review to make sure it all looks good.

Removed the transport name parameter from the skipWindows function to generalize its application across all tests. Updated all relevant test cases to reflect this change, ensuring consistent handling of Windows-specific skips for WebRTC and other transports.
@rishishanbhag
Copy link
Copy Markdown
Author

@MarcoPolo This update matches your feedback: skipWindows(t) has no transportName arg and is only called from the WebRTC - IP6 HostGenerator (with a brief ICE/::1 / Windows wsasendto note). All per-test skipWindows calls are removed; skips stay in the HostGenerator only.

For TestCloseConnWhenBlocked and TestResourceManagerIsUsed, we still pass the mock rcmgr into HostGenerator as before; we only moved EXPECT() (and the rcmgr memory defer) to after both HostGenerator calls so a Windows t.Skip() does not leave unmet GoMock expectations. WebTransport - IP6 is unchanged.
I have reviewed it myself and its ready for another look when you have time.

func TestPing(t *testing.T) {
for _, tc := range transportsToTest {
t.Run(tc.Name, func(t *testing.T) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

back out these whitespace changes

Cleaned up the transport test file by removing extraneous blank lines in various test functions, enhancing code readability without altering functionality.
@MarcoPolo
Copy link
Copy Markdown
Collaborator

CI is failing for the webrtc IPv6 tests: https://github.com/libp2p/go-libp2p/actions/runs/23658563833/job/68924824086

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.

Feature request: Add IPv6 tests to transport integration tests

2 participants