diff --git a/src/platform/datapath_winkernel.c b/src/platform/datapath_winkernel.c index dc493141f8..15090fb0fd 100644 --- a/src/platform/datapath_winkernel.c +++ b/src/platform/datapath_winkernel.c @@ -1695,6 +1695,34 @@ SocketCreateUdp( } } + if (Config->CibirIdLength > 0 && + Config->LocalAddress && + Config->LocalAddress->Ipv4.sin_port != 0) { + // + // When CIBIR is in use, multiple listeners may share the same + // port (distinguished by CIBIR ID). Use SO_REUSEADDR instead + // of exclusive binding to allow port sharing. + // + Option = TRUE; + Status = + CxPlatDataPathSetControlSocket( + Binding, + WskSetOption, + SO_REUSEADDR, + SOL_SOCKET, + sizeof(Option), + &Option); + if (QUIC_FAILED(Status)) { + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Binding, + Status, + "Set SO_REUSEADDR"); + goto Error; + } + } + IoReuseIrp(&Binding->Irp, STATUS_SUCCESS); IoSetCompletionRoutine( &Binding->Irp, diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index f0f7b582f0..d6bd0edb27 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1694,7 +1694,34 @@ SocketCreateUdp( } } - if (Datapath->Features & CXPLAT_DATAPATH_FEATURE_PORT_RESERVATIONS && + if (Config->CibirIdLength > 0 && + Config->LocalAddress && + Config->LocalAddress->Ipv4.sin_port != 0) { + // + // When CIBIR is in use, multiple listeners may share the same + // port (distinguished by CIBIR ID). Use SO_REUSEADDR instead + // of exclusive port reservations to allow port sharing. + // + Option = TRUE; + Result = + setsockopt( + SocketProc->Socket, + SOL_SOCKET, + SO_REUSEADDR, + (char*)&Option, + sizeof(Option)); + if (Result == SOCKET_ERROR) { + int WsaError = WSAGetLastError(); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + WsaError, + "Set SO_REUSEADDR"); + Status = HRESULT_FROM_WIN32(WsaError); + goto Error; + } + } else if (Datapath->Features & CXPLAT_DATAPATH_FEATURE_PORT_RESERVATIONS && Config->LocalAddress && Config->LocalAddress->Ipv4.sin_port != 0) { if (i == 0) { diff --git a/src/test/MsQuicTests.h b/src/test/MsQuicTests.h index ff2b0297a4..e9f9560ccf 100644 --- a/src/test/MsQuicTests.h +++ b/src/test/MsQuicTests.h @@ -402,6 +402,11 @@ void QuicTestCibirExtension( const CibirExtensionParams& Params ); + +void +QuicTestCibirSharedPortListeners( + const FamilyArgs& Params + ); #endif void @@ -1488,6 +1493,10 @@ typedef struct { QUIC_CTL_CODE(85, METHOD_BUFFERED, FILE_WRITE_DATA) // QUIC_RUN_CIBIR_EXTENSION +#define IOCTL_QUIC_RUN_CIBIR_SHARED_PORT_LISTENERS \ + QUIC_CTL_CODE(139, METHOD_BUFFERED, FILE_WRITE_DATA) + // int - Family + #define IOCTL_QUIC_RUN_STREAM_PRIORITY_INFINITE_LOOP \ QUIC_CTL_CODE(86, METHOD_BUFFERED, FILE_WRITE_DATA) @@ -1689,7 +1698,7 @@ struct QUIC_RUN_CONNECTION_POOL_CREATE_PARAMS { QUIC_CTL_CODE(138, METHOD_BUFFERED, FILE_WRITE_DATA) // int - Family -#define QUIC_MAX_IOCTL_FUNC_CODE 138 +#define QUIC_MAX_IOCTL_FUNC_CODE 139 // Generic IOCTL for invoking functions diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index ccba280fcc..1427ebf42f 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -1381,6 +1381,15 @@ INSTANTIATE_TEST_SUITE_P( WithCibirExtensionParams, testing::ValuesIn(WithCibirExtensionParams::Generate())); +TEST_P(WithFamilyArgs, CibirSharedPortListeners) { + TestLoggerT Logger("QuicTestCibirSharedPortListeners", GetParam()); + if (TestingKernelMode) { + ASSERT_TRUE(InvokeKernelTest(FUNC(QuicTestCibirSharedPortListeners), GetParam())); + } else { + QuicTestCibirSharedPortListeners(GetParam()); + } +} + #endif #ifdef QUIC_API_ENABLE_PREVIEW_FEATURES diff --git a/src/test/bin/winkernel/control.cpp b/src/test/bin/winkernel/control.cpp index dbf2716fe2..a96543b17c 100644 --- a/src/test/bin/winkernel/control.cpp +++ b/src/test/bin/winkernel/control.cpp @@ -539,6 +539,7 @@ size_t QUIC_IOCTL_BUFFER_SIZES[] = 0, 0, sizeof(INT32), + sizeof(INT32), // IOCTL_QUIC_RUN_CIBIR_SHARED_PORT_LISTENERS }; CXPLAT_STATIC_ASSERT( @@ -736,6 +737,7 @@ ExecuteTestRequest( RegisterTestFunction(QuicTestCustomClientCertificateValidation); RegisterTestFunction(QuicTestConnectClientCertificate); RegisterTestFunction(QuicTestCibirExtension); + RegisterTestFunction(QuicTestCibirSharedPortListeners); #ifdef QUIC_API_ENABLE_PREVIEW_FEATURES #if QUIC_TEST_DISABLE_VNE_TP_GENERATION RegisterTestFunction(QuicTestVNTPOddSize); diff --git a/src/test/lib/HandshakeTest.cpp b/src/test/lib/HandshakeTest.cpp index 8e2a4c2d2d..cacbf7e6e6 100644 --- a/src/test/lib/HandshakeTest.cpp +++ b/src/test/lib/HandshakeTest.cpp @@ -3935,6 +3935,70 @@ QuicTestCibirExtension( TEST_EQUAL(Connection.HandshakeComplete, ShouldConnnect); } +void +QuicTestCibirSharedPortListeners( + const FamilyArgs& Params + ) +{ + const int Family = Params.Family; + + // + // Two different CIBIR IDs to distinguish multiple listeners on the + // same port. + // + const uint8_t CibirId1[] = { 0 /* offset */, 4, 3, 2, 1 }; + const uint8_t CibirId2[] = { 0 /* offset */, 5, 6, 7, 8 }; + const uint8_t CibirIdLength = sizeof(CibirId1); + + MsQuicRegistration Registration(true); + TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); + + MsQuicConfiguration ServerConfiguration(Registration, "MsQuicTest", ServerSelfSignedCredConfig); + TEST_QUIC_SUCCEEDED(ServerConfiguration.GetInitStatus()); + + MsQuicConfiguration ClientConfiguration(Registration, "MsQuicTest", MsQuicCredentialConfig()); + TEST_QUIC_SUCCEEDED(ClientConfiguration.GetInitStatus()); + + QUIC_ADDRESS_FAMILY QuicAddrFamily = (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; + + // + // Start the first CIBIR-enabled listener and let it pick a port. + // + QuicAddr ServerLocalAddr(QuicAddrFamily); + MsQuicAutoAcceptListener Listener1(Registration, ServerConfiguration, MsQuicConnection::NoOpCallback); + TEST_QUIC_SUCCEEDED(Listener1.SetCibirId(CibirId1, CibirIdLength)); + TEST_QUIC_SUCCEEDED(Listener1.Start("MsQuicTest", &ServerLocalAddr.SockAddr)); + TEST_QUIC_SUCCEEDED(Listener1.GetInitStatus()); + TEST_QUIC_SUCCEEDED(Listener1.GetLocalAddr(ServerLocalAddr)); + + // + // Start the second CIBIR-enabled listener on the SAME port. + // This exercises the SO_REUSEADDR path added for CIBIR port sharing. + // + QuicAddr ServerLocalAddr2(QuicAddrFamily); + ServerLocalAddr2.SetPort(ServerLocalAddr.GetPort()); + MsQuicAutoAcceptListener Listener2(Registration, ServerConfiguration, MsQuicConnection::NoOpCallback); + TEST_QUIC_SUCCEEDED(Listener2.SetCibirId(CibirId2, CibirIdLength)); + TEST_QUIC_SUCCEEDED(Listener2.Start("MsQuicTest", &ServerLocalAddr2.SockAddr)); + TEST_QUIC_SUCCEEDED(Listener2.GetInitStatus()); + + // + // Verify a client with the first CIBIR ID can connect via the shared port. + // + MsQuicConnection Connection(Registration); + TEST_QUIC_SUCCEEDED(Connection.GetInitStatus()); + TEST_QUIC_SUCCEEDED(Connection.SetShareUdpBinding()); + TEST_QUIC_SUCCEEDED(Connection.SetCibirId(CibirId1, CibirIdLength)); + TEST_QUIC_SUCCEEDED( + Connection.Start( + ClientConfiguration, + ServerLocalAddr.GetFamily(), + QUIC_TEST_LOOPBACK_FOR_AF(ServerLocalAddr.GetFamily()), + ServerLocalAddr.GetPort())); + Connection.HandshakeCompleteEvent.WaitTimeout(TestWaitTimeout); + TEST_TRUE(Connection.HandshakeComplete); +} + #endif // QUIC_API_ENABLE_PREVIEW_FEATURES void