From adecf930e9deaf5bf2b5d77e520f8f2489ad289a Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Sat, 29 Jul 2023 00:32:49 -0700 Subject: [PATCH 01/87] Remove QUIC_USE_RAW_DATAPATH (logic only) --- src/core/connection.c | 8 ---- src/core/connection.h | 2 - src/core/cubic.c | 6 +-- src/core/listener.c | 4 +- src/core/path.c | 20 ++------- src/core/path.h | 7 ---- src/core/send.c | 54 +----------------------- src/inc/quic_datapath.h | 45 ++++++++++++-------- src/platform/datapath_epoll.c | 66 ++++++++++++++++++++++++++++++ src/platform/datapath_raw.h | 1 + src/platform/datapath_raw_socket.c | 51 +++++++++++++++++++++++ src/platform/datapath_winuser.c | 65 +++++++++++++++++++++++++++++ 12 files changed, 221 insertions(+), 108 deletions(-) diff --git a/src/core/connection.c b/src/core/connection.c index f5fb219d4d..4e3f8cbe04 100644 --- a/src/core/connection.c +++ b/src/core/connection.c @@ -3304,7 +3304,6 @@ QuicConnQueueUnreachable( } } -#ifdef QUIC_USE_RAW_DATAPATH _IRQL_requires_max_(DISPATCH_LEVEL) _Function_class_(CXPLAT_ROUTE_RESOLUTION_CALLBACK) void @@ -3342,7 +3341,6 @@ QuicConnQueueRouteCompletion( QuicConnRelease(Connection, QUIC_CONN_REF_ROUTE); } -#endif // QUIC_USE_RAW_DATAPATH // // Updates the current destination CID to the received packet's source CID, if @@ -5609,9 +5607,7 @@ QuicConnRecvDatagrams( goto Drop; } -#ifdef QUIC_USE_RAW_DATAPATH CxPlatUpdateRoute(&DatagramPath->Route, Datagram->Route); -#endif if (DatagramPath != CurrentPath) { if (BatchCount != 0) { @@ -5969,7 +5965,6 @@ QuicConnProcessUdpUnreachable( } } -#ifdef QUIC_USE_RAW_DATAPATH _IRQL_requires_max_(PASSIVE_LEVEL) void QuicConnProcessRouteCompletion( @@ -6019,7 +6014,6 @@ QuicConnProcessRouteCompletion( NULL); } } -#endif // QUIC_USE_RAW_DATAPATH _IRQL_requires_max_(PASSIVE_LEVEL) void @@ -7607,12 +7601,10 @@ QuicConnDrainOperations( QuicConnTraceRundownOper(Connection); break; -#ifdef QUIC_USE_RAW_DATAPATH case QUIC_OPER_TYPE_ROUTE_COMPLETION: QuicConnProcessRouteCompletion( Connection, Oper->ROUTE.PhysicalAddress, Oper->ROUTE.PathId, Oper->ROUTE.Succeeded); break; -#endif // QUIC_USE_RAW_DATAPATH default: CXPLAT_FRE_ASSERT(FALSE); diff --git a/src/core/connection.h b/src/core/connection.h index 3ba2734d0f..f0ac6c1e8b 100644 --- a/src/core/connection.h +++ b/src/core/connection.h @@ -1516,7 +1516,6 @@ QuicConnQueueUnreachable( _In_ const QUIC_ADDR* RemoteAddress ); -#ifdef QUIC_USE_RAW_DATAPATH // // Queues a route completion event to a connection for processing. // @@ -1531,7 +1530,6 @@ QuicConnQueueRouteCompletion( _In_ uint8_t PathId, _In_ BOOLEAN Succeeded ); -#endif // QUIC_USE_RAW_DATAPATH // // Queues up an update to the packet tolerance we want the peer to use. diff --git a/src/core/cubic.c b/src/core/cubic.c index ccc0e6ef3c..46b1132da2 100644 --- a/src/core/cubic.c +++ b/src/core/cubic.c @@ -312,9 +312,9 @@ CubicCongestionControlOnCongestionEvent( "[conn][%p] Persistent congestion event", Connection); Connection->Stats.Send.PersistentCongestionCount++; -#ifdef QUIC_USE_RAW_DATAPATH - Connection->Paths[0].Route.State = RouteSuspected; -#endif + + Connection->Paths[0].Route.State = RouteSuspected; // used only for RAW datapath + Cubic->IsInPersistentCongestion = TRUE; Cubic->WindowPrior = Cubic->WindowMax = diff --git a/src/core/listener.c b/src/core/listener.c index 3235e3ed00..9cf4c5a299 100644 --- a/src/core/listener.c +++ b/src/core/listener.c @@ -293,7 +293,8 @@ MsQuicListenerStart( #ifdef QUIC_OWNING_PROCESS UdpConfig.OwningProcess = NULL; // Owning process not supported for listeners. #endif -#ifdef QUIC_USE_RAW_DATAPATH + + // for RAW datapath UdpConfig.CibirIdLength = Listener->CibirId[0]; UdpConfig.CibirIdOffsetSrc = MsQuicLib.CidServerIdLength + 2; UdpConfig.CibirIdOffsetDst = MsQuicLib.CidServerIdLength + 2; @@ -304,7 +305,6 @@ MsQuicListenerStart( &Listener->CibirId[2], UdpConfig.CibirIdLength); } -#endif CXPLAT_TEL_ASSERT(Listener->Binding == NULL); Status = diff --git a/src/core/path.c b/src/core/path.c index 7828a6c88b..a4ac11179c 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -34,12 +34,13 @@ QuicPathInitialize( Path->RttVariance = Path->SmoothedRtt / 2; Path->EcnValidationState = Connection->Settings.EcnEnabled ? ECN_VALIDATION_TESTING : ECN_VALIDATION_FAILED; -#ifdef QUIC_USE_RAW_DATAPATH + + // for RAW datapath if (MsQuicLib.ExecutionConfig && MsQuicLib.ExecutionConfig->Flags & QUIC_EXECUTION_CONFIG_FLAG_QTIP) { CxPlatRandom(sizeof(Path->Route.TcpState.SequenceNumber), &Path->Route.TcpState.SequenceNumber); } -#endif + QuicTraceLogConnInfo( PathInitialized, Connection, @@ -183,21 +184,6 @@ QuicConnGetPathByID( return NULL; } -_IRQL_requires_max_(PASSIVE_LEVEL) -void -QuicCopyRouteInfo( - _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute - ) -{ -#ifdef QUIC_USE_RAW_DATAPATH - CxPlatCopyMemory(DstRoute, SrcRoute, (uint8_t*)&SrcRoute->State - (uint8_t*)SrcRoute); - CxPlatUpdateRoute(DstRoute, SrcRoute); -#else - *DstRoute = *SrcRoute; -#endif -} - _IRQL_requires_max_(PASSIVE_LEVEL) _Ret_maybenull_ QUIC_PATH* diff --git a/src/core/path.h b/src/core/path.h index d664cbf4d9..51cefd644a 100644 --- a/src/core/path.h +++ b/src/core/path.h @@ -301,10 +301,3 @@ QuicConnGetPathForDatagram( _In_ QUIC_CONNECTION* Connection, _In_ const CXPLAT_RECV_DATA* Datagram ); - -_IRQL_requires_max_(PASSIVE_LEVEL) -void -QuicCopyRouteInfo( - _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute - ); diff --git a/src/core/send.c b/src/core/send.c index 236d890c2b..5835ea133a 100644 --- a/src/core/send.c +++ b/src/core/send.c @@ -1042,37 +1042,10 @@ QuicSendPathChallenges( continue; } -#ifdef QUIC_USE_RAW_DATAPATH - // - // Make sure the route is resolved before sending the path challenge. - // - // We need to set the path challenge flag back on so that when route is resolved, - // we know we need to continue to send the challenge. - // - CXPLAT_DBG_ASSERT(Path->Route.State != RouteSuspected); - if (Path->Route.State == RouteUnresolved) { - QuicConnAddRef(Connection, QUIC_CONN_REF_ROUTE); - QUIC_STATUS Status = - CxPlatResolveRoute( - Path->Binding->Socket, &Path->Route, Path->ID, (void*)Connection, QuicConnQueueRouteCompletion); - if (Status == QUIC_STATUS_SUCCESS) { - QuicConnRelease(Connection, QUIC_CONN_REF_ROUTE); - } else { - // - // Route resolution failed or pended. We need to pause sending. - // - CXPLAT_DBG_ASSERT(Status == QUIC_STATUS_PENDING || QUIC_FAILED(Status)); - Send->SendFlags |= QUIC_CONN_SEND_FLAG_PATH_CHALLENGE; - continue; - } - } else if (Path->Route.State == RouteResolving) { - // - // Can't send now. Once route resolution completes, we will resume sending. - // + if (!CxPlatIsRouteReady(Connection, QuicConnQueueRouteCompletion, TRUE)) { Send->SendFlags |= QUIC_CONN_SEND_FLAG_PATH_CHALLENGE; continue; } -#endif QUIC_PACKET_BUILDER Builder = { 0 }; if (!QuicPacketBuilderInitialize(&Builder, Connection, Path)) { @@ -1159,32 +1132,9 @@ QuicSendFlush( CXPLAT_DBG_ASSERT(!Connection->State.HandleClosed); -#ifdef QUIC_USE_RAW_DATAPATH - // - // Make sure the route is resolved before sending packets. - // - CXPLAT_DBG_ASSERT(Path->IsActive); - if (Path->Route.State == RouteUnresolved || Path->Route.State == RouteSuspected) { - QuicConnAddRef(Connection, QUIC_CONN_REF_ROUTE); - QUIC_STATUS Status = - CxPlatResolveRoute( - Path->Binding->Socket, &Path->Route, Path->ID, (void*)Connection, QuicConnQueueRouteCompletion); - if (Status == QUIC_STATUS_SUCCESS) { - QuicConnRelease(Connection, QUIC_CONN_REF_ROUTE); - } else { - // - // Route resolution failed or pended. We need to pause sending. - // - CXPLAT_DBG_ASSERT(Status == QUIC_STATUS_PENDING || QUIC_FAILED(Status)); - return TRUE; - } - } else if (Path->Route.State == RouteResolving) { - // - // Can't send now. Once route resolution completes, we will resume sending. - // + if (!CxPlatIsRouteReady(Connection, QuicConnQueueRouteCompletion, FALSE)) { return TRUE; } -#endif QuicConnTimerCancel(Connection, QUIC_CONN_TIMER_PACING); QuicConnRemoveOutFlowBlockedReason( diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 67bdb8376d..674bfe11f7 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -175,7 +175,6 @@ typedef struct CXPLAT_ROUTE { QUIC_ADDR RemoteAddress; QUIC_ADDR LocalAddress; -#ifdef QUIC_USE_RAW_DATAPATH uint8_t LocalLinkLayerAddress[6]; uint8_t NextHopLinkLayerAddress[6]; @@ -185,7 +184,6 @@ typedef struct CXPLAT_ROUTE { CXPLAT_ROUTE_STATE State; CXPLAT_RAW_TCP_STATE TcpState; -#endif // QUIC_USE_RAW_DATAPATH } CXPLAT_ROUTE; @@ -544,12 +542,12 @@ typedef struct CXPLAT_UDP_CONFIG { #ifdef QUIC_OWNING_PROCESS QUIC_PROCESS OwningProcess; // Kernel client-only #endif -#ifdef QUIC_USE_RAW_DATAPATH + + // used for RAW datapath uint8_t CibirIdLength; // CIBIR ID length. Value of 0 indicates CIBIR isn't used uint8_t CibirIdOffsetSrc; // CIBIR ID offset in source CID uint8_t CibirIdOffsetDst; // CIBIR ID offset in destination CID uint8_t CibirId[6]; // CIBIR ID data -#endif } CXPLAT_UDP_CONFIG; // @@ -730,18 +728,6 @@ CxPlatSocketSend( _In_ CXPLAT_SEND_DATA* SendData ); -#ifdef QUIC_USE_RAW_DATAPATH -// -// Copies L2 address into route object and sets route state to resolved. -// -void -CxPlatResolveRouteComplete( - _In_ void* Connection, - _Inout_ CXPLAT_ROUTE* Route, - _In_reads_bytes_(6) const uint8_t* PhysicalAddress, - _In_ uint8_t PathId - ); - // // Function pointer type for datapath route resolution callbacks. // @@ -759,6 +745,32 @@ void ); typedef CXPLAT_ROUTE_RESOLUTION_CALLBACK *CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER; +typedef struct QUIC_CONNECTION QUIC_CONNECTION; + +BOOLEAN +CxPlatIsRouteReady( + _In_ QUIC_CONNECTION *Connection, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback, + _In_ BOOLEAN PathChallenge + ); + +// +// Copies L2 address into route object and sets route state to resolved. +// +void +CxPlatResolveRouteComplete( + _In_ void* Connection, + _Inout_ CXPLAT_ROUTE* Route, + _In_reads_bytes_(6) const uint8_t* PhysicalAddress, + _In_ uint8_t PathId + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +QuicCopyRouteInfo( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ); // // Tries to resolve route and neighbor for the given destination address. @@ -780,7 +792,6 @@ CxPlatUpdateRoute( _In_ CXPLAT_ROUTE* SrcRoute ); -#endif // QUIC_USE_RAW_DATAPATH #if defined(__cplusplus) } diff --git a/src/platform/datapath_epoll.c b/src/platform/datapath_epoll.c index 79b6a54fc5..af365478cf 100644 --- a/src/platform/datapath_epoll.c +++ b/src/platform/datapath_epoll.c @@ -2621,3 +2621,69 @@ CxPlatDataPathProcessCqe( } } } + + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +QuicCopyRouteInfo( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ + *DstRoute = *SrcRoute; +} + +BOOLEAN +CxPlatIsRouteReady( + _In_ QUIC_CONNECTION *Connection, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback, + _In_ BOOLEAN PathChallenge +) { + UNREFERENCED_PARAMETER(Connection); + UNREFERENCED_PARAMETER(Callback); + UNREFERENCED_PARAMETER(PathChallenge); + return TRUE; +} + +void +CxPlatResolveRouteComplete( + _In_ void* Context, + _Inout_ CXPLAT_ROUTE* Route, + _In_reads_bytes_(6) const uint8_t* PhysicalAddress, + _In_ uint8_t PathId + ) +{ + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Route); + UNREFERENCED_PARAMETER(PhysicalAddress); + UNREFERENCED_PARAMETER(PathId); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatResolveRoute( + _In_ CXPLAT_SOCKET* Socket, + _Inout_ CXPLAT_ROUTE* Route, + _In_ uint8_t PathId, + _In_ void* Context, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback + ) +{ + UNREFERENCED_PARAMETER(Socket); + UNREFERENCED_PARAMETER(Route); + UNREFERENCED_PARAMETER(PathId); + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Callback); + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatUpdateRoute( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ + UNREFERENCED_PARAMETER(DstRoute); + UNREFERENCED_PARAMETER(SrcRoute); +} diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index ec2b08ad6f..ed69710ced 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -18,6 +18,7 @@ typedef struct CXPLAT_SOCKET_POOL { } CXPLAT_SOCKET_POOL; typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; +typedef struct QUIC_PATH QUIC_PATH; // // A worker thread for draining queued route resolution operations. diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index 5c85731826..330828888d 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -43,6 +43,57 @@ CxPlatGetSocket( return Socket; } +BOOLEAN +CxPlatIsRouteReady( + _In_ QUIC_CONNECTION *Connection, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback, + _In_ BOOLEAN PathChallenge +) { + QUIC_PATH* Path = &Connection->Paths[0]; + // + // Make sure the route is resolved before sending packets. + // + // + // We need to set the path challenge flag back on so that when route is resolved, + // we know we need to continue to send the challenge. + // + CXPLAT_DBG_ASSERT((!PathChallenge && Path->IsActive) || + (PathChallenge && Path->Route.State != RouteSuspected)); + if ((!PathChallenge && Path->Route.State == RouteUnresolved || Path->Route.State == RouteSuspected) || + (PathChallenge && Path->Route.State == RouteUnresolved)) { + QuicConnAddRef(Connection, QUIC_CONN_REF_ROUTE); + QUIC_STATUS Status = + CxPlatResolveRoute( + Path->Binding->Socket, &Path->Route, Path->ID, (void*)Connection, QuicConnQueueRouteCompletion); + if (Status == QUIC_STATUS_SUCCESS) { + QuicConnRelease(Connection, QUIC_CONN_REF_ROUTE); + } else { + // + // Route resolution failed or pended. We need to pause sending. + // + CXPLAT_DBG_ASSERT(Status == QUIC_STATUS_PENDING || QUIC_FAILED(Status)); + return FALSE; + } + } else if (Path->Route.State == RouteResolving) { + // + // Can't send now. Once route resolution completes, we will resume sending. + // + return FALSE; + } + return TRUE; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +QuicCopyRouteInfo( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ + CxPlatCopyMemory(DstRoute, SrcRoute, (uint8_t*)&SrcRoute->State - (uint8_t*)SrcRoute); + CxPlatUpdateRoute(DstRoute, SrcRoute); +} + void CxPlatResolveRouteComplete( _In_ void* Context, diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 37eb9cd124..a5e48cb8a2 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -5138,3 +5138,68 @@ CxPlatDataPathProcessCqe( default: CXPLAT_DBG_ASSERT(FALSE); break; } } + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +QuicCopyRouteInfo( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ + *DstRoute = *SrcRoute; +} + +BOOLEAN +CxPlatIsRouteReady( + _In_ QUIC_CONNECTION *Connection, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback, + _In_ BOOLEAN PathChallenge +) { + UNREFERENCED_PARAMETER(Connection); + UNREFERENCED_PARAMETER(Callback); + UNREFERENCED_PARAMETER(PathChallenge); + return TRUE; +} + +void +CxPlatResolveRouteComplete( + _In_ void* Context, + _Inout_ CXPLAT_ROUTE* Route, + _In_reads_bytes_(6) const uint8_t* PhysicalAddress, + _In_ uint8_t PathId + ) +{ + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Route); + UNREFERENCED_PARAMETER(PhysicalAddress); + UNREFERENCED_PARAMETER(PathId); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatResolveRoute( + _In_ CXPLAT_SOCKET* Socket, + _Inout_ CXPLAT_ROUTE* Route, + _In_ uint8_t PathId, + _In_ void* Context, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback + ) +{ + UNREFERENCED_PARAMETER(Socket); + UNREFERENCED_PARAMETER(Route); + UNREFERENCED_PARAMETER(PathId); + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Callback); + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatUpdateRoute( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ + UNREFERENCED_PARAMETER(DstRoute); + UNREFERENCED_PARAMETER(SrcRoute); +} From 5f78bb75724c661d9e332f973a91b5636f75082c Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Sat, 29 Jul 2023 11:43:31 -0700 Subject: [PATCH 02/87] move logic to core side --- src/core/connection.c | 1 + src/core/connection.h | 5 +++ src/core/send.c | 43 ++++++++++++++++++++- src/inc/quic_datapath.h | 11 ++---- src/platform/datapath_epoll.c | 20 ++++------ src/platform/datapath_kqueue.c | 61 ++++++++++++++++++++++++++++++ src/platform/datapath_raw_socket.c | 48 ++++------------------- src/platform/datapath_winkernel.c | 61 ++++++++++++++++++++++++++++++ src/platform/datapath_winuser.c | 20 ++++------ submodules/googletest | 2 +- 10 files changed, 198 insertions(+), 74 deletions(-) diff --git a/src/core/connection.c b/src/core/connection.c index 4e3f8cbe04..429c993a57 100644 --- a/src/core/connection.c +++ b/src/core/connection.c @@ -137,6 +137,7 @@ QuicConnAlloc( QuicRangeInitialize( QUIC_MAX_RANGE_DECODE_ACKS, &Connection->DecodedAckRanges); + CxPlatSetDataPathType(&Connection->IsRawDatapath); for (uint32_t i = 0; i < ARRAYSIZE(Connection->Packets); i++) { Status = diff --git a/src/core/connection.h b/src/core/connection.h index f0ac6c1e8b..c450968a46 100644 --- a/src/core/connection.h +++ b/src/core/connection.h @@ -641,6 +641,11 @@ typedef struct QUIC_CONNECTION { QUIC_FLOW_BLOCKED_TIMING_TRACKER FlowControl; } BlockedTimings; + // + // flag whether the datapath is raw or not + // + BOOLEAN IsRawDatapath; + } QUIC_CONNECTION; typedef struct QUIC_SERIALIZED_RESUMPTION_STATE { diff --git a/src/core/send.c b/src/core/send.c index 5835ea133a..652a9cc9b2 100644 --- a/src/core/send.c +++ b/src/core/send.c @@ -1018,6 +1018,45 @@ QuicSendGetNextStream( return NULL; } +BOOLEAN +CxPlatIsRouteReady( + _In_ QUIC_CONNECTION *Connection, + _In_ BOOLEAN PathChallenge +) { + QUIC_PATH* Path = &Connection->Paths[0]; + // + // Make sure the route is resolved before sending packets. + // + // + // We need to set the path challenge flag back on so that when route is resolved, + // we know we need to continue to send the challenge. + // + CXPLAT_DBG_ASSERT((!PathChallenge && Path->IsActive) || + (PathChallenge && Path->Route.State != RouteSuspected)); + if ((!PathChallenge && Path->Route.State == RouteUnresolved || Path->Route.State == RouteSuspected) || + (PathChallenge && Path->Route.State == RouteUnresolved)) { + QuicConnAddRef(Connection, QUIC_CONN_REF_ROUTE); + QUIC_STATUS Status = + CxPlatResolveRoute( + Path->Binding->Socket, &Path->Route, Path->ID, (void*)Connection, QuicConnQueueRouteCompletion); + if (Status == QUIC_STATUS_SUCCESS) { + QuicConnRelease(Connection, QUIC_CONN_REF_ROUTE); + } else { + // + // Route resolution failed or pended. We need to pause sending. + // + CXPLAT_DBG_ASSERT(Status == QUIC_STATUS_PENDING || QUIC_FAILED(Status)); + return FALSE; + } + } else if (Path->Route.State == RouteResolving) { + // + // Can't send now. Once route resolution completes, we will resume sending. + // + return FALSE; + } + return TRUE; +} + // // This function sends a path challenge frame out on all paths that currently // need one sent. @@ -1042,7 +1081,7 @@ QuicSendPathChallenges( continue; } - if (!CxPlatIsRouteReady(Connection, QuicConnQueueRouteCompletion, TRUE)) { + if (Connection->IsRawDatapath && !CxPlatIsRouteReady(Connection, TRUE)) { Send->SendFlags |= QUIC_CONN_SEND_FLAG_PATH_CHALLENGE; continue; } @@ -1132,7 +1171,7 @@ QuicSendFlush( CXPLAT_DBG_ASSERT(!Connection->State.HandleClosed); - if (!CxPlatIsRouteReady(Connection, QuicConnQueueRouteCompletion, FALSE)) { + if (Connection->IsRawDatapath && !CxPlatIsRouteReady(Connection, FALSE)) { return TRUE; } diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 674bfe11f7..b81c3491c8 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -747,13 +747,6 @@ void typedef CXPLAT_ROUTE_RESOLUTION_CALLBACK *CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER; typedef struct QUIC_CONNECTION QUIC_CONNECTION; -BOOLEAN -CxPlatIsRouteReady( - _In_ QUIC_CONNECTION *Connection, - _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback, - _In_ BOOLEAN PathChallenge - ); - // // Copies L2 address into route object and sets route state to resolved. // @@ -792,6 +785,10 @@ CxPlatUpdateRoute( _In_ CXPLAT_ROUTE* SrcRoute ); +void +CxPlatSetDataPathType( + _Out_ BOOLEAN* IsRaw + ); #if defined(__cplusplus) } diff --git a/src/platform/datapath_epoll.c b/src/platform/datapath_epoll.c index af365478cf..eb767a7bea 100644 --- a/src/platform/datapath_epoll.c +++ b/src/platform/datapath_epoll.c @@ -2633,18 +2633,6 @@ QuicCopyRouteInfo( *DstRoute = *SrcRoute; } -BOOLEAN -CxPlatIsRouteReady( - _In_ QUIC_CONNECTION *Connection, - _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback, - _In_ BOOLEAN PathChallenge -) { - UNREFERENCED_PARAMETER(Connection); - UNREFERENCED_PARAMETER(Callback); - UNREFERENCED_PARAMETER(PathChallenge); - return TRUE; -} - void CxPlatResolveRouteComplete( _In_ void* Context, @@ -2687,3 +2675,11 @@ CxPlatUpdateRoute( UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); } + +void +CxPlatSetDataPathType( + _Out_ BOOLEAN* IsRaw + ) +{ + *IsRaw = FALSE; +} \ No newline at end of file diff --git a/src/platform/datapath_kqueue.c b/src/platform/datapath_kqueue.c index 392452c1b7..e08abb1323 100644 --- a/src/platform/datapath_kqueue.c +++ b/src/platform/datapath_kqueue.c @@ -2317,3 +2317,64 @@ CxPlatDataPathProcessCqe( } } } + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +QuicCopyRouteInfo( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ + *DstRoute = *SrcRoute; +} + +void +CxPlatResolveRouteComplete( + _In_ void* Context, + _Inout_ CXPLAT_ROUTE* Route, + _In_reads_bytes_(6) const uint8_t* PhysicalAddress, + _In_ uint8_t PathId + ) +{ + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Route); + UNREFERENCED_PARAMETER(PhysicalAddress); + UNREFERENCED_PARAMETER(PathId); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatResolveRoute( + _In_ CXPLAT_SOCKET* Socket, + _Inout_ CXPLAT_ROUTE* Route, + _In_ uint8_t PathId, + _In_ void* Context, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback + ) +{ + UNREFERENCED_PARAMETER(Socket); + UNREFERENCED_PARAMETER(Route); + UNREFERENCED_PARAMETER(PathId); + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Callback); + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatUpdateRoute( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ + UNREFERENCED_PARAMETER(DstRoute); + UNREFERENCED_PARAMETER(SrcRoute); +} + +void +CxPlatSetDataPathType( + _Out_ BOOLEAN* IsRaw + ) +{ + *IsRaw = FALSE; +} \ No newline at end of file diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index 330828888d..acf39742e2 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -43,46 +43,6 @@ CxPlatGetSocket( return Socket; } -BOOLEAN -CxPlatIsRouteReady( - _In_ QUIC_CONNECTION *Connection, - _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback, - _In_ BOOLEAN PathChallenge -) { - QUIC_PATH* Path = &Connection->Paths[0]; - // - // Make sure the route is resolved before sending packets. - // - // - // We need to set the path challenge flag back on so that when route is resolved, - // we know we need to continue to send the challenge. - // - CXPLAT_DBG_ASSERT((!PathChallenge && Path->IsActive) || - (PathChallenge && Path->Route.State != RouteSuspected)); - if ((!PathChallenge && Path->Route.State == RouteUnresolved || Path->Route.State == RouteSuspected) || - (PathChallenge && Path->Route.State == RouteUnresolved)) { - QuicConnAddRef(Connection, QUIC_CONN_REF_ROUTE); - QUIC_STATUS Status = - CxPlatResolveRoute( - Path->Binding->Socket, &Path->Route, Path->ID, (void*)Connection, QuicConnQueueRouteCompletion); - if (Status == QUIC_STATUS_SUCCESS) { - QuicConnRelease(Connection, QUIC_CONN_REF_ROUTE); - } else { - // - // Route resolution failed or pended. We need to pause sending. - // - CXPLAT_DBG_ASSERT(Status == QUIC_STATUS_PENDING || QUIC_FAILED(Status)); - return FALSE; - } - } else if (Path->Route.State == RouteResolving) { - // - // Can't send now. Once route resolution completes, we will resume sending. - // - return FALSE; - } - return TRUE; -} - _IRQL_requires_max_(PASSIVE_LEVEL) void QuicCopyRouteInfo( @@ -844,3 +804,11 @@ CxPlatFramingWriteHeaders( Buffer->Length += TransportLength + IpHeaderLen + sizeof(ETHERNET_HEADER); Buffer->Buffer -= TransportLength + IpHeaderLen + sizeof(ETHERNET_HEADER); } + +void +CxPlatSetDataPathType( + _Out_ BOOLEAN* IsRaw + ) +{ + *IsRaw = TRUE; +} diff --git a/src/platform/datapath_winkernel.c b/src/platform/datapath_winkernel.c index adb3e0ff89..9c8e9b2982 100644 --- a/src/platform/datapath_winkernel.c +++ b/src/platform/datapath_winkernel.c @@ -3151,3 +3151,64 @@ CxPlatSocketSend( return STATUS_SUCCESS; } + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +QuicCopyRouteInfo( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ + *DstRoute = *SrcRoute; +} + +void +CxPlatResolveRouteComplete( + _In_ void* Context, + _Inout_ CXPLAT_ROUTE* Route, + _In_reads_bytes_(6) const uint8_t* PhysicalAddress, + _In_ uint8_t PathId + ) +{ + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Route); + UNREFERENCED_PARAMETER(PhysicalAddress); + UNREFERENCED_PARAMETER(PathId); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatResolveRoute( + _In_ CXPLAT_SOCKET* Socket, + _Inout_ CXPLAT_ROUTE* Route, + _In_ uint8_t PathId, + _In_ void* Context, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback + ) +{ + UNREFERENCED_PARAMETER(Socket); + UNREFERENCED_PARAMETER(Route); + UNREFERENCED_PARAMETER(PathId); + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Callback); + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatUpdateRoute( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ + UNREFERENCED_PARAMETER(DstRoute); + UNREFERENCED_PARAMETER(SrcRoute); +} + +void +CxPlatSetDataPathType( + _Out_ BOOLEAN* IsRaw + ) +{ + *IsRaw = FALSE; +} \ No newline at end of file diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index a5e48cb8a2..7472437d4c 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -5149,18 +5149,6 @@ QuicCopyRouteInfo( *DstRoute = *SrcRoute; } -BOOLEAN -CxPlatIsRouteReady( - _In_ QUIC_CONNECTION *Connection, - _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback, - _In_ BOOLEAN PathChallenge -) { - UNREFERENCED_PARAMETER(Connection); - UNREFERENCED_PARAMETER(Callback); - UNREFERENCED_PARAMETER(PathChallenge); - return TRUE; -} - void CxPlatResolveRouteComplete( _In_ void* Context, @@ -5203,3 +5191,11 @@ CxPlatUpdateRoute( UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); } + +void +CxPlatSetDataPathType( + _Out_ BOOLEAN* IsRaw + ) +{ + *IsRaw = FALSE; +} \ No newline at end of file diff --git a/submodules/googletest b/submodules/googletest index 01e18376ef..4a1a299b20 160000 --- a/submodules/googletest +++ b/submodules/googletest @@ -1 +1 @@ -Subproject commit 01e18376efe643a82cff468734f87f8c60e314b6 +Subproject commit 4a1a299b206ba250a4318f74938ea67c75c3c0c9 From 4a1d1c07d27f0d87f0fc015cc036285e82eb98e1 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Sat, 29 Jul 2023 12:27:12 -0700 Subject: [PATCH 03/87] remove QUIC_USE_RAW_DATAPATH from test --- CMakeLists.txt | 4 -- src/core/connection.c | 1 - src/core/connection.h | 5 --- src/inc/quic_datapath.h | 6 +-- src/platform/datapath_epoll.c | 8 ++-- src/platform/datapath_kqueue.c | 8 ++-- src/platform/datapath_raw_socket.c | 10 ++--- src/platform/datapath_winkernel.c | 8 ++-- src/platform/datapath_winuser.c | 8 ++-- src/platform/unittest/DataPathTest.cpp | 23 ++++++++---- src/test/bin/quic_gtest.cpp | 52 +++++++++++++++----------- src/test/bin/quic_gtest.h | 10 +++-- src/test/lib/ApiTest.cpp | 29 +++++++------- src/test/lib/DataTest.cpp | 6 +-- src/test/lib/MtuTest.cpp | 6 +-- 15 files changed, 90 insertions(+), 94 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e21b766d1..2de5843e07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -333,10 +333,6 @@ if (QUIC_ENABLE_SANITIZERS) list(APPEND QUIC_COMMON_DEFINES DISABLE_CXPLAT_POOL=1) endif() -if(QUIC_USE_XDP) - list(APPEND QUIC_COMMON_DEFINES QUIC_USE_RAW_DATAPATH=1) -endif() - if(QUIC_OFFICIAL_RELEASE) list(APPEND QUIC_COMMON_DEFINES QUIC_OFFICIAL_RELEASE=1) message(STATUS "Configured for official release build") diff --git a/src/core/connection.c b/src/core/connection.c index 429c993a57..4e3f8cbe04 100644 --- a/src/core/connection.c +++ b/src/core/connection.c @@ -137,7 +137,6 @@ QuicConnAlloc( QuicRangeInitialize( QUIC_MAX_RANGE_DECODE_ACKS, &Connection->DecodedAckRanges); - CxPlatSetDataPathType(&Connection->IsRawDatapath); for (uint32_t i = 0; i < ARRAYSIZE(Connection->Packets); i++) { Status = diff --git a/src/core/connection.h b/src/core/connection.h index c450968a46..f0ac6c1e8b 100644 --- a/src/core/connection.h +++ b/src/core/connection.h @@ -641,11 +641,6 @@ typedef struct QUIC_CONNECTION { QUIC_FLOW_BLOCKED_TIMING_TRACKER FlowControl; } BlockedTimings; - // - // flag whether the datapath is raw or not - // - BOOLEAN IsRawDatapath; - } QUIC_CONNECTION; typedef struct QUIC_SERIALIZED_RESUMPTION_STATE { diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index b81c3491c8..a9be10e36a 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -785,10 +785,8 @@ CxPlatUpdateRoute( _In_ CXPLAT_ROUTE* SrcRoute ); -void -CxPlatSetDataPathType( - _Out_ BOOLEAN* IsRaw - ); +BOOLEAN +CxPlatIsRawDatapath(); #if defined(__cplusplus) } diff --git a/src/platform/datapath_epoll.c b/src/platform/datapath_epoll.c index eb767a7bea..beda76cf5e 100644 --- a/src/platform/datapath_epoll.c +++ b/src/platform/datapath_epoll.c @@ -2676,10 +2676,8 @@ CxPlatUpdateRoute( UNREFERENCED_PARAMETER(SrcRoute); } -void -CxPlatSetDataPathType( - _Out_ BOOLEAN* IsRaw - ) +BOOLEAN +CxPlatIsRawDatapath() { - *IsRaw = FALSE; + return FALSE; } \ No newline at end of file diff --git a/src/platform/datapath_kqueue.c b/src/platform/datapath_kqueue.c index e08abb1323..4899babe13 100644 --- a/src/platform/datapath_kqueue.c +++ b/src/platform/datapath_kqueue.c @@ -2371,10 +2371,8 @@ CxPlatUpdateRoute( UNREFERENCED_PARAMETER(SrcRoute); } -void -CxPlatSetDataPathType( - _Out_ BOOLEAN* IsRaw - ) +BOOLEAN +CxPlatIsRawDatapath() { - *IsRaw = FALSE; + return FALSE; } \ No newline at end of file diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index acf39742e2..6475883e3e 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -805,10 +805,8 @@ CxPlatFramingWriteHeaders( Buffer->Buffer -= TransportLength + IpHeaderLen + sizeof(ETHERNET_HEADER); } -void -CxPlatSetDataPathType( - _Out_ BOOLEAN* IsRaw - ) +BOOLEAN +CxPlatIsRawDatapath() { - *IsRaw = TRUE; -} + return TRUE; +} \ No newline at end of file diff --git a/src/platform/datapath_winkernel.c b/src/platform/datapath_winkernel.c index 9c8e9b2982..fa4df8b8f7 100644 --- a/src/platform/datapath_winkernel.c +++ b/src/platform/datapath_winkernel.c @@ -3205,10 +3205,8 @@ CxPlatUpdateRoute( UNREFERENCED_PARAMETER(SrcRoute); } -void -CxPlatSetDataPathType( - _Out_ BOOLEAN* IsRaw - ) +BOOLEAN +CxPlatIsRawDatapath() { - *IsRaw = FALSE; + return FALSE; } \ No newline at end of file diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 7472437d4c..4e121aa15b 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -5192,10 +5192,8 @@ CxPlatUpdateRoute( UNREFERENCED_PARAMETER(SrcRoute); } -void -CxPlatSetDataPathType( - _Out_ BOOLEAN* IsRaw - ) +BOOLEAN +CxPlatIsRawDatapath() { - *IsRaw = FALSE; + return FALSE; } \ No newline at end of file diff --git a/src/platform/unittest/DataPathTest.cpp b/src/platform/unittest/DataPathTest.cpp index a144a4b4ac..01159d5606 100644 --- a/src/platform/unittest/DataPathTest.cpp +++ b/src/platform/unittest/DataPathTest.cpp @@ -455,7 +455,6 @@ struct CxPlatDataPath { uint32_t GetSupportedFeatures() const noexcept { return CxPlatDataPathGetSupportedFeatures(Datapath); } }; -#ifdef QUIC_USE_RAW_DATAPATH static _IRQL_requires_max_(DISPATCH_LEVEL) _Function_class_(CXPLAT_ROUTE_RESOLUTION_CALLBACK) @@ -474,7 +473,6 @@ ResolveRouteComplete( CxPlatResolveRouteComplete(nullptr, (CXPLAT_ROUTE*)Context, PhysicalAddress, 0); } } -#endif // QUIC_USE_RAW_DATAPATH struct CxPlatSocket { CXPLAT_SOCKET* Socket {nullptr}; @@ -535,8 +533,7 @@ struct CxPlatSocket { if (QUIC_SUCCEEDED(InitStatus)) { CxPlatSocketGetLocalAddress(Socket, &Route.LocalAddress); CxPlatSocketGetRemoteAddress(Socket, &Route.RemoteAddress); -#ifdef QUIC_USE_RAW_DATAPATH - if (!QuicAddrIsWildCard(&Route.RemoteAddress)) { + if (CxPlatIsRawDatapath() && !QuicAddrIsWildCard(&Route.RemoteAddress)) { // // This is a connected socket and its route must be resolved // to be able to send traffic. @@ -549,7 +546,6 @@ struct CxPlatSocket { // EXPECT_EQ(InitStatus, QUIC_STATUS_SUCCESS); } -#endif } } void CreateTcp( @@ -1007,10 +1003,12 @@ TEST_P(DataPathTest, MultiBindListener) { } #ifdef _WIN32 -#ifndef QUIC_USE_RAW_DATAPATH TEST_F(DataPathTest, TcpListener) { CxPlatDataPath Datapath(nullptr, &EmptyTcpCallbacks); + if (!CxPlatIsRawDatapath()) { + GTEST_SKIP_("Raw Datapath Required"); + } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); ASSERT_NE(nullptr, Datapath.Datapath); @@ -1024,6 +1022,9 @@ TEST_F(DataPathTest, TcpListener) TEST_P(DataPathTest, TcpConnect) { CxPlatDataPath Datapath(nullptr, &TcpRecvCallbacks); + if (!CxPlatIsRawDatapath()) { + GTEST_SKIP_("Raw Datapath Required"); + } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); ASSERT_NE(nullptr, Datapath.Datapath); @@ -1057,6 +1058,9 @@ TEST_P(DataPathTest, TcpConnect) TEST_P(DataPathTest, TcpDisconnect) { CxPlatDataPath Datapath(nullptr, &TcpRecvCallbacks); + if (!CxPlatIsRawDatapath()) { + GTEST_SKIP_("Raw Datapath Required"); + } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); ASSERT_NE(nullptr, Datapath.Datapath); @@ -1090,6 +1094,9 @@ TEST_P(DataPathTest, TcpDisconnect) TEST_P(DataPathTest, TcpDataClient) { CxPlatDataPath Datapath(nullptr, &TcpRecvCallbacks); + if (!CxPlatIsRawDatapath()) { + GTEST_SKIP_("Raw Datapath Required"); + } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); ASSERT_NE(nullptr, Datapath.Datapath); @@ -1129,6 +1136,9 @@ TEST_P(DataPathTest, TcpDataClient) TEST_P(DataPathTest, TcpDataServer) { CxPlatDataPath Datapath(nullptr, &TcpRecvCallbacks); + if (!CxPlatIsRawDatapath()) { + GTEST_SKIP_("Raw Datapath Required"); + } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); ASSERT_NE(nullptr, Datapath.Datapath); @@ -1171,7 +1181,6 @@ TEST_P(DataPathTest, TcpDataServer) SendData)); ASSERT_TRUE(CxPlatEventWaitWithTimeout(ClientContext.ReceiveEvent, 500)); } -#endif // QUIC_USE_RAW_DATAPATH #endif // WIN32 INSTANTIATE_TEST_SUITE_P(DataPathTest, DataPathTest, ::testing::Values(4, 6), testing::PrintToStringParamName()); diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 757938e5ab..124528a423 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -13,7 +13,7 @@ bool TestingKernelMode = false; bool PrivateTestLibrary = false; bool UseDuoNic = false; -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) bool UseQTIP = false; #endif const MsQuicApi* MsQuic; @@ -81,7 +81,7 @@ class QuicTestEnvironment : public ::testing::Environment { printf("Initializing for User Mode tests\n"); MsQuic = new(std::nothrow) MsQuicApi(); ASSERT_TRUE(QUIC_SUCCEEDED(MsQuic->GetInitStatus())); -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { QUIC_EXECUTION_CONFIG Config = {QUIC_EXECUTION_CONFIG_FLAG_QTIP, 10000, 0}; ASSERT_TRUE(QUIC_SUCCEEDED( @@ -1399,8 +1399,11 @@ TEST_P(WithHandshakeArgs4, RandomLossResumeRejection) { #endif // QUIC_DISABLE_RESUMPTION #endif // QUIC_TEST_DATAPATH_HOOKS_ENABLED -#ifndef QUIC_USE_RAW_DATAPATH TEST_P(WithFamilyArgs, Unreachable) { + if (CxPlatIsRawDatapath()) { + GTEST_SKIP_("Raw Datapath doesn't support."); + } + if (GetParam().Family == 4 && IsWindows2019()) GTEST_SKIP(); // IPv4 unreachable doesn't work on 2019 TestLoggerT Logger("QuicTestConnectUnreachable", GetParam()); if (TestingKernelMode) { @@ -1409,7 +1412,6 @@ TEST_P(WithFamilyArgs, Unreachable) { QuicTestConnectUnreachable(GetParam().Family); } } -#endif // QUIC_USE_RAW_DATAPATH TEST(HandshakeTest, InvalidAddress) { TestLogger Logger("QuicTestConnectInvalidAddress"); @@ -1458,7 +1460,7 @@ TEST_P(WithFamilyArgs, ClientBlockedSourcePort) { #if QUIC_TEST_DATAPATH_HOOKS_ENABLED TEST_P(WithFamilyArgs, RebindPort) { -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. @@ -1479,7 +1481,7 @@ TEST_P(WithFamilyArgs, RebindPort) { } TEST_P(WithRebindPaddingArgs, RebindPortPadded) { -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. @@ -1500,7 +1502,7 @@ TEST_P(WithRebindPaddingArgs, RebindPortPadded) { } TEST_P(WithFamilyArgs, RebindAddr) { -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. @@ -1521,7 +1523,7 @@ TEST_P(WithFamilyArgs, RebindAddr) { } TEST_P(WithRebindPaddingArgs, RebindAddrPadded) { -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. @@ -1561,8 +1563,11 @@ TEST_P(WithFamilyArgs, ChangeMaxStreamIDs) { } #if QUIC_TEST_DATAPATH_HOOKS_ENABLED -#ifndef QUIC_USE_RAW_DATAPATH // TODO - Support this with raw datapath TEST_P(WithFamilyArgs, LoadBalanced) { + if (CxPlatIsRawDatapath()) { + GTEST_SKIP_("Raw Datapath doesn't support."); + } + #ifdef QUIC_TEST_SCHANNEL_FLAGS if (IsWindows2022()) GTEST_SKIP(); // Not supported with Schannel on WS2022 #endif @@ -1573,7 +1578,6 @@ TEST_P(WithFamilyArgs, LoadBalanced) { QuicTestLoadBalancedHandshake(GetParam().Family); } } -#endif // QUIC_USE_RAW_DATAPATH TEST_P(WithFamilyArgs, HandshakeSpecificLossPatterns) { TestLoggerT Logger("QuicTestHandshakeSpecificLossPatterns", GetParam()); @@ -1705,7 +1709,7 @@ TEST_P(WithSendArgs3, SendIntermittently) { #ifndef QUIC_DISABLE_0RTT_TESTS TEST_P(WithSend0RttArgs1, Send0Rtt) { -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // QTIP doesn't work with 0-RTT. QTIP only pauses and caches 1 packet during @@ -1754,7 +1758,7 @@ TEST_P(WithSend0RttArgs1, Send0Rtt) { } TEST_P(WithSend0RttArgs2, Reject0Rtt) { -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // QTIP doesn't work with 0-RTT. QTIP only pauses and caches 1 packet during @@ -2080,8 +2084,11 @@ TEST(Drill, VarIntEncoder) { } } -#ifndef QUIC_USE_RAW_DATAPATH // TODO - Support this with raw datapath TEST_P(WithDrillInitialPacketCidArgs, DrillInitialPacketCids) { + if (CxPlatIsRawDatapath()) { + GTEST_SKIP_("Raw Datapath doesn't support"); + } + TestLoggerT Logger("QuicDrillInitialPacketCids", GetParam()); if (TestingKernelMode) { QUIC_RUN_DRILL_INITIAL_PACKET_CID_PARAMS Params = { @@ -2103,6 +2110,10 @@ TEST_P(WithDrillInitialPacketCidArgs, DrillInitialPacketCids) { } TEST_P(WithDrillInitialPacketTokenArgs, DrillInitialPacketToken) { + if (CxPlatIsRawDatapath()) { + GTEST_SKIP_("Raw Datapath doesn't support"); + } + TestLoggerT Logger("QuicDrillInitialPacketToken", GetParam()); if (TestingKernelMode) { ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_DRILL_INITIAL_PACKET_TOKEN, GetParam().Family)); @@ -2110,7 +2121,6 @@ TEST_P(WithDrillInitialPacketTokenArgs, DrillInitialPacketToken) { QuicDrillTestInitialToken(GetParam().Family); } } -#endif // QUIC_USE_RAW_DATAPATH TEST_P(WithDatagramNegotiationArgs, DatagramNegotiation) { TestLoggerT Logger("QuicTestDatagramNegotiation", GetParam()); @@ -2339,7 +2349,6 @@ INSTANTIATE_TEST_SUITE_P( WithDatagramNegotiationArgs, testing::ValuesIn(DatagramNegotiationArgs::Generate())); -#ifndef QUIC_USE_RAW_DATAPATH INSTANTIATE_TEST_SUITE_P( Drill, WithDrillInitialPacketCidArgs, @@ -2349,7 +2358,6 @@ INSTANTIATE_TEST_SUITE_P( Drill, WithDrillInitialPacketTokenArgs, testing::ValuesIn(DrillInitialPacketTokenArgs::Generate())); -#endif // QUIC_USE_RAW_DATAPATH int main(int argc, char** argv) { #ifdef _WIN32 @@ -2375,12 +2383,14 @@ int main(int argc, char** argv) { } else if (strcmp("--duoNic", argv[i]) == 0) { UseDuoNic = true; } else if (strcmp("--useQTIP", argv[i]) == 0) { -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - UseQTIP = true; -#else - printf("QTIP is not supported in this build.\n"); - return -1; + UseQTIP = CxPlatIsRawDatapath(); +#if !defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + UseQTIP = false; #endif + if (!UseQTIP) { + printf("QTIP is not supported in this build.\n"); + return -1; + } } else if (strstr(argv[i], "--osRunner")) { OsRunner = argv[i] + sizeof("--osRunner"); } else if (strcmp("--timeout", argv[i]) == 0) { diff --git a/src/test/bin/quic_gtest.h b/src/test/bin/quic_gtest.h index 890e2f5819..4b2579ef34 100644 --- a/src/test/bin/quic_gtest.h +++ b/src/test/bin/quic_gtest.h @@ -8,6 +8,7 @@ #define QUIC_TEST_APIS 1 #include "quic_platform.h" +#include "quic_datapath.h" #include "MsQuicTests.h" #include "msquichelper.h" #include "quic_trace.h" @@ -20,7 +21,7 @@ #endif extern bool TestingKernelMode; -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif @@ -346,7 +347,7 @@ struct SendArgs2 { for (bool UseZeroRtt : { false }) #endif { -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP && UseZeroRtt) { continue; } @@ -697,10 +698,11 @@ struct ValidateConnectionEventArgs { uint32_t Test; static ::std::vector Generate() { ::std::vector list; -#if !defined(QUIC_DISABLE_0RTT_TESTS) && !defined(QUIC_USE_RAW_DATAPATH) // TODO: Fix openssl/XDP bug and enable this back +#if !defined(QUIC_DISABLE_0RTT_TESTS) // TODO: Fix openssl/XDP bug and enable this back for (uint32_t Test = 0; Test < 3; ++Test) #else - for (uint32_t Test = 0; Test < 2; ++Test) + uint32_t TestCount = 3 - static_cast(CxPlatIsRawDatapath()); + for (uint32_t Test = 0; Test < TestCount; ++Test) #endif list.push_back({ Test }); return list; diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index e25c763d80..ff5af110d8 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -16,7 +16,7 @@ #pragma warning(disable:6387) // '_Param_(1)' could be '0': this does not adhere to the specification for the function -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif @@ -2517,8 +2517,8 @@ void QuicTestGlobalParam() // SimpleGetParamTest(nullptr, QUIC_PARAM_GLOBAL_EXECUTION_CONFIG, DataLength, Data); } -#ifdef QUIC_USE_RAW_DATAPATH - if (!UseQTIP) { + + if (CxPlatIsRawDatapath() && !UseQTIP) { // // Good GetParam with length == 0 when QTIP is not in use. // @@ -2529,19 +2529,18 @@ void QuicTestGlobalParam() QUIC_PARAM_GLOBAL_EXECUTION_CONFIG, &BufferLength, nullptr)); + } else { + // + // Good GetParam with length == 0 + // + uint32_t BufferLength = 0; + TEST_QUIC_SUCCEEDED( + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_EXECUTION_CONFIG, + &BufferLength, + nullptr)); } -#else - // - // Good GetParam with length == 0 - // - uint32_t BufferLength = 0; - TEST_QUIC_SUCCEEDED( - MsQuic->GetParam( - nullptr, - QUIC_PARAM_GLOBAL_EXECUTION_CONFIG, - &BufferLength, - nullptr)); -#endif // QUIC_USE_RAW_DATAPATH } #endif // QUIC_API_ENABLE_PREVIEW_FEATURES #endif // !_KERNEL_MODE diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index e451177a82..7fee05a306 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -14,7 +14,7 @@ #include "DataTest.cpp.clog.h" #endif -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif @@ -505,7 +505,7 @@ QuicTestConnectAndPing( } TEST_QUIC_SUCCEEDED(Connections.get()[i]->SetRemoteAddr(RemoteAddr)); -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (!UseQTIP && i != 0) { Connections.get()[i]->SetLocalAddr(LocalAddr); } @@ -520,7 +520,7 @@ QuicTestConnectAndPing( QuicAddrFamily, ClientZeroRtt ? QUIC_LOCALHOST_FOR_AF(QuicAddrFamily) : nullptr, ServerLocalAddr.GetPort())); -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (!UseQTIP && i == 0) { Connections.get()[i]->GetLocalAddr(LocalAddr); } diff --git a/src/test/lib/MtuTest.cpp b/src/test/lib/MtuTest.cpp index 45fd7d69be..56102e60ae 100644 --- a/src/test/lib/MtuTest.cpp +++ b/src/test/lib/MtuTest.cpp @@ -14,9 +14,7 @@ #include "MtuTest.cpp.clog.h" #endif -#ifdef QUIC_USE_RAW_DATAPATH extern bool UseQTIP; -#endif static QUIC_STATUS MtuStreamCallback(_In_ MsQuicStream*, _In_opt_ void*, _Inout_ QUIC_STREAM_EVENT*) { return QUIC_STATUS_SUCCESS; @@ -49,7 +47,7 @@ struct ResetSettings { void QuicTestMtuSettings() { -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) const uint16_t DefaultMaximumMtu = UseQTIP ? 1488 : 1500; // reserve 12B for TCP header #else const uint16_t DefaultMaximumMtu = 1500; @@ -315,7 +313,7 @@ QuicTestMtuDiscovery( TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); const uint16_t MinimumMtu = RaiseMinimumMtu ? 1360 : 1248; -#if defined(QUIC_USE_RAW_DATAPATH) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) const uint16_t MaximumMtu = UseQTIP ? 1488 : 1500; // reserve 12B for TCP header #else const uint16_t MaximumMtu = 1500; From 8faacacad466747d4d1333c895780caf11533707 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Sat, 29 Jul 2023 21:29:46 -0700 Subject: [PATCH 04/87] fix googletest version and add last line --- src/platform/datapath_epoll.c | 2 +- src/platform/datapath_kqueue.c | 2 +- src/platform/datapath_raw_socket.c | 2 +- src/platform/datapath_winkernel.c | 2 +- src/platform/datapath_winuser.c | 2 +- submodules/googletest | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platform/datapath_epoll.c b/src/platform/datapath_epoll.c index beda76cf5e..1d20779cb3 100644 --- a/src/platform/datapath_epoll.c +++ b/src/platform/datapath_epoll.c @@ -2680,4 +2680,4 @@ BOOLEAN CxPlatIsRawDatapath() { return FALSE; -} \ No newline at end of file +} diff --git a/src/platform/datapath_kqueue.c b/src/platform/datapath_kqueue.c index 4899babe13..0eb1a19198 100644 --- a/src/platform/datapath_kqueue.c +++ b/src/platform/datapath_kqueue.c @@ -2375,4 +2375,4 @@ BOOLEAN CxPlatIsRawDatapath() { return FALSE; -} \ No newline at end of file +} diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index 6475883e3e..433f7f11e8 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -809,4 +809,4 @@ BOOLEAN CxPlatIsRawDatapath() { return TRUE; -} \ No newline at end of file +} diff --git a/src/platform/datapath_winkernel.c b/src/platform/datapath_winkernel.c index fa4df8b8f7..1c47f0165f 100644 --- a/src/platform/datapath_winkernel.c +++ b/src/platform/datapath_winkernel.c @@ -3209,4 +3209,4 @@ BOOLEAN CxPlatIsRawDatapath() { return FALSE; -} \ No newline at end of file +} diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 4e121aa15b..babc3caf0c 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -5196,4 +5196,4 @@ BOOLEAN CxPlatIsRawDatapath() { return FALSE; -} \ No newline at end of file +} diff --git a/submodules/googletest b/submodules/googletest index 4a1a299b20..01e18376ef 160000 --- a/submodules/googletest +++ b/submodules/googletest @@ -1 +1 @@ -Subproject commit 4a1a299b206ba250a4318f74938ea67c75c3c0c9 +Subproject commit 01e18376efe643a82cff468734f87f8c60e314b6 From cef044deb83a27d456933eb323d16c6e2c2759c6 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Sat, 29 Jul 2023 21:45:57 -0700 Subject: [PATCH 05/87] replace from flag to function call --- src/core/send.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/send.c b/src/core/send.c index 652a9cc9b2..6bdf23476a 100644 --- a/src/core/send.c +++ b/src/core/send.c @@ -1081,7 +1081,7 @@ QuicSendPathChallenges( continue; } - if (Connection->IsRawDatapath && !CxPlatIsRouteReady(Connection, TRUE)) { + if (CxPlatIsRawDatapath() && !CxPlatIsRouteReady(Connection, TRUE)) { Send->SendFlags |= QUIC_CONN_SEND_FLAG_PATH_CHALLENGE; continue; } @@ -1171,7 +1171,7 @@ QuicSendFlush( CXPLAT_DBG_ASSERT(!Connection->State.HandleClosed); - if (Connection->IsRawDatapath && !CxPlatIsRouteReady(Connection, FALSE)) { + if (CxPlatIsRawDatapath() && !CxPlatIsRouteReady(Connection, FALSE)) { return TRUE; } From 54e953d01ed8e0e15f150d3c9ca58e4981286bb0 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Sat, 29 Jul 2023 22:58:17 -0700 Subject: [PATCH 06/87] fix build error --- src/core/send.c | 2 +- src/test/bin/quic_gtest.cpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/send.c b/src/core/send.c index 6bdf23476a..fb174a2ce2 100644 --- a/src/core/send.c +++ b/src/core/send.c @@ -1033,7 +1033,7 @@ CxPlatIsRouteReady( // CXPLAT_DBG_ASSERT((!PathChallenge && Path->IsActive) || (PathChallenge && Path->Route.State != RouteSuspected)); - if ((!PathChallenge && Path->Route.State == RouteUnresolved || Path->Route.State == RouteSuspected) || + if ((!PathChallenge && (Path->Route.State == RouteUnresolved || Path->Route.State == RouteSuspected)) || (PathChallenge && Path->Route.State == RouteUnresolved)) { QuicConnAddRef(Connection, QUIC_CONN_REF_ROUTE); QUIC_STATUS Status = diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 124528a423..eab0e41fce 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -13,9 +13,7 @@ bool TestingKernelMode = false; bool PrivateTestLibrary = false; bool UseDuoNic = false; -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) bool UseQTIP = false; -#endif const MsQuicApi* MsQuic; const char* OsRunner = nullptr; uint32_t Timeout = UINT32_MAX; From d90c4d90a9c94ff3663b8641a315835e1a556f29 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Sun, 30 Jul 2023 23:49:35 -0700 Subject: [PATCH 07/87] fix build/test issues --- src/inc/quic_datapath.h | 2 +- src/test/bin/quic_gtest.h | 10 ++++------ src/test/lib/ApiTest.cpp | 2 -- src/test/lib/DataTest.cpp | 2 -- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index a9be10e36a..aa262b52da 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -752,7 +752,7 @@ typedef struct QUIC_CONNECTION QUIC_CONNECTION; // void CxPlatResolveRouteComplete( - _In_ void* Connection, + _In_ void* Context, _Inout_ CXPLAT_ROUTE* Route, _In_reads_bytes_(6) const uint8_t* PhysicalAddress, _In_ uint8_t PathId diff --git a/src/test/bin/quic_gtest.h b/src/test/bin/quic_gtest.h index 4b2579ef34..19c95571ee 100644 --- a/src/test/bin/quic_gtest.h +++ b/src/test/bin/quic_gtest.h @@ -21,9 +21,7 @@ #endif extern bool TestingKernelMode; -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; -#endif class WithBool : public testing::Test, public testing::WithParamInterface { @@ -698,12 +696,12 @@ struct ValidateConnectionEventArgs { uint32_t Test; static ::std::vector Generate() { ::std::vector list; + uint32_t TestCount = 3; + #if !defined(QUIC_DISABLE_0RTT_TESTS) // TODO: Fix openssl/XDP bug and enable this back - for (uint32_t Test = 0; Test < 3; ++Test) -#else - uint32_t TestCount = 3 - static_cast(CxPlatIsRawDatapath()); - for (uint32_t Test = 0; Test < TestCount; ++Test) + TestCount = CxPlatIsRawDatapath() ? 2 : 3; #endif + for (uint32_t Test = 0; Test < TestCount; ++Test) list.push_back({ Test }); return list; } diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index ff5af110d8..af22fc2a84 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -16,9 +16,7 @@ #pragma warning(disable:6387) // '_Param_(1)' could be '0': this does not adhere to the specification for the function -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; -#endif void QuicTestValidateApi() { diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index 7fee05a306..ad920aa9fc 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -14,9 +14,7 @@ #include "DataTest.cpp.clog.h" #endif -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; -#endif /* Helper function to estimate a maximum timeout for a test with a From 3c3af4ef4927173a191e832bdd11ebde3e87f6b2 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Mon, 31 Jul 2023 09:50:48 -0700 Subject: [PATCH 08/87] implement CxPlatResolveRoute for normal socket --- src/core/path.c | 1 - src/core/send.c | 8 ++++++-- src/platform/datapath_epoll.c | 4 ++-- src/platform/datapath_kqueue.c | 4 ++-- src/platform/datapath_raw.h | 1 - src/platform/datapath_winkernel.c | 4 ++-- src/platform/datapath_winuser.c | 4 ++-- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/core/path.c b/src/core/path.c index a4ac11179c..270312ea28 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -35,7 +35,6 @@ QuicPathInitialize( Path->EcnValidationState = Connection->Settings.EcnEnabled ? ECN_VALIDATION_TESTING : ECN_VALIDATION_FAILED; - // for RAW datapath if (MsQuicLib.ExecutionConfig && MsQuicLib.ExecutionConfig->Flags & QUIC_EXECUTION_CONFIG_FLAG_QTIP) { CxPlatRandom(sizeof(Path->Route.TcpState.SequenceNumber), &Path->Route.TcpState.SequenceNumber); diff --git a/src/core/send.c b/src/core/send.c index fb174a2ce2..6a58d29999 100644 --- a/src/core/send.c +++ b/src/core/send.c @@ -1027,6 +1027,10 @@ CxPlatIsRouteReady( // // Make sure the route is resolved before sending packets. // + if (Path->Route.State == RouteResolved) { + return TRUE; + } + // // We need to set the path challenge flag back on so that when route is resolved, // we know we need to continue to send the challenge. @@ -1081,7 +1085,7 @@ QuicSendPathChallenges( continue; } - if (CxPlatIsRawDatapath() && !CxPlatIsRouteReady(Connection, TRUE)) { + if (!CxPlatIsRouteReady(Connection, TRUE)) { Send->SendFlags |= QUIC_CONN_SEND_FLAG_PATH_CHALLENGE; continue; } @@ -1171,7 +1175,7 @@ QuicSendFlush( CXPLAT_DBG_ASSERT(!Connection->State.HandleClosed); - if (CxPlatIsRawDatapath() && !CxPlatIsRouteReady(Connection, FALSE)) { + if (!CxPlatIsRouteReady(Connection, FALSE)) { return TRUE; } diff --git a/src/platform/datapath_epoll.c b/src/platform/datapath_epoll.c index 1d20779cb3..de6868b65b 100644 --- a/src/platform/datapath_epoll.c +++ b/src/platform/datapath_epoll.c @@ -2658,11 +2658,11 @@ CxPlatResolveRoute( ) { UNREFERENCED_PARAMETER(Socket); - UNREFERENCED_PARAMETER(Route); UNREFERENCED_PARAMETER(PathId); UNREFERENCED_PARAMETER(Context); UNREFERENCED_PARAMETER(Callback); - return QUIC_STATUS_NOT_SUPPORTED; + Route->State = RouteResolved; + return QUIC_STATUS_SUCCESS; } _IRQL_requires_max_(PASSIVE_LEVEL) diff --git a/src/platform/datapath_kqueue.c b/src/platform/datapath_kqueue.c index 0eb1a19198..a97aa9618a 100644 --- a/src/platform/datapath_kqueue.c +++ b/src/platform/datapath_kqueue.c @@ -2353,11 +2353,11 @@ CxPlatResolveRoute( ) { UNREFERENCED_PARAMETER(Socket); - UNREFERENCED_PARAMETER(Route); UNREFERENCED_PARAMETER(PathId); UNREFERENCED_PARAMETER(Context); UNREFERENCED_PARAMETER(Callback); - return QUIC_STATUS_NOT_SUPPORTED; + Route->State = RouteResolved; + return QUIC_STATUS_SUCCESS; } _IRQL_requires_max_(PASSIVE_LEVEL) diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index ed69710ced..ec2b08ad6f 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -18,7 +18,6 @@ typedef struct CXPLAT_SOCKET_POOL { } CXPLAT_SOCKET_POOL; typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; -typedef struct QUIC_PATH QUIC_PATH; // // A worker thread for draining queued route resolution operations. diff --git a/src/platform/datapath_winkernel.c b/src/platform/datapath_winkernel.c index 1c47f0165f..38a11c6049 100644 --- a/src/platform/datapath_winkernel.c +++ b/src/platform/datapath_winkernel.c @@ -3187,11 +3187,11 @@ CxPlatResolveRoute( ) { UNREFERENCED_PARAMETER(Socket); - UNREFERENCED_PARAMETER(Route); UNREFERENCED_PARAMETER(PathId); UNREFERENCED_PARAMETER(Context); UNREFERENCED_PARAMETER(Callback); - return QUIC_STATUS_NOT_SUPPORTED; + Route->State = RouteResolved; + return QUIC_STATUS_SUCCESS; } _IRQL_requires_max_(PASSIVE_LEVEL) diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index babc3caf0c..253c958a79 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -5174,11 +5174,11 @@ CxPlatResolveRoute( ) { UNREFERENCED_PARAMETER(Socket); - UNREFERENCED_PARAMETER(Route); UNREFERENCED_PARAMETER(PathId); UNREFERENCED_PARAMETER(Context); UNREFERENCED_PARAMETER(Callback); - return QUIC_STATUS_NOT_SUPPORTED; + Route->State = RouteResolved; + return QUIC_STATUS_SUCCESS; } _IRQL_requires_max_(PASSIVE_LEVEL) From 4252477c7650014512568e7e39d6bd95fbe8b4ab Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Mon, 31 Jul 2023 16:29:54 -0700 Subject: [PATCH 09/87] add Getter of Datapath feature --- src/core/library.c | 23 ++++++++ src/core/send.c | 13 ++--- src/inc/msquic.h | 1 + src/inc/quic_datapath.h | 5 +- src/platform/datapath_epoll.c | 10 ++-- src/platform/datapath_kqueue.c | 6 --- src/platform/datapath_raw_linux.c | 2 +- src/platform/datapath_raw_socket.c | 6 --- src/platform/datapath_raw_win.c | 2 +- src/platform/datapath_winkernel.c | 10 ++-- src/platform/datapath_winuser.c | 10 ++-- src/platform/unittest/DataPathTest.cpp | 23 ++++---- src/test/bin/quic_gtest.cpp | 72 +++++++++++++++----------- src/test/bin/quic_gtest.h | 10 +++- src/test/lib/ApiTest.cpp | 69 +++++++++++++++++++----- src/test/lib/DataTest.cpp | 14 +---- src/test/lib/MtuTest.cpp | 8 --- src/tools/etw/quicetw.h | 4 ++ 18 files changed, 170 insertions(+), 118 deletions(-) diff --git a/src/core/library.c b/src/core/library.c index 22ee0a23cd..d3de6de5eb 100644 --- a/src/core/library.c +++ b/src/core/library.c @@ -1293,6 +1293,29 @@ QuicLibraryGetGlobalParam( Status = QUIC_STATUS_SUCCESS; break; + case QUIC_PARAM_GLOBAL_DATAPATH_FEATURES: + if (*BufferLength < sizeof(uint32_t)) { + *BufferLength = sizeof(uint32_t); + Status = QUIC_STATUS_BUFFER_TOO_SMALL; + break; + } + + if (Buffer == NULL) { + Status = QUIC_STATUS_INVALID_PARAMETER; + break; + } + + if (MsQuicLib.Datapath == NULL) { + Status = QUIC_STATUS_INVALID_STATE; + break; + } + + *BufferLength = sizeof(uint32_t); + *(uint32_t*)Buffer = CxPlatDataPathGetSupportedFeatures(MsQuicLib.Datapath); + + Status = QUIC_STATUS_SUCCESS; + break; + case QUIC_PARAM_GLOBAL_VERSION_NEGOTIATION_ENABLED: if (*BufferLength < sizeof(BOOLEAN)) { diff --git a/src/core/send.c b/src/core/send.c index 6a58d29999..e83480f2ec 100644 --- a/src/core/send.c +++ b/src/core/send.c @@ -1020,8 +1020,7 @@ QuicSendGetNextStream( BOOLEAN CxPlatIsRouteReady( - _In_ QUIC_CONNECTION *Connection, - _In_ BOOLEAN PathChallenge + _In_ QUIC_CONNECTION *Connection ) { QUIC_PATH* Path = &Connection->Paths[0]; // @@ -1035,10 +1034,8 @@ CxPlatIsRouteReady( // We need to set the path challenge flag back on so that when route is resolved, // we know we need to continue to send the challenge. // - CXPLAT_DBG_ASSERT((!PathChallenge && Path->IsActive) || - (PathChallenge && Path->Route.State != RouteSuspected)); - if ((!PathChallenge && (Path->Route.State == RouteUnresolved || Path->Route.State == RouteSuspected)) || - (PathChallenge && Path->Route.State == RouteUnresolved)) { + CXPLAT_DBG_ASSERT(Path->IsActive); + if (Path->Route.State == RouteUnresolved) { QuicConnAddRef(Connection, QUIC_CONN_REF_ROUTE); QUIC_STATUS Status = CxPlatResolveRoute( @@ -1085,7 +1082,7 @@ QuicSendPathChallenges( continue; } - if (!CxPlatIsRouteReady(Connection, TRUE)) { + if (!CxPlatIsRouteReady(Connection)) { Send->SendFlags |= QUIC_CONN_SEND_FLAG_PATH_CHALLENGE; continue; } @@ -1175,7 +1172,7 @@ QuicSendFlush( CXPLAT_DBG_ASSERT(!Connection->State.HandleClosed); - if (!CxPlatIsRouteReady(Connection, FALSE)) { + if (!CxPlatIsRouteReady(Connection)) { return TRUE; } diff --git a/src/inc/msquic.h b/src/inc/msquic.h index ad9c5b4a14..fea90ade99 100644 --- a/src/inc/msquic.h +++ b/src/inc/msquic.h @@ -828,6 +828,7 @@ void #define QUIC_PARAM_GLOBAL_EXECUTION_CONFIG 0x01000009 // QUIC_EXECUTION_CONFIG #endif #define QUIC_PARAM_GLOBAL_TLS_PROVIDER 0x0100000A // QUIC_TLS_PROVIDER +#define QUIC_PARAM_GLOBAL_DATAPATH_FEATURES 0x0100000B // uint32_t // // Parameters for Registration. diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index aa262b52da..3b6d160f44 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -444,6 +444,8 @@ CxPlatDataPathUpdateConfig( #define CXPLAT_DATAPATH_FEATURE_SEND_SEGMENTATION 0x0004 #define CXPLAT_DATAPATH_FEATURE_LOCAL_PORT_SHARING 0x0008 #define CXPLAT_DATAPATH_FEATURE_PORT_RESERVATIONS 0x0010 +#define CXPLAT_DATAPATH_FEATURE_TCP 0x0020 +#define CXPLAT_DATAPATH_FEATURE_RAW_SOCKET 0x0040 // // Queries the currently supported features of the datapath. @@ -785,9 +787,6 @@ CxPlatUpdateRoute( _In_ CXPLAT_ROUTE* SrcRoute ); -BOOLEAN -CxPlatIsRawDatapath(); - #if defined(__cplusplus) } #endif diff --git a/src/platform/datapath_epoll.c b/src/platform/datapath_epoll.c index de6868b65b..7994104091 100644 --- a/src/platform/datapath_epoll.c +++ b/src/platform/datapath_epoll.c @@ -567,6 +567,10 @@ CxPlatDataPathCalculateFeatureSupport( if (SendSocket != INVALID_SOCKET) { close(SendSocket); } #endif // UDP_SEGMENT +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + Datapath->Features |= CXPLAT_DATAPATH_FEATURE_TCP; +#endif + if (Datapath->Features & CXPLAT_DATAPATH_FEATURE_SEND_SEGMENTATION) { Datapath->SendDataSize = sizeof(CXPLAT_SEND_DATA); Datapath->SendIoVecCount = 1; @@ -2675,9 +2679,3 @@ CxPlatUpdateRoute( UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); } - -BOOLEAN -CxPlatIsRawDatapath() -{ - return FALSE; -} diff --git a/src/platform/datapath_kqueue.c b/src/platform/datapath_kqueue.c index a97aa9618a..bcb377e97b 100644 --- a/src/platform/datapath_kqueue.c +++ b/src/platform/datapath_kqueue.c @@ -2370,9 +2370,3 @@ CxPlatUpdateRoute( UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); } - -BOOLEAN -CxPlatIsRawDatapath() -{ - return FALSE; -} diff --git a/src/platform/datapath_raw_linux.c b/src/platform/datapath_raw_linux.c index eb11929965..741bdcadf3 100644 --- a/src/platform/datapath_raw_linux.c +++ b/src/platform/datapath_raw_linux.c @@ -244,7 +244,7 @@ CxPlatDataPathGetSupportedFeatures( ) { UNREFERENCED_PARAMETER(Datapath); - return 0; + return CXPLAT_DATAPATH_FEATURE_RAW_SOCKET; } _IRQL_requires_max_(DISPATCH_LEVEL) diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index 433f7f11e8..9cdc3a346e 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -804,9 +804,3 @@ CxPlatFramingWriteHeaders( Buffer->Length += TransportLength + IpHeaderLen + sizeof(ETHERNET_HEADER); Buffer->Buffer -= TransportLength + IpHeaderLen + sizeof(ETHERNET_HEADER); } - -BOOLEAN -CxPlatIsRawDatapath() -{ - return TRUE; -} diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index 0c4b5bb8d6..010faed490 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -242,7 +242,7 @@ CxPlatDataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH* Datapath ) { - return 0; + return CXPLAT_DATAPATH_FEATURE_RAW_SOCKET; } _IRQL_requires_max_(DISPATCH_LEVEL) diff --git a/src/platform/datapath_winkernel.c b/src/platform/datapath_winkernel.c index 38a11c6049..26344eb7f7 100644 --- a/src/platform/datapath_winkernel.c +++ b/src/platform/datapath_winkernel.c @@ -796,6 +796,10 @@ CxPlatDataPathQuerySockoptSupport( } while (FALSE); +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + Datapath->Features |= CXPLAT_DATAPATH_FEATURE_TCP; +#endif + Error: if (UdpSocket != NULL) { @@ -3204,9 +3208,3 @@ CxPlatUpdateRoute( UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); } - -BOOLEAN -CxPlatIsRawDatapath() -{ - return FALSE; -} diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 253c958a79..44c9b6bdc7 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1138,6 +1138,10 @@ CxPlatDataPathQuerySockoptSupport( } else { Datapath->Features |= CXPLAT_DATAPATH_FEATURE_RECV_COALESCING; } + +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + Datapath->Features |= CXPLAT_DATAPATH_FEATURE_TCP; +#endif } Error: @@ -5191,9 +5195,3 @@ CxPlatUpdateRoute( UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); } - -BOOLEAN -CxPlatIsRawDatapath() -{ - return FALSE; -} diff --git a/src/platform/unittest/DataPathTest.cpp b/src/platform/unittest/DataPathTest.cpp index 01159d5606..ba008d2747 100644 --- a/src/platform/unittest/DataPathTest.cpp +++ b/src/platform/unittest/DataPathTest.cpp @@ -533,7 +533,8 @@ struct CxPlatSocket { if (QUIC_SUCCEEDED(InitStatus)) { CxPlatSocketGetLocalAddress(Socket, &Route.LocalAddress); CxPlatSocketGetRemoteAddress(Socket, &Route.RemoteAddress); - if (CxPlatIsRawDatapath() && !QuicAddrIsWildCard(&Route.RemoteAddress)) { + if (!!(CxPlatDataPathGetSupportedFeatures(Datapath) & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) && + !QuicAddrIsWildCard(&Route.RemoteAddress)) { // // This is a connected socket and its route must be resolved // to be able to send traffic. @@ -1006,8 +1007,8 @@ TEST_P(DataPathTest, MultiBindListener) { TEST_F(DataPathTest, TcpListener) { CxPlatDataPath Datapath(nullptr, &EmptyTcpCallbacks); - if (!CxPlatIsRawDatapath()) { - GTEST_SKIP_("Raw Datapath Required"); + if (!(CxPlatDataPathGetSupportedFeatures(Datapath) & CXPLAT_DATAPATH_FEATURE_TCP)) { + GTEST_SKIP_("TCP is not supported"); } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); ASSERT_NE(nullptr, Datapath.Datapath); @@ -1022,8 +1023,8 @@ TEST_F(DataPathTest, TcpListener) TEST_P(DataPathTest, TcpConnect) { CxPlatDataPath Datapath(nullptr, &TcpRecvCallbacks); - if (!CxPlatIsRawDatapath()) { - GTEST_SKIP_("Raw Datapath Required"); + if (!(CxPlatDataPathGetSupportedFeatures(Datapath) & CXPLAT_DATAPATH_FEATURE_TCP)) { + GTEST_SKIP_("TCP is not supported"); } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); ASSERT_NE(nullptr, Datapath.Datapath); @@ -1058,8 +1059,8 @@ TEST_P(DataPathTest, TcpConnect) TEST_P(DataPathTest, TcpDisconnect) { CxPlatDataPath Datapath(nullptr, &TcpRecvCallbacks); - if (!CxPlatIsRawDatapath()) { - GTEST_SKIP_("Raw Datapath Required"); + if (!(CxPlatDataPathGetSupportedFeatures(Datapath) & CXPLAT_DATAPATH_FEATURE_TCP)) { + GTEST_SKIP_("TCP is not supported"); } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); ASSERT_NE(nullptr, Datapath.Datapath); @@ -1094,8 +1095,8 @@ TEST_P(DataPathTest, TcpDisconnect) TEST_P(DataPathTest, TcpDataClient) { CxPlatDataPath Datapath(nullptr, &TcpRecvCallbacks); - if (!CxPlatIsRawDatapath()) { - GTEST_SKIP_("Raw Datapath Required"); + if (!(CxPlatDataPathGetSupportedFeatures(Datapath) & CXPLAT_DATAPATH_FEATURE_TCP)) { + GTEST_SKIP_("TCP is not supported"); } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); ASSERT_NE(nullptr, Datapath.Datapath); @@ -1136,8 +1137,8 @@ TEST_P(DataPathTest, TcpDataClient) TEST_P(DataPathTest, TcpDataServer) { CxPlatDataPath Datapath(nullptr, &TcpRecvCallbacks); - if (!CxPlatIsRawDatapath()) { - GTEST_SKIP_("Raw Datapath Required"); + if (!(CxPlatDataPathGetSupportedFeatures(Datapath) & CXPLAT_DATAPATH_FEATURE_TCP)) { + GTEST_SKIP_("TCP is not supported"); } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); ASSERT_NE(nullptr, Datapath.Datapath); diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index eab0e41fce..83aa5be62b 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -79,7 +79,6 @@ class QuicTestEnvironment : public ::testing::Environment { printf("Initializing for User Mode tests\n"); MsQuic = new(std::nothrow) MsQuicApi(); ASSERT_TRUE(QUIC_SUCCEEDED(MsQuic->GetInitStatus())); -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { QUIC_EXECUTION_CONFIG Config = {QUIC_EXECUTION_CONFIG_FLAG_QTIP, 10000, 0}; ASSERT_TRUE(QUIC_SUCCEEDED( @@ -89,7 +88,6 @@ class QuicTestEnvironment : public ::testing::Environment { sizeof(Config), &Config))); } -#endif memcpy(&ServerSelfSignedCredConfig, SelfSignedCertParams, sizeof(QUIC_CREDENTIAL_CONFIG)); memcpy(&ServerSelfSignedCredConfigClientAuth, SelfSignedCertParams, sizeof(QUIC_CREDENTIAL_CONFIG)); ServerSelfSignedCredConfigClientAuth.Flags |= @@ -1398,10 +1396,17 @@ TEST_P(WithHandshakeArgs4, RandomLossResumeRejection) { #endif // QUIC_TEST_DATAPATH_HOOKS_ENABLED TEST_P(WithFamilyArgs, Unreachable) { - if (CxPlatIsRawDatapath()) { - GTEST_SKIP_("Raw Datapath doesn't support."); + uint32_t Length = sizeof(uint32_t); + uint32_t Features = 0; + GTEST_ASSERT_EQ(QUIC_STATUS_SUCCESS, + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + &Features)); + if ((Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) == 0) { + GTEST_SKIP_("Raw datapath not enabled"); } - if (GetParam().Family == 4 && IsWindows2019()) GTEST_SKIP(); // IPv4 unreachable doesn't work on 2019 TestLoggerT Logger("QuicTestConnectUnreachable", GetParam()); if (TestingKernelMode) { @@ -1458,14 +1463,12 @@ TEST_P(WithFamilyArgs, ClientBlockedSourcePort) { #if QUIC_TEST_DATAPATH_HOOKS_ENABLED TEST_P(WithFamilyArgs, RebindPort) { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } -#endif TestLoggerT Logger("QuicTestNatPortRebind", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1479,14 +1482,12 @@ TEST_P(WithFamilyArgs, RebindPort) { } TEST_P(WithRebindPaddingArgs, RebindPortPadded) { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } -#endif TestLoggerT Logger("QuicTestNatPortRebind(pad)", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1500,14 +1501,12 @@ TEST_P(WithRebindPaddingArgs, RebindPortPadded) { } TEST_P(WithFamilyArgs, RebindAddr) { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } -#endif TestLoggerT Logger("QuicTestNatAddrRebind", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1521,14 +1520,12 @@ TEST_P(WithFamilyArgs, RebindAddr) { } TEST_P(WithRebindPaddingArgs, RebindAddrPadded) { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } -#endif TestLoggerT Logger("QuicTestNatAddrRebind(pad)", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1562,8 +1559,16 @@ TEST_P(WithFamilyArgs, ChangeMaxStreamIDs) { #if QUIC_TEST_DATAPATH_HOOKS_ENABLED TEST_P(WithFamilyArgs, LoadBalanced) { - if (CxPlatIsRawDatapath()) { - GTEST_SKIP_("Raw Datapath doesn't support."); + uint32_t Length = sizeof(uint32_t); + uint32_t Features = 0; + GTEST_ASSERT_EQ(QUIC_STATUS_SUCCESS, + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + &Features)); + if ((Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) == 0) { + GTEST_SKIP_("Raw datapath not enabled"); } #ifdef QUIC_TEST_SCHANNEL_FLAGS @@ -1707,7 +1712,6 @@ TEST_P(WithSendArgs3, SendIntermittently) { #ifndef QUIC_DISABLE_0RTT_TESTS TEST_P(WithSend0RttArgs1, Send0Rtt) { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // QTIP doesn't work with 0-RTT. QTIP only pauses and caches 1 packet during @@ -1715,7 +1719,6 @@ TEST_P(WithSend0RttArgs1, Send0Rtt) { // return; } -#endif TestLoggerT Logger("Send0Rtt", GetParam()); if (TestingKernelMode) { @@ -1756,7 +1759,6 @@ TEST_P(WithSend0RttArgs1, Send0Rtt) { } TEST_P(WithSend0RttArgs2, Reject0Rtt) { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // QTIP doesn't work with 0-RTT. QTIP only pauses and caches 1 packet during @@ -1764,7 +1766,6 @@ TEST_P(WithSend0RttArgs2, Reject0Rtt) { // return; } -#endif TestLoggerT Logger("Reject0Rtt", GetParam()); if (TestingKernelMode) { QUIC_RUN_CONNECT_AND_PING_PARAMS Params = { @@ -2083,8 +2084,16 @@ TEST(Drill, VarIntEncoder) { } TEST_P(WithDrillInitialPacketCidArgs, DrillInitialPacketCids) { - if (CxPlatIsRawDatapath()) { - GTEST_SKIP_("Raw Datapath doesn't support"); + uint32_t Length = sizeof(uint32_t); + uint32_t Features = 0; + GTEST_ASSERT_EQ(QUIC_STATUS_SUCCESS, + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + &Features)); + if ((Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) == 0) { + GTEST_SKIP_("Raw datapath not enabled"); } TestLoggerT Logger("QuicDrillInitialPacketCids", GetParam()); @@ -2108,8 +2117,16 @@ TEST_P(WithDrillInitialPacketCidArgs, DrillInitialPacketCids) { } TEST_P(WithDrillInitialPacketTokenArgs, DrillInitialPacketToken) { - if (CxPlatIsRawDatapath()) { - GTEST_SKIP_("Raw Datapath doesn't support"); + uint32_t Length = sizeof(uint32_t); + uint32_t Features = 0; + GTEST_ASSERT_EQ(QUIC_STATUS_SUCCESS, + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + &Features)); + if ((Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) == 0) { + GTEST_SKIP_("Raw datapath not enabled"); } TestLoggerT Logger("QuicDrillInitialPacketToken", GetParam()); @@ -2381,14 +2398,7 @@ int main(int argc, char** argv) { } else if (strcmp("--duoNic", argv[i]) == 0) { UseDuoNic = true; } else if (strcmp("--useQTIP", argv[i]) == 0) { - UseQTIP = CxPlatIsRawDatapath(); -#if !defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - UseQTIP = false; -#endif - if (!UseQTIP) { - printf("QTIP is not supported in this build.\n"); - return -1; - } + UseQTIP = true; } else if (strstr(argv[i], "--osRunner")) { OsRunner = argv[i] + sizeof("--osRunner"); } else if (strcmp("--timeout", argv[i]) == 0) { diff --git a/src/test/bin/quic_gtest.h b/src/test/bin/quic_gtest.h index 19c95571ee..6e5b0b5329 100644 --- a/src/test/bin/quic_gtest.h +++ b/src/test/bin/quic_gtest.h @@ -699,7 +699,15 @@ struct ValidateConnectionEventArgs { uint32_t TestCount = 3; #if !defined(QUIC_DISABLE_0RTT_TESTS) // TODO: Fix openssl/XDP bug and enable this back - TestCount = CxPlatIsRawDatapath() ? 2 : 3; + uint32_t Length = sizeof(uint32_t); + uint32_t Features = 0; + GTEST_ASSERT_EQ(QUIC_STATUS_SUCCESS, + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + &Features)); + TestCount = !!(Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) ? 2 : 3; #endif for (uint32_t Test = 0; Test < TestCount; ++Test) list.push_back({ Test }); diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index af22fc2a84..04477a40ea 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -2462,6 +2462,53 @@ void QuicTestGlobalParam() } } + // + // QUIC_PARAM_GLOBAL_DATAPATH_FEATURES + // + { + TestScopeLogger LogScope0("QUIC_PARAM_GLOBAL_DATAPATH_FEATURES"); + GlobalSettingScope ParamScope(QUIC_PARAM_GLOBAL_DATAPATH_FEATURES); + { + TestScopeLogger LogScope1("SetParam"); + // + // Invalid features + // + { + TestScopeLogger LogScope2("SetParam is not allowed"); + TEST_QUIC_STATUS( + QUIC_STATUS_INVALID_PARAMETER, + MsQuic->SetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + 0, + nullptr)); + } + } + + { + TestScopeLogger LogScope2("GetParam"); + uint32_t Length = 0; + TEST_QUIC_STATUS( + QUIC_STATUS_BUFFER_TOO_SMALL, + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + nullptr)); + TEST_EQUAL(Length, sizeof(uint32_t)); + + uint32_t ActualFeatures = 0; + TEST_QUIC_SUCCEEDED( + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + &ActualFeatures)); + TEST_NOT_EQUAL(ActualFeatures, 0); + } + + } + #ifndef _KERNEL_MODE #ifdef QUIC_API_ENABLE_PREVIEW_FEATURES // @@ -2516,18 +2563,16 @@ void QuicTestGlobalParam() SimpleGetParamTest(nullptr, QUIC_PARAM_GLOBAL_EXECUTION_CONFIG, DataLength, Data); } - if (CxPlatIsRawDatapath() && !UseQTIP) { - // - // Good GetParam with length == 0 when QTIP is not in use. - // - uint32_t BufferLength = 0; - TEST_QUIC_SUCCEEDED( - MsQuic->GetParam( - nullptr, - QUIC_PARAM_GLOBAL_EXECUTION_CONFIG, - &BufferLength, - nullptr)); - } else { + uint32_t Length = sizeof(uint32_t); + uint32_t Features = 0; + TEST_QUIC_SUCCEEDED( + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + &Features)); + if (!(Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) || + (!!(Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) && !UseQTIP)) { // // Good GetParam with length == 0 // diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index ad920aa9fc..b4ebb25c2c 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -503,30 +503,20 @@ QuicTestConnectAndPing( } TEST_QUIC_SUCCEEDED(Connections.get()[i]->SetRemoteAddr(RemoteAddr)); -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (!UseQTIP && i != 0) { Connections.get()[i]->SetLocalAddr(LocalAddr); } -#else - if (i != 0) { - Connections.get()[i]->SetLocalAddr(LocalAddr); - } -#endif + TEST_QUIC_SUCCEEDED( Connections.get()[i]->Start( ClientConfiguration, QuicAddrFamily, ClientZeroRtt ? QUIC_LOCALHOST_FOR_AF(QuicAddrFamily) : nullptr, ServerLocalAddr.GetPort())); -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (!UseQTIP && i == 0) { Connections.get()[i]->GetLocalAddr(LocalAddr); } -#else - if (i == 0) { - Connections.get()[i]->GetLocalAddr(LocalAddr); - } -#endif } } } diff --git a/src/test/lib/MtuTest.cpp b/src/test/lib/MtuTest.cpp index 56102e60ae..5e1bfcea24 100644 --- a/src/test/lib/MtuTest.cpp +++ b/src/test/lib/MtuTest.cpp @@ -47,11 +47,7 @@ struct ResetSettings { void QuicTestMtuSettings() { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) const uint16_t DefaultMaximumMtu = UseQTIP ? 1488 : 1500; // reserve 12B for TCP header -#else - const uint16_t DefaultMaximumMtu = 1500; -#endif { // @@ -313,11 +309,7 @@ QuicTestMtuDiscovery( TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); const uint16_t MinimumMtu = RaiseMinimumMtu ? 1360 : 1248; -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) const uint16_t MaximumMtu = UseQTIP ? 1488 : 1500; // reserve 12B for TCP header -#else - const uint16_t MaximumMtu = 1500; -#endif MsQuicAlpn Alpn("MsQuicTest"); MsQuicSettings Settings; diff --git a/src/tools/etw/quicetw.h b/src/tools/etw/quicetw.h index 3ea2b52542..a3aa87e0ed 100644 --- a/src/tools/etw/quicetw.h +++ b/src/tools/etw/quicetw.h @@ -94,6 +94,10 @@ typedef enum QUIC_EVENT_ID_GLOBAL { #define CXPLAT_DATAPATH_FEATURE_RECV_SIDE_SCALING 0x0001 #define CXPLAT_DATAPATH_FEATURE_RECV_COALESCING 0x0002 #define CXPLAT_DATAPATH_FEATURE_SEND_SEGMENTATION 0x0004 +#define CXPLAT_DATAPATH_FEATURE_LOCAL_PORT_SHARING 0x0008 +#define CXPLAT_DATAPATH_FEATURE_PORT_RESERVATIONS 0x0010 +#define CXPLAT_DATAPATH_FEATURE_TCP 0x0020 +#define CXPLAT_DATAPATH_FEATURE_RAW_SOCKET 0x0040 #pragma pack(push) #pragma pack(1) From cd261b4b9acc71b8f2b50af32560fd26a0a3f62f Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Mon, 31 Jul 2023 16:54:07 -0700 Subject: [PATCH 10/87] fix build issues --- src/inc/msquic.h | 4 +--- src/platform/datapath_epoll.c | 4 ---- src/platform/datapath_raw_win.c | 3 ++- src/platform/datapath_winkernel.c | 4 ---- src/platform/datapath_winuser.c | 3 --- src/test/bin/quic_gtest.cpp | 2 +- src/test/bin/quic_gtest.h | 13 +++++-------- 7 files changed, 9 insertions(+), 24 deletions(-) diff --git a/src/inc/msquic.h b/src/inc/msquic.h index fea90ade99..ff28bac745 100644 --- a/src/inc/msquic.h +++ b/src/inc/msquic.h @@ -258,8 +258,8 @@ typedef enum QUIC_DATAGRAM_SEND_STATE { typedef enum QUIC_EXECUTION_CONFIG_FLAGS { QUIC_EXECUTION_CONFIG_FLAG_NONE = 0x0000, -#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES QUIC_EXECUTION_CONFIG_FLAG_QTIP = 0x0001, +#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES QUIC_EXECUTION_CONFIG_FLAG_RIO = 0x0002, #endif } QUIC_EXECUTION_CONFIG_FLAGS; @@ -824,9 +824,7 @@ void #define QUIC_PARAM_GLOBAL_VERSION_SETTINGS 0x01000007 // QUIC_VERSION_SETTINGS #endif #define QUIC_PARAM_GLOBAL_LIBRARY_GIT_HASH 0x01000008 // char[64] -#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES #define QUIC_PARAM_GLOBAL_EXECUTION_CONFIG 0x01000009 // QUIC_EXECUTION_CONFIG -#endif #define QUIC_PARAM_GLOBAL_TLS_PROVIDER 0x0100000A // QUIC_TLS_PROVIDER #define QUIC_PARAM_GLOBAL_DATAPATH_FEATURES 0x0100000B // uint32_t diff --git a/src/platform/datapath_epoll.c b/src/platform/datapath_epoll.c index 7994104091..5e0b331632 100644 --- a/src/platform/datapath_epoll.c +++ b/src/platform/datapath_epoll.c @@ -567,10 +567,6 @@ CxPlatDataPathCalculateFeatureSupport( if (SendSocket != INVALID_SOCKET) { close(SendSocket); } #endif // UDP_SEGMENT -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - Datapath->Features |= CXPLAT_DATAPATH_FEATURE_TCP; -#endif - if (Datapath->Features & CXPLAT_DATAPATH_FEATURE_SEND_SEGMENTATION) { Datapath->SendDataSize = sizeof(CXPLAT_SEND_DATA); Datapath->SendIoVecCount = 1; diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index 010faed490..83ab4db892 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -242,7 +242,8 @@ CxPlatDataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH* Datapath ) { - return CXPLAT_DATAPATH_FEATURE_RAW_SOCKET; + return CXPLAT_DATAPATH_FEATURE_RAW_SOCKET | + CXPLAT_DATAPATH_FEATURE_TCP; } _IRQL_requires_max_(DISPATCH_LEVEL) diff --git a/src/platform/datapath_winkernel.c b/src/platform/datapath_winkernel.c index 26344eb7f7..6ee2888310 100644 --- a/src/platform/datapath_winkernel.c +++ b/src/platform/datapath_winkernel.c @@ -796,10 +796,6 @@ CxPlatDataPathQuerySockoptSupport( } while (FALSE); -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - Datapath->Features |= CXPLAT_DATAPATH_FEATURE_TCP; -#endif - Error: if (UdpSocket != NULL) { diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 44c9b6bdc7..cb6640a917 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1139,9 +1139,6 @@ CxPlatDataPathQuerySockoptSupport( Datapath->Features |= CXPLAT_DATAPATH_FEATURE_RECV_COALESCING; } -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - Datapath->Features |= CXPLAT_DATAPATH_FEATURE_TCP; -#endif } Error: diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 83aa5be62b..2d9a5a20e5 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -80,7 +80,7 @@ class QuicTestEnvironment : public ::testing::Environment { MsQuic = new(std::nothrow) MsQuicApi(); ASSERT_TRUE(QUIC_SUCCEEDED(MsQuic->GetInitStatus())); if (UseQTIP) { - QUIC_EXECUTION_CONFIG Config = {QUIC_EXECUTION_CONFIG_FLAG_QTIP, 10000, 0}; + QUIC_EXECUTION_CONFIG Config = {QUIC_EXECUTION_CONFIG_FLAG_QTIP, 10000, 0, {0}}; ASSERT_TRUE(QUIC_SUCCEEDED( MsQuic->SetParam( nullptr, diff --git a/src/test/bin/quic_gtest.h b/src/test/bin/quic_gtest.h index 6e5b0b5329..5632972569 100644 --- a/src/test/bin/quic_gtest.h +++ b/src/test/bin/quic_gtest.h @@ -345,11 +345,9 @@ struct SendArgs2 { for (bool UseZeroRtt : { false }) #endif { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP && UseZeroRtt) { continue; } -#endif list.push_back({ Family, UseSendBuffer, UseZeroRtt }); } return list; @@ -701,12 +699,11 @@ struct ValidateConnectionEventArgs { #if !defined(QUIC_DISABLE_0RTT_TESTS) // TODO: Fix openssl/XDP bug and enable this back uint32_t Length = sizeof(uint32_t); uint32_t Features = 0; - GTEST_ASSERT_EQ(QUIC_STATUS_SUCCESS, - MsQuic->GetParam( - nullptr, - QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, - &Length, - &Features)); + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + &Features); TestCount = !!(Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) ? 2 : 3; #endif for (uint32_t Test = 0; Test < TestCount; ++Test) From 5930c93b523f126630741587aae0f4b7f10bbfa3 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 1 Aug 2023 11:59:37 -0700 Subject: [PATCH 11/87] adopt comments --- src/core/path.c | 2 + src/core/send.c | 2 +- src/inc/msquic.h | 3 +- src/inc/msquicp.h | 1 + src/inc/quic_datapath.h | 2 +- src/platform/datapath_raw_linux.c | 2 +- src/platform/datapath_raw_win.c | 3 +- src/platform/datapath_winuser.c | 3 +- src/platform/unittest/DataPathTest.cpp | 15 +++-- src/test/MsQuicTests.h | 8 +++ src/test/bin/quic_gtest.cpp | 81 +++++++++++--------------- src/test/bin/quic_gtest.h | 32 +++++++--- src/test/lib/ApiTest.cpp | 18 +++--- src/test/lib/DataTest.cpp | 12 +++- src/test/lib/MtuTest.cpp | 14 ++++- src/tools/etw/quicetw.h | 2 +- 16 files changed, 113 insertions(+), 87 deletions(-) diff --git a/src/core/path.c b/src/core/path.c index 270312ea28..6156b2db72 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -35,10 +35,12 @@ QuicPathInitialize( Path->EcnValidationState = Connection->Settings.EcnEnabled ? ECN_VALIDATION_TESTING : ECN_VALIDATION_FAILED; +#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES if (MsQuicLib.ExecutionConfig && MsQuicLib.ExecutionConfig->Flags & QUIC_EXECUTION_CONFIG_FLAG_QTIP) { CxPlatRandom(sizeof(Path->Route.TcpState.SequenceNumber), &Path->Route.TcpState.SequenceNumber); } +#endif QuicTraceLogConnInfo( PathInitialized, diff --git a/src/core/send.c b/src/core/send.c index e83480f2ec..609fb68257 100644 --- a/src/core/send.c +++ b/src/core/send.c @@ -1035,7 +1035,7 @@ CxPlatIsRouteReady( // we know we need to continue to send the challenge. // CXPLAT_DBG_ASSERT(Path->IsActive); - if (Path->Route.State == RouteUnresolved) { + if (Path->Route.State == RouteUnresolved || Path->Route.State == RouteSuspected) { QuicConnAddRef(Connection, QUIC_CONN_REF_ROUTE); QUIC_STATUS Status = CxPlatResolveRoute( diff --git a/src/inc/msquic.h b/src/inc/msquic.h index ff28bac745..573d17bbd8 100644 --- a/src/inc/msquic.h +++ b/src/inc/msquic.h @@ -258,8 +258,8 @@ typedef enum QUIC_DATAGRAM_SEND_STATE { typedef enum QUIC_EXECUTION_CONFIG_FLAGS { QUIC_EXECUTION_CONFIG_FLAG_NONE = 0x0000, - QUIC_EXECUTION_CONFIG_FLAG_QTIP = 0x0001, #ifdef QUIC_API_ENABLE_PREVIEW_FEATURES + QUIC_EXECUTION_CONFIG_FLAG_QTIP = 0x0001, QUIC_EXECUTION_CONFIG_FLAG_RIO = 0x0002, #endif } QUIC_EXECUTION_CONFIG_FLAGS; @@ -826,7 +826,6 @@ void #define QUIC_PARAM_GLOBAL_LIBRARY_GIT_HASH 0x01000008 // char[64] #define QUIC_PARAM_GLOBAL_EXECUTION_CONFIG 0x01000009 // QUIC_EXECUTION_CONFIG #define QUIC_PARAM_GLOBAL_TLS_PROVIDER 0x0100000A // QUIC_TLS_PROVIDER -#define QUIC_PARAM_GLOBAL_DATAPATH_FEATURES 0x0100000B // uint32_t // // Parameters for Registration. diff --git a/src/inc/msquicp.h b/src/inc/msquicp.h index adf423339e..b9aa0a586c 100644 --- a/src/inc/msquicp.h +++ b/src/inc/msquicp.h @@ -114,6 +114,7 @@ typedef struct QUIC_PRIVATE_TRANSPORT_PARAMETER { #define QUIC_PARAM_GLOBAL_VERSION_NEGOTIATION_ENABLED 0x81000003 // BOOLEAN #endif #define QUIC_PARAM_GLOBAL_IN_USE 0x81000004 // BOOLEAN +#define QUIC_PARAM_GLOBAL_DATAPATH_FEATURES 0x81000005 // uint32_t // // The different private parameters for Configuration. diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 3b6d160f44..8751780d53 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -445,7 +445,7 @@ CxPlatDataPathUpdateConfig( #define CXPLAT_DATAPATH_FEATURE_LOCAL_PORT_SHARING 0x0008 #define CXPLAT_DATAPATH_FEATURE_PORT_RESERVATIONS 0x0010 #define CXPLAT_DATAPATH_FEATURE_TCP 0x0020 -#define CXPLAT_DATAPATH_FEATURE_RAW_SOCKET 0x0040 +#define CXPLAT_DATAPATH_FEATURE_RAW 0x0040 // // Queries the currently supported features of the datapath. diff --git a/src/platform/datapath_raw_linux.c b/src/platform/datapath_raw_linux.c index 741bdcadf3..5ab92ed393 100644 --- a/src/platform/datapath_raw_linux.c +++ b/src/platform/datapath_raw_linux.c @@ -244,7 +244,7 @@ CxPlatDataPathGetSupportedFeatures( ) { UNREFERENCED_PARAMETER(Datapath); - return CXPLAT_DATAPATH_FEATURE_RAW_SOCKET; + return CXPLAT_DATAPATH_FEATURE_RAW; } _IRQL_requires_max_(DISPATCH_LEVEL) diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index 83ab4db892..1ddc5ee280 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -242,8 +242,7 @@ CxPlatDataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH* Datapath ) { - return CXPLAT_DATAPATH_FEATURE_RAW_SOCKET | - CXPLAT_DATAPATH_FEATURE_TCP; + return CXPLAT_DATAPATH_FEATURE_RAW; } _IRQL_requires_max_(DISPATCH_LEVEL) diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index cb6640a917..9141762fc1 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1138,9 +1138,10 @@ CxPlatDataPathQuerySockoptSupport( } else { Datapath->Features |= CXPLAT_DATAPATH_FEATURE_RECV_COALESCING; } - } + Datapath->Features |= CXPLAT_DATAPATH_FEATURE_TCP; + Error: if (UdpSocket != INVALID_SOCKET) { diff --git a/src/platform/unittest/DataPathTest.cpp b/src/platform/unittest/DataPathTest.cpp index ba008d2747..03717d1c7a 100644 --- a/src/platform/unittest/DataPathTest.cpp +++ b/src/platform/unittest/DataPathTest.cpp @@ -453,6 +453,7 @@ struct CxPlatDataPath { CxPlatDataPath operator=(CxPlatDataPath& Other) = delete; operator CXPLAT_DATAPATH* () const noexcept { return Datapath; } uint32_t GetSupportedFeatures() const noexcept { return CxPlatDataPathGetSupportedFeatures(Datapath); } + bool IsSupported(uint32_t feature) const noexcept { return static_cast(GetSupportedFeatures() & feature); } }; static @@ -533,7 +534,7 @@ struct CxPlatSocket { if (QUIC_SUCCEEDED(InitStatus)) { CxPlatSocketGetLocalAddress(Socket, &Route.LocalAddress); CxPlatSocketGetRemoteAddress(Socket, &Route.RemoteAddress); - if (!!(CxPlatDataPathGetSupportedFeatures(Datapath) & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) && + if (Datapath.IsSupported(CXPLAT_DATAPATH_FEATURE_RAW) && !QuicAddrIsWildCard(&Route.RemoteAddress)) { // // This is a connected socket and its route must be resolved @@ -1003,11 +1004,10 @@ TEST_P(DataPathTest, MultiBindListener) { ASSERT_EQ(QUIC_STATUS_ADDRESS_IN_USE, Server2.GetInitStatus()); } -#ifdef _WIN32 TEST_F(DataPathTest, TcpListener) { CxPlatDataPath Datapath(nullptr, &EmptyTcpCallbacks); - if (!(CxPlatDataPathGetSupportedFeatures(Datapath) & CXPLAT_DATAPATH_FEATURE_TCP)) { + if (!Datapath.IsSupported(CXPLAT_DATAPATH_FEATURE_TCP)) { GTEST_SKIP_("TCP is not supported"); } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); @@ -1023,7 +1023,7 @@ TEST_F(DataPathTest, TcpListener) TEST_P(DataPathTest, TcpConnect) { CxPlatDataPath Datapath(nullptr, &TcpRecvCallbacks); - if (!(CxPlatDataPathGetSupportedFeatures(Datapath) & CXPLAT_DATAPATH_FEATURE_TCP)) { + if (!Datapath.IsSupported(CXPLAT_DATAPATH_FEATURE_TCP)) { GTEST_SKIP_("TCP is not supported"); } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); @@ -1059,7 +1059,7 @@ TEST_P(DataPathTest, TcpConnect) TEST_P(DataPathTest, TcpDisconnect) { CxPlatDataPath Datapath(nullptr, &TcpRecvCallbacks); - if (!(CxPlatDataPathGetSupportedFeatures(Datapath) & CXPLAT_DATAPATH_FEATURE_TCP)) { + if (!Datapath.IsSupported(CXPLAT_DATAPATH_FEATURE_TCP)) { GTEST_SKIP_("TCP is not supported"); } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); @@ -1095,7 +1095,7 @@ TEST_P(DataPathTest, TcpDisconnect) TEST_P(DataPathTest, TcpDataClient) { CxPlatDataPath Datapath(nullptr, &TcpRecvCallbacks); - if (!(CxPlatDataPathGetSupportedFeatures(Datapath) & CXPLAT_DATAPATH_FEATURE_TCP)) { + if (!Datapath.IsSupported(CXPLAT_DATAPATH_FEATURE_TCP)) { GTEST_SKIP_("TCP is not supported"); } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); @@ -1137,7 +1137,7 @@ TEST_P(DataPathTest, TcpDataClient) TEST_P(DataPathTest, TcpDataServer) { CxPlatDataPath Datapath(nullptr, &TcpRecvCallbacks); - if (!(CxPlatDataPathGetSupportedFeatures(Datapath) & CXPLAT_DATAPATH_FEATURE_TCP)) { + if (!Datapath.IsSupported(CXPLAT_DATAPATH_FEATURE_TCP)) { GTEST_SKIP_("TCP is not supported"); } VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus()); @@ -1182,6 +1182,5 @@ TEST_P(DataPathTest, TcpDataServer) SendData)); ASSERT_TRUE(CxPlatEventWaitWithTimeout(ClientContext.ReceiveEvent, 500)); } -#endif // WIN32 INSTANTIATE_TEST_SUITE_P(DataPathTest, DataPathTest, ::testing::Values(4, 6), testing::PrintToStringParamName()); diff --git a/src/test/MsQuicTests.h b/src/test/MsQuicTests.h index d0bd406c98..fb7f5b75f5 100644 --- a/src/test/MsQuicTests.h +++ b/src/test/MsQuicTests.h @@ -231,6 +231,14 @@ QuicTestInterfaceBinding( _In_ int Family ); +uint32_t +QuitTestGetDatapathFeatureFlags(); + +bool +QuitTestIsFeatureSupported( + _In_ uint32_t Feature + ); + #ifdef QUIC_API_ENABLE_PREVIEW_FEATURES void QuicTestCibirExtension( diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 2d9a5a20e5..a02837c6e2 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -13,7 +13,9 @@ bool TestingKernelMode = false; bool PrivateTestLibrary = false; bool UseDuoNic = false; +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) bool UseQTIP = false; +#endif const MsQuicApi* MsQuic; const char* OsRunner = nullptr; uint32_t Timeout = UINT32_MAX; @@ -79,7 +81,8 @@ class QuicTestEnvironment : public ::testing::Environment { printf("Initializing for User Mode tests\n"); MsQuic = new(std::nothrow) MsQuicApi(); ASSERT_TRUE(QUIC_SUCCEEDED(MsQuic->GetInitStatus())); - if (UseQTIP) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { QUIC_EXECUTION_CONFIG Config = {QUIC_EXECUTION_CONFIG_FLAG_QTIP, 10000, 0, {0}}; ASSERT_TRUE(QUIC_SUCCEEDED( MsQuic->SetParam( @@ -88,6 +91,7 @@ class QuicTestEnvironment : public ::testing::Environment { sizeof(Config), &Config))); } +#endif memcpy(&ServerSelfSignedCredConfig, SelfSignedCertParams, sizeof(QUIC_CREDENTIAL_CONFIG)); memcpy(&ServerSelfSignedCredConfigClientAuth, SelfSignedCertParams, sizeof(QUIC_CREDENTIAL_CONFIG)); ServerSelfSignedCredConfigClientAuth.Flags |= @@ -1396,15 +1400,7 @@ TEST_P(WithHandshakeArgs4, RandomLossResumeRejection) { #endif // QUIC_TEST_DATAPATH_HOOKS_ENABLED TEST_P(WithFamilyArgs, Unreachable) { - uint32_t Length = sizeof(uint32_t); - uint32_t Features = 0; - GTEST_ASSERT_EQ(QUIC_STATUS_SUCCESS, - MsQuic->GetParam( - nullptr, - QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, - &Length, - &Features)); - if ((Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) == 0) { + if (!QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { GTEST_SKIP_("Raw datapath not enabled"); } if (GetParam().Family == 4 && IsWindows2019()) GTEST_SKIP(); // IPv4 unreachable doesn't work on 2019 @@ -1463,12 +1459,14 @@ TEST_P(WithFamilyArgs, ClientBlockedSourcePort) { #if QUIC_TEST_DATAPATH_HOOKS_ENABLED TEST_P(WithFamilyArgs, RebindPort) { - if (UseQTIP) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } +#endif TestLoggerT Logger("QuicTestNatPortRebind", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1482,12 +1480,14 @@ TEST_P(WithFamilyArgs, RebindPort) { } TEST_P(WithRebindPaddingArgs, RebindPortPadded) { - if (UseQTIP) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } +#endif TestLoggerT Logger("QuicTestNatPortRebind(pad)", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1501,12 +1501,14 @@ TEST_P(WithRebindPaddingArgs, RebindPortPadded) { } TEST_P(WithFamilyArgs, RebindAddr) { - if (UseQTIP) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } +#endif TestLoggerT Logger("QuicTestNatAddrRebind", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1520,12 +1522,14 @@ TEST_P(WithFamilyArgs, RebindAddr) { } TEST_P(WithRebindPaddingArgs, RebindAddrPadded) { - if (UseQTIP) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } +#endif TestLoggerT Logger("QuicTestNatAddrRebind(pad)", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1559,15 +1563,7 @@ TEST_P(WithFamilyArgs, ChangeMaxStreamIDs) { #if QUIC_TEST_DATAPATH_HOOKS_ENABLED TEST_P(WithFamilyArgs, LoadBalanced) { - uint32_t Length = sizeof(uint32_t); - uint32_t Features = 0; - GTEST_ASSERT_EQ(QUIC_STATUS_SUCCESS, - MsQuic->GetParam( - nullptr, - QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, - &Length, - &Features)); - if ((Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) == 0) { + if (!QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { GTEST_SKIP_("Raw datapath not enabled"); } @@ -1712,13 +1708,14 @@ TEST_P(WithSendArgs3, SendIntermittently) { #ifndef QUIC_DISABLE_0RTT_TESTS TEST_P(WithSend0RttArgs1, Send0Rtt) { - if (UseQTIP) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { // - // QTIP doesn't work with 0-RTT. QTIP only pauses and caches 1 packet during - // TCP handshake. + // NAT rebind doesn't make sense for TCP and QTIP. // return; } +#endif TestLoggerT Logger("Send0Rtt", GetParam()); if (TestingKernelMode) { @@ -1759,13 +1756,14 @@ TEST_P(WithSend0RttArgs1, Send0Rtt) { } TEST_P(WithSend0RttArgs2, Reject0Rtt) { - if (UseQTIP) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { // - // QTIP doesn't work with 0-RTT. QTIP only pauses and caches 1 packet during - // TCP handshake. + // NAT rebind doesn't make sense for TCP and QTIP. // return; } +#endif TestLoggerT Logger("Reject0Rtt", GetParam()); if (TestingKernelMode) { QUIC_RUN_CONNECT_AND_PING_PARAMS Params = { @@ -2084,15 +2082,7 @@ TEST(Drill, VarIntEncoder) { } TEST_P(WithDrillInitialPacketCidArgs, DrillInitialPacketCids) { - uint32_t Length = sizeof(uint32_t); - uint32_t Features = 0; - GTEST_ASSERT_EQ(QUIC_STATUS_SUCCESS, - MsQuic->GetParam( - nullptr, - QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, - &Length, - &Features)); - if ((Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) == 0) { + if (!QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { GTEST_SKIP_("Raw datapath not enabled"); } @@ -2117,15 +2107,7 @@ TEST_P(WithDrillInitialPacketCidArgs, DrillInitialPacketCids) { } TEST_P(WithDrillInitialPacketTokenArgs, DrillInitialPacketToken) { - uint32_t Length = sizeof(uint32_t); - uint32_t Features = 0; - GTEST_ASSERT_EQ(QUIC_STATUS_SUCCESS, - MsQuic->GetParam( - nullptr, - QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, - &Length, - &Features)); - if ((Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) == 0) { + if (!QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { GTEST_SKIP_("Raw datapath not enabled"); } @@ -2398,7 +2380,12 @@ int main(int argc, char** argv) { } else if (strcmp("--duoNic", argv[i]) == 0) { UseDuoNic = true; } else if (strcmp("--useQTIP", argv[i]) == 0) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) UseQTIP = true; +#else + printf("QTIP is not supported in this build.\n"); + return -1; +#endif } else if (strstr(argv[i], "--osRunner")) { OsRunner = argv[i] + sizeof("--osRunner"); } else if (strcmp("--timeout", argv[i]) == 0) { diff --git a/src/test/bin/quic_gtest.h b/src/test/bin/quic_gtest.h index 5632972569..5a2b341927 100644 --- a/src/test/bin/quic_gtest.h +++ b/src/test/bin/quic_gtest.h @@ -21,7 +21,26 @@ #endif extern bool TestingKernelMode; +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; +#endif + +uint32_t +QuitTestGetDatapathFeatureFlags() { + static uint32_t Length = sizeof(uint32_t); + uint32_t Features = 0; + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + &Features); + return Features; +} + +bool +QuitTestIsFeatureSupported(uint32_t Feature) { + return static_cast(QuitTestGetDatapathFeatureFlags() & Feature); +} class WithBool : public testing::Test, public testing::WithParamInterface { @@ -345,9 +364,11 @@ struct SendArgs2 { for (bool UseZeroRtt : { false }) #endif { - if (UseQTIP && UseZeroRtt) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP && UseZeroRtt) { continue; } +#endif list.push_back({ Family, UseSendBuffer, UseZeroRtt }); } return list; @@ -697,14 +718,7 @@ struct ValidateConnectionEventArgs { uint32_t TestCount = 3; #if !defined(QUIC_DISABLE_0RTT_TESTS) // TODO: Fix openssl/XDP bug and enable this back - uint32_t Length = sizeof(uint32_t); - uint32_t Features = 0; - MsQuic->GetParam( - nullptr, - QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, - &Length, - &Features); - TestCount = !!(Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) ? 2 : 3; + TestCount = QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) ? 2 : 3; #endif for (uint32_t Test = 0; Test < TestCount; ++Test) list.push_back({ Test }); diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index 04477a40ea..bd16edaa1c 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -16,7 +16,9 @@ #pragma warning(disable:6387) // '_Param_(1)' could be '0': this does not adhere to the specification for the function +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; +#endif void QuicTestValidateApi() { @@ -2563,16 +2565,12 @@ void QuicTestGlobalParam() SimpleGetParamTest(nullptr, QUIC_PARAM_GLOBAL_EXECUTION_CONFIG, DataLength, Data); } - uint32_t Length = sizeof(uint32_t); - uint32_t Features = 0; - TEST_QUIC_SUCCEEDED( - MsQuic->GetParam( - nullptr, - QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, - &Length, - &Features)); - if (!(Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) || - (!!(Features & CXPLAT_DATAPATH_FEATURE_RAW_SOCKET) && !UseQTIP)) { + uint32_t Features = QuitTestGetDatapathFeatureFlags(); + if (!(Features & CXPLAT_DATAPATH_FEATURE_RAW) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + || (!!(Features & CXPLAT_DATAPATH_FEATURE_RAW) && !UseQTIP) +#endif + ) { // // Good GetParam with length == 0 // diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index b4ebb25c2c..209fa9a29f 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -503,7 +503,11 @@ QuicTestConnectAndPing( } TEST_QUIC_SUCCEEDED(Connections.get()[i]->SetRemoteAddr(RemoteAddr)); - if (!UseQTIP && i != 0) { + if (i != 0 +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + && (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && !UseQTIP) +#endif + ) { Connections.get()[i]->SetLocalAddr(LocalAddr); } @@ -514,7 +518,11 @@ QuicTestConnectAndPing( ClientZeroRtt ? QUIC_LOCALHOST_FOR_AF(QuicAddrFamily) : nullptr, ServerLocalAddr.GetPort())); - if (!UseQTIP && i == 0) { + if (i == 0 +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + && (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && !UseQTIP) +#endif + ) { Connections.get()[i]->GetLocalAddr(LocalAddr); } } diff --git a/src/test/lib/MtuTest.cpp b/src/test/lib/MtuTest.cpp index 5e1bfcea24..28c5909c8e 100644 --- a/src/test/lib/MtuTest.cpp +++ b/src/test/lib/MtuTest.cpp @@ -14,7 +14,9 @@ #include "MtuTest.cpp.clog.h" #endif +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; +#endif static QUIC_STATUS MtuStreamCallback(_In_ MsQuicStream*, _In_opt_ void*, _Inout_ QUIC_STREAM_EVENT*) { return QUIC_STATUS_SUCCESS; @@ -47,7 +49,11 @@ struct ResetSettings { void QuicTestMtuSettings() { - const uint16_t DefaultMaximumMtu = UseQTIP ? 1488 : 1500; // reserve 12B for TCP header +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + const uint16_t DefaultMaximumMtu = (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP)? 1488 : 1500; +#else + const uint16_t DefaultMaximumMtu = 1500; +#endif { // @@ -309,7 +315,11 @@ QuicTestMtuDiscovery( TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); const uint16_t MinimumMtu = RaiseMinimumMtu ? 1360 : 1248; - const uint16_t MaximumMtu = UseQTIP ? 1488 : 1500; // reserve 12B for TCP header +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + const uint16_t MaximumMtu = (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP)? 1488 : 1500; +#else + const uint16_t MaximumMtu = 1500; +#endif MsQuicAlpn Alpn("MsQuicTest"); MsQuicSettings Settings; diff --git a/src/tools/etw/quicetw.h b/src/tools/etw/quicetw.h index a3aa87e0ed..c6a8674858 100644 --- a/src/tools/etw/quicetw.h +++ b/src/tools/etw/quicetw.h @@ -97,7 +97,7 @@ typedef enum QUIC_EVENT_ID_GLOBAL { #define CXPLAT_DATAPATH_FEATURE_LOCAL_PORT_SHARING 0x0008 #define CXPLAT_DATAPATH_FEATURE_PORT_RESERVATIONS 0x0010 #define CXPLAT_DATAPATH_FEATURE_TCP 0x0020 -#define CXPLAT_DATAPATH_FEATURE_RAW_SOCKET 0x0040 +#define CXPLAT_DATAPATH_FEATURE_RAW 0x0040 #pragma pack(push) #pragma pack(1) From 0a633d2a83757b2abb5bdf42d3643122c252aa9c Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 1 Aug 2023 13:32:43 -0700 Subject: [PATCH 12/87] adopt comment and fix kernel build error --- src/core/path.c | 2 -- src/core/send.c | 3 ++- src/test/bin/quic_gtest.h | 2 +- src/test/lib/DataTest.cpp | 2 ++ 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/core/path.c b/src/core/path.c index 6156b2db72..270312ea28 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -35,12 +35,10 @@ QuicPathInitialize( Path->EcnValidationState = Connection->Settings.EcnEnabled ? ECN_VALIDATION_TESTING : ECN_VALIDATION_FAILED; -#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES if (MsQuicLib.ExecutionConfig && MsQuicLib.ExecutionConfig->Flags & QUIC_EXECUTION_CONFIG_FLAG_QTIP) { CxPlatRandom(sizeof(Path->Route.TcpState.SequenceNumber), &Path->Route.TcpState.SequenceNumber); } -#endif QuicTraceLogConnInfo( PathInitialized, diff --git a/src/core/send.c b/src/core/send.c index 609fb68257..074788d1cc 100644 --- a/src/core/send.c +++ b/src/core/send.c @@ -1021,7 +1021,8 @@ QuicSendGetNextStream( BOOLEAN CxPlatIsRouteReady( _In_ QUIC_CONNECTION *Connection -) { + ) +{ QUIC_PATH* Path = &Connection->Paths[0]; // // Make sure the route is resolved before sending packets. diff --git a/src/test/bin/quic_gtest.h b/src/test/bin/quic_gtest.h index 5a2b341927..86badfe0b2 100644 --- a/src/test/bin/quic_gtest.h +++ b/src/test/bin/quic_gtest.h @@ -718,7 +718,7 @@ struct ValidateConnectionEventArgs { uint32_t TestCount = 3; #if !defined(QUIC_DISABLE_0RTT_TESTS) // TODO: Fix openssl/XDP bug and enable this back - TestCount = QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) ? 2 : 3; + TestCount = QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) ? 3 : 2; #endif for (uint32_t Test = 0; Test < TestCount; ++Test) list.push_back({ Test }); diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index 209fa9a29f..33a06aed84 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -14,7 +14,9 @@ #include "DataTest.cpp.clog.h" #endif +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; +#endif /* Helper function to estimate a maximum timeout for a test with a From 4ff90daa1ef695731edb93ebdb75b7e1991044ad Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 1 Aug 2023 14:43:38 -0700 Subject: [PATCH 13/87] fix kernel build error --- src/test/bin/quic_gtest.cpp | 2 +- src/test/lib/DataTest.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index a02837c6e2..87d50b3b3b 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -1502,7 +1502,7 @@ TEST_P(WithRebindPaddingArgs, RebindPortPadded) { TEST_P(WithFamilyArgs, RebindAddr) { #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index 33a06aed84..f809a2c80d 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -14,7 +14,7 @@ #include "DataTest.cpp.clog.h" #endif -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif @@ -506,7 +506,7 @@ QuicTestConnectAndPing( TEST_QUIC_SUCCEEDED(Connections.get()[i]->SetRemoteAddr(RemoteAddr)); if (i != 0 -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) && (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && !UseQTIP) #endif ) { @@ -521,7 +521,7 @@ QuicTestConnectAndPing( ServerLocalAddr.GetPort())); if (i == 0 -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) && (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && !UseQTIP) #endif ) { From 6cadc704f01935862e26a0b329a09704c49d8f1b Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 1 Aug 2023 15:06:28 -0700 Subject: [PATCH 14/87] more fix for kernel build --- src/test/lib/ApiTest.cpp | 2 +- src/test/lib/MtuTest.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index bd16edaa1c..0bf5f6ef25 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -16,7 +16,7 @@ #pragma warning(disable:6387) // '_Param_(1)' could be '0': this does not adhere to the specification for the function -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif diff --git a/src/test/lib/MtuTest.cpp b/src/test/lib/MtuTest.cpp index 28c5909c8e..98a605825b 100644 --- a/src/test/lib/MtuTest.cpp +++ b/src/test/lib/MtuTest.cpp @@ -14,7 +14,7 @@ #include "MtuTest.cpp.clog.h" #endif -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif @@ -49,7 +49,7 @@ struct ResetSettings { void QuicTestMtuSettings() { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) const uint16_t DefaultMaximumMtu = (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP)? 1488 : 1500; #else const uint16_t DefaultMaximumMtu = 1500; @@ -315,7 +315,7 @@ QuicTestMtuDiscovery( TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); const uint16_t MinimumMtu = RaiseMinimumMtu ? 1360 : 1248; -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) const uint16_t MaximumMtu = (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP)? 1488 : 1500; #else const uint16_t MaximumMtu = 1500; From cb83953c2b0b95a596caba0d723ca2c225880a1c Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 11 Jul 2023 23:37:12 -0700 Subject: [PATCH 15/87] Unify datapath --- src/inc/quic_datapath.h | 64 +++++++ src/platform/CMakeLists.txt | 2 +- src/platform/datapath_win.c | 289 ++++++++++++++++++++++++++++++++ src/platform/datapath_winuser.c | 59 ++++--- 4 files changed, 387 insertions(+), 27 deletions(-) create mode 100644 src/platform/datapath_win.c diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 67bdb8376d..c9acb0269e 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -782,6 +782,70 @@ CxPlatUpdateRoute( #endif // QUIC_USE_RAW_DATAPATH +typedef struct CXPLAT_DATAPATH_FUNCTIONS { + CXPLAT_RECV_DATA* (*CxPlatDataPathRecvPacketToRecvData)(const CXPLAT_RECV_PACKET* const Context); + CXPLAT_RECV_PACKET* (*CxPlatDataPathRecvDataToRecvPacket)(const CXPLAT_RECV_DATA* const Datagram); + QUIC_STATUS (*CxPlatDataPathInitialize)(_In_ uint32_t ClientRecvContextLength, + _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, + _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, + _In_opt_ QUIC_EXECUTION_CONFIG* Config, + _Out_ CXPLAT_DATAPATH** NewDataPath); + void (*CxPlatDataPathUninitialize)(_In_ CXPLAT_DATAPATH* Datapath); + void (*CxPlatDataPathUpdateConfig)(_In_ CXPLAT_DATAPATH* Datapath, + _In_ QUIC_EXECUTION_CONFIG* Config); + uint32_t (*CxPlatDataPathGetSupportedFeatures)(_In_ CXPLAT_DATAPATH* Datapath); + BOOLEAN (*CxPlatDataPathIsPaddingPreferred)(_In_ CXPLAT_DATAPATH* Datapath); + QUIC_STATUS (*CxPlatDataPathGetLocalAddresses)(_In_ CXPLAT_DATAPATH* Datapath, + _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) + CXPLAT_ADAPTER_ADDRESS** Addresses, + _Out_ uint32_t* AddressesCount); + QUIC_STATUS (*CxPlatDataPathGetGatewayAddresses)(_In_ CXPLAT_DATAPATH* Datapath, + _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) + QUIC_ADDR** GatewayAddresses, + _Out_ uint32_t* GatewayAddressesCount); + QUIC_STATUS (*CxPlatDataPathResolveAddress)(_In_ CXPLAT_DATAPATH* Datapath, + _In_z_ const char* HostName, + _Inout_ QUIC_ADDR* Address); + QUIC_STATUS (*CxPlatSocketCreateUdp)(_In_ CXPLAT_DATAPATH* Datapath, + _In_ const CXPLAT_UDP_CONFIG* Config, + _Out_ CXPLAT_SOCKET** NewSocket); + QUIC_STATUS (*CxPlatSocketCreateTcp)(_In_ CXPLAT_DATAPATH* Datapath, + _In_opt_ const QUIC_ADDR* LocalAddress, + _In_ const QUIC_ADDR* RemoteAddress, + _In_opt_ void* CallbackContext, + _Out_ CXPLAT_SOCKET** Socket); + QUIC_STATUS (*CxPlatSocketCreateTcpListener)(_In_ CXPLAT_DATAPATH* Datapath, + _In_opt_ const QUIC_ADDR* LocalAddress, + _In_opt_ void* RecvCallbackContext, + _Out_ CXPLAT_SOCKET** NewSocket); + void (*CxPlatSocketDelete)(_In_ CXPLAT_SOCKET* Socket); + QUIC_STATUS (*CxPlatSocketUpdateQeo)(_In_ CXPLAT_SOCKET* Socket, + _In_reads_(OffloadCount) + const CXPLAT_QEO_CONNECTION* Offloads, + _In_ uint32_t OffloadCount); + UINT16 (*CxPlatSocketGetLocalMtu)(_In_ CXPLAT_SOCKET* Socket); + void (*CxPlatSocketGetLocalAddress)(_In_ CXPLAT_SOCKET* Socket, + _Out_ QUIC_ADDR* Address); + void (*CxPlatSocketGetRemoteAddress)(_In_ CXPLAT_SOCKET* Socket, + _Out_ QUIC_ADDR* Address); + void (*CxPlatRecvDataReturn)(_In_opt_ CXPLAT_RECV_DATA* RecvDataChain); + CXPLAT_SEND_DATA* (*CxPlatSendDataAlloc)(_In_ CXPLAT_SOCKET* Socket, + _Inout_ CXPLAT_SEND_CONFIG* Config); + void (*CxPlatSendDataFree)(_In_ CXPLAT_SEND_DATA* SendData); + QUIC_BUFFER* (*CxPlatSendDataAllocBuffer)(_In_ CXPLAT_SEND_DATA* SendData, + _In_ uint16_t MaxBufferLength); + void (*CxPlatSendDataFreeBuffer)(_In_ CXPLAT_SEND_DATA* SendData, + _In_ QUIC_BUFFER* Buffer); + BOOLEAN (*CxPlatSendDataIsFull)(_In_ CXPLAT_SEND_DATA* SendData); + QUIC_STATUS (*CxPlatSocketSend)(_In_ CXPLAT_SOCKET* Socket, + _In_ const CXPLAT_ROUTE* Route, + _In_ CXPLAT_SEND_DATA* SendData); + void (*CxPlatDataPathProcessCqe)(_In_ CXPLAT_CQE* Cqe); +} CXPLAT_DATAPATH_FUNCTIONS; + +// TODO: move to each plat? +void InitSocketDataPath(struct CXPLAT_DATAPATH_FUNCTIONS* Funcs); + #if defined(__cplusplus) } #endif diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt index 7bfb572160..73b51a7382 100644 --- a/src/platform/CMakeLists.txt +++ b/src/platform/CMakeLists.txt @@ -16,7 +16,7 @@ if("${CX_PLATFORM}" STREQUAL "windows") if(QUIC_USE_XDP) set(SOURCES ${SOURCES} datapath_raw_win.c datapath_raw_socket.c datapath_raw_socket_win.c datapath_raw_xdp_win.c) else() - set(SOURCES ${SOURCES} datapath_winuser.c) + set(SOURCES ${SOURCES} datapath_win.c datapath_winuser.c) endif() else() set(SOURCES ${SOURCES} inline.c platform_posix.c storage_posix.c cgroup.c) diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c new file mode 100644 index 0000000000..d2e6579e61 --- /dev/null +++ b/src/platform/datapath_win.c @@ -0,0 +1,289 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Datapath Implementation (User Mode) + +--*/ + +#include "platform_internal.h" + +#ifdef QUIC_CLOG +#include "datapath_winuser.c.clog.h" +#endif + +#pragma warning(disable:4116) // unnamed type definition in parentheses + +#pragma warning(disable:4100) // unreferenced +#pragma warning(disable:6101) // uninitialized + +CXPLAT_RECV_DATA* +CxPlatDataPathRecvPacketToRecvData( + _In_ const CXPLAT_RECV_PACKET* const Context + ) +{ + return NULL; +} + +CXPLAT_RECV_PACKET* +CxPlatDataPathRecvDataToRecvPacket( + _In_ const CXPLAT_RECV_DATA* const Datagram + ) +{ + return NULL; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatDataPathInitialize( + _In_ uint32_t ClientRecvContextLength, + _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, + _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, + _In_opt_ QUIC_EXECUTION_CONFIG* Config, + _Out_ CXPLAT_DATAPATH** NewDataPath + ) +{ + fprintf(stderr, "CxPlatDataPathInitialize\n"); + struct CXPLAT_DATAPATH_FUNCTIONS Funcs; // TODO: global + InitSocketDataPath(&Funcs); + + return QUIC_STATUS_SUCCESS; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatDataPathUninitialize( + _In_ CXPLAT_DATAPATH* Datapath + ) +{ +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatDataPathUpdateConfig( + _In_ CXPLAT_DATAPATH* Datapath, + _In_ QUIC_EXECUTION_CONFIG* Config + ) +{ + UNREFERENCED_PARAMETER(Datapath); + UNREFERENCED_PARAMETER(Config); +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +uint32_t +CxPlatDataPathGetSupportedFeatures( + _In_ CXPLAT_DATAPATH* Datapath + ) +{ + return 0; +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +BOOLEAN +CxPlatDataPathIsPaddingPreferred( + _In_ CXPLAT_DATAPATH* Datapath + ) +{ + return FALSE; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +_Success_(QUIC_SUCCEEDED(return)) +QUIC_STATUS +CxPlatDataPathGetLocalAddresses( + _In_ CXPLAT_DATAPATH* Datapath, + _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) + CXPLAT_ADAPTER_ADDRESS** Addresses, + _Out_ uint32_t* AddressesCount + ) +{ + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +_Success_(QUIC_SUCCEEDED(return)) +QUIC_STATUS +CxPlatDataPathGetGatewayAddresses( + _In_ CXPLAT_DATAPATH* Datapath, + _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) + QUIC_ADDR** GatewayAddresses, + _Out_ uint32_t* GatewayAddressesCount + ) +{ + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatDataPathResolveAddress( + _In_ CXPLAT_DATAPATH* Datapath, + _In_z_ const char* HostName, + _Inout_ QUIC_ADDR* Address + ) +{ + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatSocketCreateUdp( + _In_ CXPLAT_DATAPATH* Datapath, + _In_ const CXPLAT_UDP_CONFIG* Config, + _Out_ CXPLAT_SOCKET** NewSocket + ) +{ + fprintf(stderr, "CxPlatSocketCreateUdp\n"); + return QUIC_STATUS_SUCCESS; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatSocketCreateTcp( + _In_ CXPLAT_DATAPATH* Datapath, + _In_opt_ const QUIC_ADDR* LocalAddress, + _In_ const QUIC_ADDR* RemoteAddress, + _In_opt_ void* CallbackContext, + _Out_ CXPLAT_SOCKET** Socket + ) +{ + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatSocketCreateTcpListener( + _In_ CXPLAT_DATAPATH* Datapath, + _In_opt_ const QUIC_ADDR* LocalAddress, + _In_opt_ void* RecvCallbackContext, + _Out_ CXPLAT_SOCKET** NewSocket + ) +{ + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatSocketDelete( + _In_ CXPLAT_SOCKET* Socket + ) +{ +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatSocketUpdateQeo( + _In_ CXPLAT_SOCKET* Socket, + _In_reads_(OffloadCount) + const CXPLAT_QEO_CONNECTION* Offloads, + _In_ uint32_t OffloadCount + ) +{ + UNREFERENCED_PARAMETER(Socket); + UNREFERENCED_PARAMETER(Offloads); + UNREFERENCED_PARAMETER(OffloadCount); + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +UINT16 +CxPlatSocketGetLocalMtu( + _In_ CXPLAT_SOCKET* Socket + ) +{ + return 0; +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +CxPlatSocketGetLocalAddress( + _In_ CXPLAT_SOCKET* Socket, + _Out_ QUIC_ADDR* Address + ) +{ +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +CxPlatSocketGetRemoteAddress( + _In_ CXPLAT_SOCKET* Socket, + _Out_ QUIC_ADDR* Address + ) +{ +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +CxPlatRecvDataReturn( + _In_opt_ CXPLAT_RECV_DATA* RecvDataChain + ) +{ +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +CXPLAT_SEND_DATA* +CxPlatSendDataAlloc( + _In_ CXPLAT_SOCKET* Socket, + _Inout_ CXPLAT_SEND_CONFIG* Config + ) +{ + return NULL; +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +CxPlatSendDataFree( + _In_ CXPLAT_SEND_DATA* SendData + ) +{ +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +QUIC_BUFFER* +CxPlatSendDataAllocBuffer( + _In_ CXPLAT_SEND_DATA* SendData, + _In_ uint16_t MaxBufferLength + ) +{ + return NULL; +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +CxPlatSendDataFreeBuffer( + _In_ CXPLAT_SEND_DATA* SendData, + _In_ QUIC_BUFFER* Buffer + ) +{ +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +BOOLEAN +CxPlatSendDataIsFull( + _In_ CXPLAT_SEND_DATA* SendData + ) +{ + return FALSE; +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +QUIC_STATUS +CxPlatSocketSend( + _In_ CXPLAT_SOCKET* Socket, + _In_ const CXPLAT_ROUTE* Route, + _In_ CXPLAT_SEND_DATA* SendData + ) +{ + return QUIC_STATUS_NOT_SUPPORTED; +} + +void +CxPlatDataPathProcessCqe( + _In_ CXPLAT_CQE* Cqe + ) +{ +} diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 37eb9cd124..eba94c67b4 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -17,6 +17,8 @@ #pragma warning(disable:4116) // unnamed type definition in parentheses +#define MANGLE(x) SOCKET_##x + // // This IOCTL allows for creating per-processor sockets for the same UDP port. // This is used to get better parallelization to improve performance. @@ -779,7 +781,7 @@ CxPlatStopInlineDatapathIo( } CXPLAT_RECV_DATA* -CxPlatDataPathRecvPacketToRecvData( +MANGLE(CxPlatDataPathRecvPacketToRecvData)( _In_ const CXPLAT_RECV_PACKET* const Context ) { @@ -790,7 +792,7 @@ CxPlatDataPathRecvPacketToRecvData( } CXPLAT_RECV_PACKET* -CxPlatDataPathRecvDataToRecvPacket( +MANGLE(CxPlatDataPathRecvDataToRecvPacket)( _In_ const CXPLAT_RECV_DATA* const Datagram ) { @@ -1157,7 +1159,7 @@ typedef LONG (WINAPI *FuncRtlGetVersion)(RTL_OSVERSIONINFOW *); _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatDataPathInitialize( +MANGLE(CxPlatDataPathInitialize)( _In_ uint32_t ClientRecvContextLength, _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, @@ -1434,7 +1436,7 @@ CxPlatProcessorContextRelease( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatDataPathUninitialize( +MANGLE(CxPlatDataPathUninitialize)( _In_ CXPLAT_DATAPATH* Datapath ) { @@ -1450,7 +1452,7 @@ CxPlatDataPathUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatDataPathUpdateConfig( +MANGLE(CxPlatDataPathUpdateConfig)( _In_ CXPLAT_DATAPATH* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ) @@ -1461,7 +1463,7 @@ CxPlatDataPathUpdateConfig( _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t -CxPlatDataPathGetSupportedFeatures( +MANGLE(CxPlatDataPathGetSupportedFeatures)( _In_ CXPLAT_DATAPATH* Datapath ) { @@ -1470,7 +1472,7 @@ CxPlatDataPathGetSupportedFeatures( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -CxPlatDataPathIsPaddingPreferred( +MANGLE(CxPlatDataPathIsPaddingPreferred)( _In_ CXPLAT_DATAPATH* Datapath ) { @@ -1480,7 +1482,7 @@ CxPlatDataPathIsPaddingPreferred( _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS -CxPlatDataPathGetLocalAddresses( +MANGLE(CxPlatDataPathGetLocalAddresses)( _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) CXPLAT_ADAPTER_ADDRESS** Addresses, @@ -1594,7 +1596,7 @@ CxPlatDataPathGetLocalAddresses( _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS -CxPlatDataPathGetGatewayAddresses( +MANGLE(CxPlatDataPathGetGatewayAddresses)( _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) QUIC_ADDR** GatewayAddresses, @@ -1731,7 +1733,7 @@ CxPlatDataPathPopulateTargetAddress( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatDataPathResolveAddress( +MANGLE(CxPlatDataPathResolveAddress)( _In_ CXPLAT_DATAPATH* Datapath, _In_z_ const char* HostName, _Inout_ QUIC_ADDR* Address @@ -1846,7 +1848,7 @@ CxPlatSocketEnqueueSqe( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatSocketCreateUdp( +MANGLE(CxPlatSocketCreateUdp)( _In_ CXPLAT_DATAPATH* Datapath, _In_ const CXPLAT_UDP_CONFIG* Config, _Out_ CXPLAT_SOCKET** NewSocket @@ -2712,7 +2714,7 @@ CxPlatSocketCreateTcpInternal( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatSocketCreateTcp( +MANGLE(CxPlatSocketCreateTcp)( _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, @@ -2732,7 +2734,7 @@ CxPlatSocketCreateTcp( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatSocketCreateTcpListener( +MANGLE(CxPlatSocketCreateTcpListener)( _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ void* RecvCallbackContext, @@ -2952,7 +2954,7 @@ CxPlatSocketCreateTcpListener( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatSocketDelete( +MANGLE(CxPlatSocketDelete)( _In_ CXPLAT_SOCKET* Socket ) { @@ -3119,7 +3121,7 @@ CxPlatSocketContextUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatSocketUpdateQeo( +MANGLE(CxPlatSocketUpdateQeo)( _In_ CXPLAT_SOCKET* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, @@ -3134,7 +3136,7 @@ CxPlatSocketUpdateQeo( _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 -CxPlatSocketGetLocalMtu( +MANGLE(CxPlatSocketGetLocalMtu)( _In_ CXPLAT_SOCKET* Socket ) { @@ -3144,7 +3146,7 @@ CxPlatSocketGetLocalMtu( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatSocketGetLocalAddress( +MANGLE(CxPlatSocketGetLocalAddress)( _In_ CXPLAT_SOCKET* Socket, _Out_ QUIC_ADDR* Address ) @@ -3155,7 +3157,7 @@ CxPlatSocketGetLocalAddress( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatSocketGetRemoteAddress( +MANGLE(CxPlatSocketGetRemoteAddress)( _In_ CXPLAT_SOCKET* Socket, _Out_ QUIC_ADDR* Address ) @@ -4210,7 +4212,7 @@ CxPlatFreeRecvContext( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatRecvDataReturn( +MANGLE(CxPlatRecvDataReturn)( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ) { @@ -4371,7 +4373,7 @@ RioSendDataFree( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* -CxPlatSendDataAlloc( +MANGLE(CxPlatSendDataAlloc)( _In_ CXPLAT_SOCKET* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) @@ -4424,7 +4426,7 @@ CxPlatSendDataAlloc( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatSendDataFree( +MANGLE(CxPlatSendDataFree)( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -4664,7 +4666,7 @@ CxPlatSendDataAllocSegmentBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* -CxPlatSendDataAllocBuffer( +MANGLE(CxPlatSendDataAllocBuffer)( _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ) @@ -4687,7 +4689,7 @@ CxPlatSendDataAllocBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatSendDataFreeBuffer( +MANGLE(CxPlatSendDataFreeBuffer)( _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ) @@ -4718,7 +4720,7 @@ CxPlatSendDataFreeBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -CxPlatSendDataIsFull( +MANGLE(CxPlatSendDataIsFull)( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -4983,7 +4985,7 @@ CxPlatSocketSendEnqueue( _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS -CxPlatSocketSend( +MANGLE(CxPlatSocketSend)( _In_ CXPLAT_SOCKET* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA* SendData @@ -5061,7 +5063,7 @@ CxPlatDataPathStartRioSends( } void -CxPlatDataPathProcessCqe( +MANGLE(CxPlatDataPathProcessCqe)( _In_ CXPLAT_CQE* Cqe ) { @@ -5138,3 +5140,8 @@ CxPlatDataPathProcessCqe( default: CXPLAT_DBG_ASSERT(FALSE); break; } } + +void InitSocketDataPath(struct CXPLAT_DATAPATH_FUNCTIONS* Funcs) { + Funcs->CxPlatDataPathRecvPacketToRecvData = MANGLE(CxPlatDataPathRecvPacketToRecvData); + +} \ No newline at end of file From 18c054e2be72f0ade060fdb7fe3b7e5754f657be Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Thu, 13 Jul 2023 16:27:40 -0700 Subject: [PATCH 16/87] DatapathTest partially work --- src/inc/quic_datapath.h | 78 ++++++---- src/platform/datapath_win.c | 183 ++++++++++++++++++++-- src/platform/datapath_winuser.c | 268 ++++++++++++++++++-------------- 3 files changed, 366 insertions(+), 163 deletions(-) diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index c9acb0269e..d3203f1cbf 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -124,12 +124,31 @@ PacketSizeFromUdpPayloadSize( // // The top level datapath handle type. // -typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; +// typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; + +typedef enum DATAPATH_TYPE { + DATAPATH_TYPE_NONE, + DATAPATH_TYPE_USER, + DATAPATH_TYPE_XDP + // DATAPATH_TYPE_KERNEL, + // DATAPATH_TYPE_DPDK, +} DATAPATH_TYPE; + +typedef struct CXPLAT_DATAPATH_INTERNAL CXPLAT_DATAPATH_INTERNAL; + +typedef struct CXPLAT_DATAPATH { + CXPLAT_DATAPATH_INTERNAL* User; + CXPLAT_DATAPATH_INTERNAL* Xdp; +} CXPLAT_DATAPATH; // // Represents a UDP or TCP abstraction. // -typedef struct CXPLAT_SOCKET CXPLAT_SOCKET; +typedef struct CXPLAT_SOCKET_INTERNAL CXPLAT_SOCKET_INTERNAL; + +typedef struct CXPLAT_SOCKET { + DATAPATH_TYPE DataPathType; +} CXPLAT_SOCKET; // // Can be defined to whatever the client needs. @@ -139,7 +158,11 @@ typedef struct CXPLAT_RECV_PACKET CXPLAT_RECV_PACKET; // // Structure that maintains the 'per send' context. // -typedef struct CXPLAT_SEND_DATA CXPLAT_SEND_DATA; +typedef struct CXPLAT_SEND_DATA_INTERNAL CXPLAT_SEND_DATA_INTERNAL; + +typedef struct CXPLAT_SEND_DATA { + DATAPATH_TYPE DataPathType; +} CXPLAT_SEND_DATA; // // Contains a pointer and length. @@ -789,62 +812,61 @@ typedef struct CXPLAT_DATAPATH_FUNCTIONS { _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, _In_opt_ QUIC_EXECUTION_CONFIG* Config, - _Out_ CXPLAT_DATAPATH** NewDataPath); - void (*CxPlatDataPathUninitialize)(_In_ CXPLAT_DATAPATH* Datapath); - void (*CxPlatDataPathUpdateConfig)(_In_ CXPLAT_DATAPATH* Datapath, + _Out_ CXPLAT_DATAPATH_INTERNAL** NewDataPath); + void (*CxPlatDataPathUninitialize)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath); + void (*CxPlatDataPathUpdateConfig)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config); - uint32_t (*CxPlatDataPathGetSupportedFeatures)(_In_ CXPLAT_DATAPATH* Datapath); - BOOLEAN (*CxPlatDataPathIsPaddingPreferred)(_In_ CXPLAT_DATAPATH* Datapath); - QUIC_STATUS (*CxPlatDataPathGetLocalAddresses)(_In_ CXPLAT_DATAPATH* Datapath, + uint32_t (*CxPlatDataPathGetSupportedFeatures)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath); + BOOLEAN (*CxPlatDataPathIsPaddingPreferred)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath); + QUIC_STATUS (*CxPlatDataPathGetLocalAddresses)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) CXPLAT_ADAPTER_ADDRESS** Addresses, _Out_ uint32_t* AddressesCount); - QUIC_STATUS (*CxPlatDataPathGetGatewayAddresses)(_In_ CXPLAT_DATAPATH* Datapath, + QUIC_STATUS (*CxPlatDataPathGetGatewayAddresses)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) QUIC_ADDR** GatewayAddresses, _Out_ uint32_t* GatewayAddressesCount); - QUIC_STATUS (*CxPlatDataPathResolveAddress)(_In_ CXPLAT_DATAPATH* Datapath, + QUIC_STATUS (*CxPlatDataPathResolveAddress)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _In_z_ const char* HostName, _Inout_ QUIC_ADDR* Address); - QUIC_STATUS (*CxPlatSocketCreateUdp)(_In_ CXPLAT_DATAPATH* Datapath, + QUIC_STATUS (*CxPlatSocketCreateUdp)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _In_ const CXPLAT_UDP_CONFIG* Config, _Out_ CXPLAT_SOCKET** NewSocket); - QUIC_STATUS (*CxPlatSocketCreateTcp)(_In_ CXPLAT_DATAPATH* Datapath, + QUIC_STATUS (*CxPlatSocketCreateTcp)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, _In_opt_ void* CallbackContext, _Out_ CXPLAT_SOCKET** Socket); - QUIC_STATUS (*CxPlatSocketCreateTcpListener)(_In_ CXPLAT_DATAPATH* Datapath, + QUIC_STATUS (*CxPlatSocketCreateTcpListener)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ void* RecvCallbackContext, _Out_ CXPLAT_SOCKET** NewSocket); - void (*CxPlatSocketDelete)(_In_ CXPLAT_SOCKET* Socket); - QUIC_STATUS (*CxPlatSocketUpdateQeo)(_In_ CXPLAT_SOCKET* Socket, + void (*CxPlatSocketDelete)(_In_ CXPLAT_SOCKET_INTERNAL* Socket); + QUIC_STATUS (*CxPlatSocketUpdateQeo)(_In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, _In_ uint32_t OffloadCount); - UINT16 (*CxPlatSocketGetLocalMtu)(_In_ CXPLAT_SOCKET* Socket); - void (*CxPlatSocketGetLocalAddress)(_In_ CXPLAT_SOCKET* Socket, + UINT16 (*CxPlatSocketGetLocalMtu)(_In_ CXPLAT_SOCKET_INTERNAL* Socket); + void (*CxPlatSocketGetLocalAddress)(_In_ CXPLAT_SOCKET_INTERNAL* Socket, _Out_ QUIC_ADDR* Address); - void (*CxPlatSocketGetRemoteAddress)(_In_ CXPLAT_SOCKET* Socket, + void (*CxPlatSocketGetRemoteAddress)(_In_ CXPLAT_SOCKET_INTERNAL* Socket, _Out_ QUIC_ADDR* Address); void (*CxPlatRecvDataReturn)(_In_opt_ CXPLAT_RECV_DATA* RecvDataChain); - CXPLAT_SEND_DATA* (*CxPlatSendDataAlloc)(_In_ CXPLAT_SOCKET* Socket, + CXPLAT_SEND_DATA* (*CxPlatSendDataAlloc)(_In_ CXPLAT_SOCKET_INTERNAL* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config); - void (*CxPlatSendDataFree)(_In_ CXPLAT_SEND_DATA* SendData); - QUIC_BUFFER* (*CxPlatSendDataAllocBuffer)(_In_ CXPLAT_SEND_DATA* SendData, + void (*CxPlatSendDataFree)(_In_ CXPLAT_SEND_DATA_INTERNAL* SendData); + QUIC_BUFFER* (*CxPlatSendDataAllocBuffer)(_In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ uint16_t MaxBufferLength); - void (*CxPlatSendDataFreeBuffer)(_In_ CXPLAT_SEND_DATA* SendData, + void (*CxPlatSendDataFreeBuffer)(_In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ QUIC_BUFFER* Buffer); - BOOLEAN (*CxPlatSendDataIsFull)(_In_ CXPLAT_SEND_DATA* SendData); - QUIC_STATUS (*CxPlatSocketSend)(_In_ CXPLAT_SOCKET* Socket, + BOOLEAN (*CxPlatSendDataIsFull)(_In_ CXPLAT_SEND_DATA_INTERNAL* SendData); + QUIC_STATUS (*CxPlatSocketSend)(_In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ const CXPLAT_ROUTE* Route, - _In_ CXPLAT_SEND_DATA* SendData); + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData); void (*CxPlatDataPathProcessCqe)(_In_ CXPLAT_CQE* Cqe); } CXPLAT_DATAPATH_FUNCTIONS; -// TODO: move to each plat? -void InitSocketDataPath(struct CXPLAT_DATAPATH_FUNCTIONS* Funcs); +extern const struct CXPLAT_DATAPATH_FUNCTIONS DataPathUserFuncs; #if defined(__cplusplus) } diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index d2e6579e61..8608aa01d7 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -25,7 +25,10 @@ CxPlatDataPathRecvPacketToRecvData( _In_ const CXPLAT_RECV_PACKET* const Context ) { - return NULL; + return DataPathUserFuncs.CxPlatDataPathRecvPacketToRecvData(Context); + // TODO: xdp + // Or inline the function + // use global variable to store the offset? set at init phase } CXPLAT_RECV_PACKET* @@ -33,7 +36,10 @@ CxPlatDataPathRecvDataToRecvPacket( _In_ const CXPLAT_RECV_DATA* const Datagram ) { - return NULL; + return DataPathUserFuncs.CxPlatDataPathRecvDataToRecvPacket(Datagram); + // TODO: xdp + // Or inline the function + // use global variable to store the offset? set at init phase } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -46,11 +52,30 @@ CxPlatDataPathInitialize( _Out_ CXPLAT_DATAPATH** NewDataPath ) { - fprintf(stderr, "CxPlatDataPathInitialize\n"); - struct CXPLAT_DATAPATH_FUNCTIONS Funcs; // TODO: global - InitSocketDataPath(&Funcs); - - return QUIC_STATUS_SUCCESS; + // Init all Datapath + // + QUIC_STATUS Status = QUIC_STATUS_SUCCESS; + + CXPLAT_DATAPATH* DataPath = (CXPLAT_DATAPATH*)CXPLAT_ALLOC_PAGED(sizeof(CXPLAT_DATAPATH), QUIC_POOL_DATAPATH); + if (DataPath == NULL) { + Status = QUIC_STATUS_OUT_OF_MEMORY; + goto Error; + } + CxPlatZeroMemory(DataPath, sizeof(CXPLAT_DATAPATH)); + Status = DataPathUserFuncs.CxPlatDataPathInitialize( + ClientRecvContextLength, + UdpCallbacks, + TcpCallbacks, + Config, + &DataPath->User); + + // TODO: xdp + + *NewDataPath = DataPath; + +Error: + + return Status; } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -59,6 +84,11 @@ CxPlatDataPathUninitialize( _In_ CXPLAT_DATAPATH* Datapath ) { + DataPathUserFuncs.CxPlatDataPathUninitialize(Datapath->User); + if (Datapath->Xdp) { + // DataPathXdpFuncs.CxPlatDataPathUninitialize(Datapath->User); + } + CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -68,8 +98,10 @@ CxPlatDataPathUpdateConfig( _In_ QUIC_EXECUTION_CONFIG* Config ) { - UNREFERENCED_PARAMETER(Datapath); - UNREFERENCED_PARAMETER(Config); + DataPathUserFuncs.CxPlatDataPathUpdateConfig(Datapath->User, Config); + if (Datapath->Xdp) { + // DataPathXdpFuncs.CxPlatDataPathUpdateConfig(Datapath->Xdp, Config); + } } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -78,7 +110,9 @@ CxPlatDataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH* Datapath ) { - return 0; + // Which feature should be taken? + return DataPathUserFuncs.CxPlatDataPathGetSupportedFeatures(Datapath->User); + // TODO: xdp } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -87,7 +121,10 @@ CxPlatDataPathIsPaddingPreferred( _In_ CXPLAT_DATAPATH* Datapath ) { - return FALSE; + // Which flag should be taken? + return DataPathUserFuncs.CxPlatDataPathIsPaddingPreferred(Datapath->User); + // TODO: xdp + } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -100,7 +137,12 @@ CxPlatDataPathGetLocalAddresses( _Out_ uint32_t* AddressesCount ) { - return QUIC_STATUS_NOT_SUPPORTED; + // which datapath should be used? + return DataPathUserFuncs.CxPlatDataPathGetLocalAddresses( + Datapath->User, + Addresses, + AddressesCount); + // TODO: xdp } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -113,7 +155,12 @@ CxPlatDataPathGetGatewayAddresses( _Out_ uint32_t* GatewayAddressesCount ) { - return QUIC_STATUS_NOT_SUPPORTED; + // which datapath should be used? + return DataPathUserFuncs.CxPlatDataPathGetGatewayAddresses( + Datapath->User, + GatewayAddresses, + GatewayAddressesCount); + // TODO: xdp } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -124,7 +171,12 @@ CxPlatDataPathResolveAddress( _Inout_ QUIC_ADDR* Address ) { - return QUIC_STATUS_NOT_SUPPORTED; + // TODO: both datapath adopt same procedure + // can be flatten here and may no need for calling into internal datapath + return DataPathUserFuncs.CxPlatDataPathResolveAddress( + Datapath->User, + HostName, + Address); } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -135,8 +187,24 @@ CxPlatSocketCreateUdp( _Out_ CXPLAT_SOCKET** NewSocket ) { - fprintf(stderr, "CxPlatSocketCreateUdp\n"); - return QUIC_STATUS_SUCCESS; + QUIC_STATUS Status = QUIC_STATUS_SUCCESS; + + // - if client, use one out of 2 datapath + // - if Config->RemoteAddress is loopback, use user + // - if not, use xdp + // - if server, init socket, then share info to xdp? + + if (FALSE /*(server && xdp) || (client && remote is not loopback)*/) { + // use XDP + } else { + Status = DataPathUserFuncs.CxPlatSocketCreateUdp( + Datapath->User, + Config, + NewSocket); + (*NewSocket)->DataPathType = DATAPATH_TYPE_USER; + } + + return Status; } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -170,6 +238,13 @@ CxPlatSocketDelete( _In_ CXPLAT_SOCKET* Socket ) { + if (Socket->DataPathType == DATAPATH_TYPE_USER) { + DataPathUserFuncs.CxPlatSocketDelete((CXPLAT_SOCKET_INTERNAL*)Socket); + } else if (Socket->DataPathType == DATAPATH_TYPE_XDP) { + // use XDP + } else { + CXPLAT_DBG_ASSERT(FALSE); + } } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -193,6 +268,13 @@ CxPlatSocketGetLocalMtu( _In_ CXPLAT_SOCKET* Socket ) { + if (Socket->DataPathType == DATAPATH_TYPE_USER) { + return DataPathUserFuncs.CxPlatSocketGetLocalMtu((CXPLAT_SOCKET_INTERNAL*)Socket); + } else if (Socket->DataPathType == DATAPATH_TYPE_XDP) { + // use XDP + } else { + CXPLAT_DBG_ASSERT(FALSE); + } return 0; } @@ -203,6 +285,10 @@ CxPlatSocketGetLocalAddress( _Out_ QUIC_ADDR* Address ) { + // TODO: inline + DataPathUserFuncs.CxPlatSocketGetLocalAddress( + (CXPLAT_SOCKET_INTERNAL*)Socket, + Address); } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -212,6 +298,10 @@ CxPlatSocketGetRemoteAddress( _Out_ QUIC_ADDR* Address ) { + // TODO: inline + DataPathUserFuncs.CxPlatSocketGetRemoteAddress( + (CXPLAT_SOCKET_INTERNAL*)Socket, + Address); } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -220,6 +310,7 @@ CxPlatRecvDataReturn( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ) { + // TODO: CXPLAT_RECV_DATA to have flag to indicate which datapath it belongs to } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -230,7 +321,19 @@ CxPlatSendDataAlloc( _Inout_ CXPLAT_SEND_CONFIG* Config ) { - return NULL; + CXPLAT_SEND_DATA* SendData = NULL; + if (Socket->DataPathType == DATAPATH_TYPE_USER) { + SendData = DataPathUserFuncs.CxPlatSendDataAlloc( + (CXPLAT_SOCKET_INTERNAL*)Socket, + Config); + SendData->DataPathType = DATAPATH_TYPE_USER; + } else if (Socket->DataPathType == DATAPATH_TYPE_XDP) { + // use XDP + // SendData->DataPathType = DATAPATH_TYPE_USER; + } else { + CXPLAT_DBG_ASSERT(FALSE); + } + return SendData; } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -239,6 +342,14 @@ CxPlatSendDataFree( _In_ CXPLAT_SEND_DATA* SendData ) { + if (SendData->DataPathType == DATAPATH_TYPE_USER) { + DataPathUserFuncs.CxPlatSendDataFree( + (CXPLAT_SEND_DATA_INTERNAL*)SendData); + } else if (SendData->DataPathType == DATAPATH_TYPE_XDP) { + // use XDP + } else { + CXPLAT_DBG_ASSERT(FALSE); + } } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -249,6 +360,15 @@ CxPlatSendDataAllocBuffer( _In_ uint16_t MaxBufferLength ) { + if (SendData->DataPathType == DATAPATH_TYPE_USER) { + return DataPathUserFuncs.CxPlatSendDataAllocBuffer( + (CXPLAT_SEND_DATA_INTERNAL*)SendData, + MaxBufferLength); + } else if (SendData->DataPathType == DATAPATH_TYPE_XDP) { + // use XDP + } else { + CXPLAT_DBG_ASSERT(FALSE); + } return NULL; } @@ -259,6 +379,15 @@ CxPlatSendDataFreeBuffer( _In_ QUIC_BUFFER* Buffer ) { + if (SendData->DataPathType == DATAPATH_TYPE_USER) { + DataPathUserFuncs.CxPlatSendDataFreeBuffer( + (CXPLAT_SEND_DATA_INTERNAL*)SendData, + Buffer); + } else if (SendData->DataPathType == DATAPATH_TYPE_XDP) { + // use XDP + } else { + CXPLAT_DBG_ASSERT(FALSE); + } } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -267,6 +396,14 @@ CxPlatSendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ) { + if (SendData->DataPathType == DATAPATH_TYPE_USER) { + return DataPathUserFuncs.CxPlatSendDataIsFull( + (CXPLAT_SEND_DATA_INTERNAL*)SendData); + } else if (SendData->DataPathType == DATAPATH_TYPE_XDP) { + // use XDP + } else { + CXPLAT_DBG_ASSERT(FALSE); + } return FALSE; } @@ -278,6 +415,16 @@ CxPlatSocketSend( _In_ CXPLAT_SEND_DATA* SendData ) { + if (Socket->DataPathType == DATAPATH_TYPE_USER) { + return DataPathUserFuncs.CxPlatSocketSend( + (CXPLAT_SOCKET_INTERNAL*)Socket, + Route, + (CXPLAT_SEND_DATA_INTERNAL*)SendData); + } else if (Socket->DataPathType == DATAPATH_TYPE_XDP) { + // use XDP + } else { + CXPLAT_DBG_ASSERT(FALSE); + } return QUIC_STATUS_NOT_SUPPORTED; } @@ -286,4 +433,6 @@ CxPlatDataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ) { + // what is the difference between datapath? + DataPathUserFuncs.CxPlatDataPathProcessCqe(Cqe); } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index eba94c67b4..d723f08597 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -222,18 +222,19 @@ typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) CXPLAT_RIO_SEND_BUFFE // // This send buffer's datapath. // - CXPLAT_DATAPATH* Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath; // // This send buffer's send data. // - CXPLAT_SEND_DATA* SendData; + CXPLAT_SEND_DATA_INTERNAL* SendData; } CXPLAT_RIO_SEND_BUFFER_HEADER; // // Send context. // -typedef struct CXPLAT_SEND_DATA { +typedef struct CXPLAT_SEND_DATA_INTERNAL { + CXPLAT_SEND_DATA; // // The per-processor socket for this send data. // @@ -323,7 +324,7 @@ typedef struct CXPLAT_SEND_DATA { // The V6-mapped remote address to send to. // QUIC_ADDR MappedRemoteAddress; -} CXPLAT_SEND_DATA; +} CXPLAT_SEND_DATA_INTERNAL; // // Per-processor socket state. @@ -352,7 +353,7 @@ typedef struct QUIC_CACHEALIGN CXPLAT_SOCKET_PROC { // // Parent CXPLAT_SOCKET. // - CXPLAT_SOCKET* Parent; + CXPLAT_SOCKET_INTERNAL* Parent; // // Socket handle to the networking stack. @@ -401,7 +402,7 @@ typedef struct QUIC_CACHEALIGN CXPLAT_SOCKET_PROC { // TCP Listener socket data // struct { - CXPLAT_SOCKET* AcceptSocket; + CXPLAT_SOCKET_INTERNAL* AcceptSocket; char AcceptAddrSpace[ sizeof(SOCKADDR_INET) + 16 + sizeof(SOCKADDR_INET) + 16 @@ -413,12 +414,13 @@ typedef struct QUIC_CACHEALIGN CXPLAT_SOCKET_PROC { // // Per-port state. Multiple sockets are created on each port. // -typedef struct CXPLAT_SOCKET { +typedef struct CXPLAT_SOCKET_INTERNAL { + CXPLAT_SOCKET; // // Parent datapath. // - CXPLAT_DATAPATH* Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath; // // Client context pointer. @@ -486,7 +488,7 @@ typedef struct CXPLAT_SOCKET { // CXPLAT_SOCKET_PROC Processors[0]; -} CXPLAT_SOCKET; +} CXPLAT_SOCKET_INTERNAL; // // Represents a single IO completion port and thread for processing work that is @@ -497,7 +499,7 @@ typedef struct QUIC_CACHEALIGN CXPLAT_DATAPATH_PROC { // // Parent datapath. // - CXPLAT_DATAPATH* Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath; // // Event queue used for processing work. @@ -568,7 +570,7 @@ typedef struct QUIC_CACHEALIGN CXPLAT_DATAPATH_PROC { // // Main structure for tracking all UDP abstractions. // -typedef struct CXPLAT_DATAPATH { +typedef struct CXPLAT_DATAPATH_INTERNAL { // // The UDP callback function pointers. @@ -653,7 +655,13 @@ typedef struct CXPLAT_DATAPATH { // CXPLAT_DATAPATH_PROC Processors[0]; -} CXPLAT_DATAPATH; +} CXPLAT_DATAPATH_INTERNAL; + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +MANGLE(CxPlatSocketDelete)( + _In_ CXPLAT_SOCKET_INTERNAL* Socket + ); #ifdef DEBUG #ifndef AllocOffset @@ -813,7 +821,7 @@ CxPlatDataPathDatagramToInternalDatagramContext( CXPLAT_DATAPATH_PROC* CxPlatDataPathGetProc( - _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _In_ uint16_t Processor ) { @@ -898,7 +906,7 @@ CxPlatDataPathStartRioSends( void CxPlatSendDataComplete( - _In_ CXPLAT_SEND_DATA* SendData, + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ ULONG IoResult ); @@ -917,7 +925,7 @@ CxPlatFreeRecvContext( void CxPlatDataPathQueryRssScalabilityInfo( - _Inout_ CXPLAT_DATAPATH* Datapath + _Inout_ CXPLAT_DATAPATH_INTERNAL* Datapath ) { int Result; @@ -967,7 +975,7 @@ CxPlatDataPathQueryRssScalabilityInfo( QUIC_STATUS CxPlatDataPathQuerySockoptSupport( - _Inout_ CXPLAT_DATAPATH* Datapath + _Inout_ CXPLAT_DATAPATH_INTERNAL* Datapath ) { int Result; @@ -1164,7 +1172,7 @@ MANGLE(CxPlatDataPathInitialize)( _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, _In_opt_ QUIC_EXECUTION_CONFIG* Config, - _Out_ CXPLAT_DATAPATH** NewDataPath + _Out_ CXPLAT_DATAPATH_INTERNAL** NewDataPath ) { int WsaError; @@ -1173,7 +1181,7 @@ MANGLE(CxPlatDataPathInitialize)( const uint16_t* ProcessorList; uint32_t ProcessorCount; uint32_t DatapathLength; - CXPLAT_DATAPATH* Datapath = NULL; + CXPLAT_DATAPATH_INTERNAL* Datapath = NULL; BOOLEAN WsaInitialized = FALSE; if (NewDataPath == NULL) { @@ -1221,10 +1229,10 @@ MANGLE(CxPlatDataPathInitialize)( } DatapathLength = - sizeof(CXPLAT_DATAPATH) + + sizeof(CXPLAT_DATAPATH_INTERNAL) + ProcessorCount * sizeof(CXPLAT_DATAPATH_PROC); - Datapath = (CXPLAT_DATAPATH*)CXPLAT_ALLOC_PAGED(DatapathLength, QUIC_POOL_DATAPATH); + Datapath = (CXPLAT_DATAPATH_INTERNAL*)CXPLAT_ALLOC_PAGED(DatapathLength, QUIC_POOL_DATAPATH); if (Datapath == NULL) { QuicTraceEvent( AllocFailure, @@ -1318,13 +1326,13 @@ MANGLE(CxPlatDataPathInitialize)( CxPlatPoolInitialize( FALSE, - sizeof(CXPLAT_SEND_DATA), + sizeof(CXPLAT_SEND_DATA_INTERNAL), QUIC_POOL_PLATFORM_SENDCTX, &Datapath->Processors[i].SendDataPool); CxPlatPoolInitializeEx( FALSE, - sizeof(CXPLAT_SEND_DATA), + sizeof(CXPLAT_SEND_DATA_INTERNAL), QUIC_POOL_PLATFORM_SENDCTX, 0, RioSendDataAllocate, @@ -1400,7 +1408,7 @@ MANGLE(CxPlatDataPathInitialize)( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDataPathRelease( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH_INTERNAL* Datapath ) { if (CxPlatRefDecrement(&Datapath->RefCount)) { @@ -1437,7 +1445,7 @@ CxPlatProcessorContextRelease( _IRQL_requires_max_(PASSIVE_LEVEL) void MANGLE(CxPlatDataPathUninitialize)( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH_INTERNAL* Datapath ) { if (Datapath != NULL) { @@ -1453,7 +1461,7 @@ MANGLE(CxPlatDataPathUninitialize)( _IRQL_requires_max_(PASSIVE_LEVEL) void MANGLE(CxPlatDataPathUpdateConfig)( - _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ) { @@ -1464,7 +1472,7 @@ MANGLE(CxPlatDataPathUpdateConfig)( _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t MANGLE(CxPlatDataPathGetSupportedFeatures)( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH_INTERNAL* Datapath ) { return Datapath->Features; @@ -1473,7 +1481,7 @@ MANGLE(CxPlatDataPathGetSupportedFeatures)( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN MANGLE(CxPlatDataPathIsPaddingPreferred)( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH_INTERNAL* Datapath ) { return !!(Datapath->Features & CXPLAT_DATAPATH_FEATURE_SEND_SEGMENTATION); @@ -1483,7 +1491,7 @@ _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS MANGLE(CxPlatDataPathGetLocalAddresses)( - _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) CXPLAT_ADAPTER_ADDRESS** Addresses, _Out_ uint32_t* AddressesCount @@ -1597,7 +1605,7 @@ _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS MANGLE(CxPlatDataPathGetGatewayAddresses)( - _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) QUIC_ADDR** GatewayAddresses, _Out_ uint32_t* GatewayAddressesCount @@ -1734,7 +1742,7 @@ CxPlatDataPathPopulateTargetAddress( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatDataPathResolveAddress)( - _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _In_z_ const char* HostName, _Inout_ QUIC_ADDR* Address ) @@ -1849,7 +1857,7 @@ CxPlatSocketEnqueueSqe( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketCreateUdp)( - _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _In_ const CXPLAT_UDP_CONFIG* Config, _Out_ CXPLAT_SOCKET** NewSocket ) @@ -1864,8 +1872,8 @@ MANGLE(CxPlatSocketCreateUdp)( CXPLAT_DBG_ASSERT(Datapath->UdpHandlers.Receive != NULL || Config->Flags & CXPLAT_SOCKET_FLAG_PCP); uint32_t SocketLength = - sizeof(CXPLAT_SOCKET) + SocketCount * sizeof(CXPLAT_SOCKET_PROC); - CXPLAT_SOCKET* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); + sizeof(CXPLAT_SOCKET_INTERNAL) + SocketCount * sizeof(CXPLAT_SOCKET_PROC); + CXPLAT_SOCKET_INTERNAL* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); if (Socket == NULL) { QuicTraceEvent( AllocFailure, @@ -2437,7 +2445,7 @@ MANGLE(CxPlatSocketCreateUdp)( // Must set output pointer before starting receive path, as the receive path // will try to use the output. // - *NewSocket = Socket; + *NewSocket = (CXPLAT_SOCKET*)Socket; for (uint16_t i = 0; i < SocketCount; i++) { CxPlatDataPathStartReceiveAsync(&Socket->Processors[i]); @@ -2450,7 +2458,7 @@ MANGLE(CxPlatSocketCreateUdp)( Error: if (Socket != NULL) { - CxPlatSocketDelete(Socket); + MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET_INTERNAL*)Socket); } return Status; @@ -2459,7 +2467,7 @@ MANGLE(CxPlatSocketCreateUdp)( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatSocketCreateTcpInternal( - _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _In_ CXPLAT_SOCKET_TYPE Type, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ const QUIC_ADDR* RemoteAddress, @@ -2476,8 +2484,8 @@ CxPlatSocketCreateTcpInternal( CXPLAT_DBG_ASSERT(Datapath->TcpHandlers.Receive != NULL); CXPLAT_SOCKET_PROC* SocketProc = NULL; - uint32_t SocketLength = sizeof(CXPLAT_SOCKET) + sizeof(CXPLAT_SOCKET_PROC); - CXPLAT_SOCKET* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); + uint32_t SocketLength = sizeof(CXPLAT_SOCKET_INTERNAL) + sizeof(CXPLAT_SOCKET_PROC); + CXPLAT_SOCKET_INTERNAL* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); if (Socket == NULL) { QuicTraceEvent( AllocFailure, @@ -2698,7 +2706,7 @@ CxPlatSocketCreateTcpInternal( Socket->RemoteAddress.Ipv4.sin_port = 0; } - *NewSocket = Socket; + *NewSocket = (CXPLAT_SOCKET*)Socket; Socket = NULL; Status = QUIC_STATUS_SUCCESS; @@ -2706,7 +2714,7 @@ CxPlatSocketCreateTcpInternal( Error: if (Socket != NULL) { - CxPlatSocketDelete(Socket); + MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET_INTERNAL*)Socket); } return Status; @@ -2715,7 +2723,7 @@ CxPlatSocketCreateTcpInternal( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketCreateTcp)( - _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, _In_opt_ void* CallbackContext, @@ -2735,7 +2743,7 @@ MANGLE(CxPlatSocketCreateTcp)( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketCreateTcpListener)( - _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ void* RecvCallbackContext, _Out_ CXPLAT_SOCKET** NewSocket @@ -2748,8 +2756,8 @@ MANGLE(CxPlatSocketCreateTcpListener)( CXPLAT_DBG_ASSERT(Datapath->TcpHandlers.Receive != NULL); CXPLAT_SOCKET_PROC* SocketProc = NULL; - uint32_t SocketLength = sizeof(CXPLAT_SOCKET) + sizeof(CXPLAT_SOCKET_PROC); - CXPLAT_SOCKET* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); + uint32_t SocketLength = sizeof(CXPLAT_SOCKET_INTERNAL) + sizeof(CXPLAT_SOCKET_PROC); + CXPLAT_SOCKET_INTERNAL* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); if (Socket == NULL) { QuicTraceEvent( AllocFailure, @@ -2939,14 +2947,14 @@ MANGLE(CxPlatSocketCreateTcpListener)( SocketProc->IoStarted = TRUE; - *NewSocket = Socket; + *NewSocket = (CXPLAT_SOCKET*)Socket; Socket = NULL; Status = QUIC_STATUS_SUCCESS; Error: if (Socket != NULL) { - CxPlatSocketDelete(Socket); + MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET_INTERNAL*)Socket); } return Status; @@ -2955,7 +2963,7 @@ MANGLE(CxPlatSocketCreateTcpListener)( _IRQL_requires_max_(PASSIVE_LEVEL) void MANGLE(CxPlatSocketDelete)( - _In_ CXPLAT_SOCKET* Socket + _In_ CXPLAT_SOCKET_INTERNAL* Socket ) { CXPLAT_DBG_ASSERT(Socket != NULL); @@ -2979,7 +2987,7 @@ MANGLE(CxPlatSocketDelete)( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatSocketRelease( - _In_ CXPLAT_SOCKET* Socket + _In_ CXPLAT_SOCKET_INTERNAL* Socket ) { if (CxPlatRefDecrement(&Socket->RefCount)) { @@ -3010,7 +3018,7 @@ CxPlatSocketContextRelease( while (!CxPlatListIsEmpty(&SocketProc->RioSendOverflow)) { CXPLAT_LIST_ENTRY* Entry = CxPlatListRemoveHead(&SocketProc->RioSendOverflow); CxPlatSendDataComplete( - CONTAINING_RECORD(Entry, CXPLAT_SEND_DATA, RioOverflowEntry), + CONTAINING_RECORD(Entry, CXPLAT_SEND_DATA_INTERNAL, RioOverflowEntry), WSA_OPERATION_ABORTED); } @@ -3021,7 +3029,7 @@ CxPlatSocketContextRelease( } } else { if (SocketProc->AcceptSocket != NULL) { - CxPlatSocketDelete(SocketProc->AcceptSocket); + MANGLE(CxPlatSocketDelete)(SocketProc->AcceptSocket); SocketProc->AcceptSocket = NULL; } } @@ -3122,7 +3130,7 @@ CxPlatSocketContextUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketUpdateQeo)( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, _In_ uint32_t OffloadCount @@ -3137,7 +3145,7 @@ MANGLE(CxPlatSocketUpdateQeo)( _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 MANGLE(CxPlatSocketGetLocalMtu)( - _In_ CXPLAT_SOCKET* Socket + _In_ CXPLAT_SOCKET_INTERNAL* Socket ) { CXPLAT_DBG_ASSERT(Socket != NULL); @@ -3147,7 +3155,7 @@ MANGLE(CxPlatSocketGetLocalMtu)( _IRQL_requires_max_(DISPATCH_LEVEL) void MANGLE(CxPlatSocketGetLocalAddress)( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _Out_ QUIC_ADDR* Address ) { @@ -3158,7 +3166,7 @@ MANGLE(CxPlatSocketGetLocalAddress)( _IRQL_requires_max_(DISPATCH_LEVEL) void MANGLE(CxPlatSocketGetRemoteAddress)( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _Out_ QUIC_ADDR* Address ) { @@ -3175,7 +3183,7 @@ RioRecvBufferAllocate( { CXPLAT_DATAPATH_PROC* DatapathProc = CXPLAT_CONTAINING_RECORD(Pool, CXPLAT_DATAPATH_PROC, RioRecvPool); - CXPLAT_DATAPATH* Datapath = DatapathProc->Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath = DatapathProc->Datapath; CXPLAT_DATAPATH_INTERNAL_RECV_CONTEXT* RecvContext = CxPlatLargeAlloc(Size, Tag); @@ -3202,7 +3210,7 @@ RioRecvBufferFree( CXPLAT_DATAPATH_INTERNAL_RECV_CONTEXT* RecvContext = Entry; CXPLAT_DATAPATH_PROC* DatapathProc = CXPLAT_CONTAINING_RECORD(Pool, CXPLAT_DATAPATH_PROC, RioRecvPool); - CXPLAT_DATAPATH* Datapath = DatapathProc->Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath = DatapathProc->Datapath; CXPLAT_DBG_ASSERT(RecvContext->RioBufferId != RIO_INVALID_BUFFERID); Datapath->RioDispatch.RIODeregisterBuffer(RecvContext->RioBufferId); @@ -3252,7 +3260,7 @@ CxPlatSocketStartAccept( ) { QUIC_STATUS Status; - CXPLAT_DATAPATH* Datapath = ListenerSocketProc->Parent->Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath = ListenerSocketProc->Parent->Datapath; DWORD BytesRecv = 0; int Result; @@ -3267,7 +3275,7 @@ CxPlatSocketStartAccept( NULL, NULL, NULL, - &ListenerSocketProc->AcceptSocket); + (CXPLAT_SOCKET**)&ListenerSocketProc->AcceptSocket); if (QUIC_FAILED(Status)) { goto Error; } @@ -3408,9 +3416,9 @@ CxPlatDataPathSocketProcessAcceptCompletion( AcceptSocketProc->IoStarted = TRUE; ListenerSocketProc->Parent->Datapath->TcpHandlers.Accept( - ListenerSocketProc->Parent, + (CXPLAT_SOCKET*)ListenerSocketProc->Parent, ListenerSocketProc->Parent->ClientContext, - ListenerSocketProc->AcceptSocket, + (CXPLAT_SOCKET*)ListenerSocketProc->AcceptSocket, &ListenerSocketProc->AcceptSocket->ClientContext); ListenerSocketProc->AcceptSocket = NULL; @@ -3426,7 +3434,7 @@ CxPlatDataPathSocketProcessAcceptCompletion( Error: if (ListenerSocketProc->AcceptSocket != NULL) { - CxPlatSocketDelete(ListenerSocketProc->AcceptSocket); + MANGLE(CxPlatSocketDelete)(ListenerSocketProc->AcceptSocket); ListenerSocketProc->AcceptSocket = NULL; } @@ -3469,7 +3477,7 @@ CxPlatDataPathSocketProcessConnectCompletion( "ConnectEx Completed!"); SocketProc->Parent->Datapath->TcpHandlers.Connect( - SocketProc->Parent, + (CXPLAT_SOCKET*)SocketProc->Parent, SocketProc->Parent->ClientContext, TRUE); @@ -3487,7 +3495,7 @@ CxPlatDataPathSocketProcessConnectCompletion( "ConnectEx completion"); SocketProc->Parent->Datapath->TcpHandlers.Connect( - SocketProc->Parent, + (CXPLAT_SOCKET*)SocketProc->Parent, SocketProc->Parent->ClientContext, FALSE); } @@ -3502,7 +3510,7 @@ CxPlatSocketStartRioReceives( { QUIC_STATUS Status; BOOLEAN NeedCommit = FALSE; - CXPLAT_DATAPATH* Datapath = SocketProc->Parent->Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->Parent->Datapath; while (SocketProc->RioRecvCount < RIO_RECV_QUEUE_DEPTH) { RIO_BUF Data = {0}; @@ -3603,7 +3611,7 @@ CxPlatSocketStartWinsockReceive( _Out_opt_ CXPLAT_DATAPATH_INTERNAL_RECV_CONTEXT** SyncRecvContext ) { - const CXPLAT_DATAPATH* Datapath = SocketProc->Parent->Datapath; + const CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->Parent->Datapath; CXPLAT_DBG_ASSERT((SyncIoResult != NULL) == (SyncBytesReceived != NULL)); CXPLAT_DBG_ASSERT((SyncIoResult != NULL) == (SyncRecvContext != NULL)); @@ -3780,7 +3788,7 @@ CxPlatDataPathUdpRecvComplete( CASTED_CLOG_BYTEARRAY(sizeof(*RemoteAddr), RemoteAddr)); #endif SocketProc->Parent->Datapath->UdpHandlers.Unreachable( - SocketProc->Parent, + (CXPLAT_SOCKET*)SocketProc->Parent, SocketProc->Parent->ClientContext, RemoteAddr); } @@ -3812,7 +3820,7 @@ CxPlatDataPathUdpRecvComplete( CXPLAT_RECV_DATA* RecvDataChain = NULL; CXPLAT_RECV_DATA** DatagramChainTail = &RecvDataChain; - CXPLAT_DATAPATH* Datapath = SocketProc->Parent->Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->Parent->Datapath; CXPLAT_RECV_DATA* Datagram; PUCHAR RecvPayload = ((PUCHAR)RecvContext) + Datapath->RecvPayloadOffset; @@ -3943,12 +3951,12 @@ CxPlatDataPathUdpRecvComplete( if (!SocketProc->Parent->PcpBinding) { SocketProc->Parent->Datapath->UdpHandlers.Receive( - SocketProc->Parent, + (CXPLAT_SOCKET*)SocketProc->Parent, SocketProc->Parent->ClientContext, RecvDataChain); } else { CxPlatPcpRecvCallback( - SocketProc->Parent, + (CXPLAT_SOCKET*)SocketProc->Parent, SocketProc->Parent->ClientContext, RecvDataChain); } @@ -4026,7 +4034,7 @@ CxPlatDataPathSocketProcessRioCompletion( { UNREFERENCED_PARAMETER(Cqe); CXPLAT_SOCKET_PROC* SocketProc = CONTAINING_RECORD(Sqe, CXPLAT_SOCKET_PROC, RioSqe); - CXPLAT_DATAPATH* Datapath = SocketProc->DatapathProc->Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->DatapathProc->Datapath; ULONG ResultCount; BOOLEAN UpcallAcquired; ULONG TotalResultCount = 0; @@ -4127,7 +4135,7 @@ CxPlatDataPathTcpRecvComplete( if (!SocketProc->Parent->DisconnectIndicated) { SocketProc->Parent->DisconnectIndicated = TRUE; SocketProc->Parent->Datapath->TcpHandlers.Connect( - SocketProc->Parent, + (CXPLAT_SOCKET*)SocketProc->Parent, SocketProc->Parent->ClientContext, FALSE); } @@ -4141,7 +4149,7 @@ CxPlatDataPathTcpRecvComplete( if (!SocketProc->Parent->DisconnectIndicated) { SocketProc->Parent->DisconnectIndicated = TRUE; SocketProc->Parent->Datapath->TcpHandlers.Connect( - SocketProc->Parent, + (CXPLAT_SOCKET*)SocketProc->Parent, SocketProc->Parent->ClientContext, FALSE); } @@ -4160,7 +4168,7 @@ CxPlatDataPathTcpRecvComplete( CXPLAT_DBG_ASSERT(NumberOfBytesTransferred <= SocketProc->Parent->RecvBufLen); - CXPLAT_DATAPATH* Datapath = SocketProc->Parent->Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->Parent->Datapath; CXPLAT_RECV_DATA* Data = (CXPLAT_RECV_DATA*)(RecvContext + 1); CXPLAT_DATAPATH_INTERNAL_RECV_BUFFER_CONTEXT* InternalDatagramContext = @@ -4179,7 +4187,7 @@ CxPlatDataPathTcpRecvComplete( RecvContext = NULL; SocketProc->Parent->Datapath->TcpHandlers.Receive( - SocketProc->Parent, + (CXPLAT_SOCKET*)SocketProc->Parent, SocketProc->Parent->ClientContext, Data); @@ -4336,8 +4344,8 @@ RioSendDataAllocate( { CXPLAT_DATAPATH_PROC* DatapathProc = CXPLAT_CONTAINING_RECORD(Pool, CXPLAT_DATAPATH_PROC, RioSendDataPool); - CXPLAT_DATAPATH* Datapath = DatapathProc->Datapath; - CXPLAT_SEND_DATA* SendData; + CXPLAT_DATAPATH_INTERNAL* Datapath = DatapathProc->Datapath; + CXPLAT_SEND_DATA_INTERNAL* SendData; SendData = CxPlatLargeAlloc(Size, Tag); @@ -4360,8 +4368,8 @@ RioSendDataFree( _Inout_ CXPLAT_POOL* Pool ) { - CXPLAT_SEND_DATA* SendData = Entry; - CXPLAT_DATAPATH* Datapath = SendData->Owner->Datapath; + CXPLAT_SEND_DATA_INTERNAL* SendData = Entry; + CXPLAT_DATAPATH_INTERNAL* Datapath = SendData->Owner->Datapath; UNREFERENCED_PARAMETER(Pool); @@ -4374,7 +4382,7 @@ _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* MANGLE(CxPlatSendDataAlloc)( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) { @@ -4389,7 +4397,7 @@ MANGLE(CxPlatSendDataAlloc)( CXPLAT_POOL* SendDataPool = Socket->UseRio ? &DatapathProc->RioSendDataPool : &DatapathProc->SendDataPool; - CXPLAT_SEND_DATA* SendData = CxPlatPoolAlloc(SendDataPool); + CXPLAT_SEND_DATA_INTERNAL* SendData = CxPlatPoolAlloc(SendDataPool); if (SendData != NULL) { SendData->Owner = DatapathProc; @@ -4421,13 +4429,13 @@ MANGLE(CxPlatSendDataAlloc)( } } - return SendData; + return (CXPLAT_SEND_DATA*)SendData; } _IRQL_requires_max_(DISPATCH_LEVEL) void MANGLE(CxPlatSendDataFree)( - _In_ CXPLAT_SEND_DATA* SendData + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) { for (UINT8 i = 0; i < SendData->WsaBufferCount; ++i) { @@ -4447,7 +4455,7 @@ RioSendBufferHeaderFromBuffer( void* RioSendBufferAllocateInternal( - CXPLAT_DATAPATH* Datapath, + CXPLAT_DATAPATH_INTERNAL* Datapath, _In_ uint32_t Size, _In_ uint32_t Tag ) @@ -4482,7 +4490,7 @@ RioSendBufferAllocate( { CXPLAT_DATAPATH_PROC* DatapathProc = CXPLAT_CONTAINING_RECORD(Pool, CXPLAT_DATAPATH_PROC, RioSendBufferPool); - CXPLAT_DATAPATH* Datapath = DatapathProc->Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath = DatapathProc->Datapath; return RioSendBufferAllocateInternal(Datapath, Size, Tag); } @@ -4496,7 +4504,7 @@ RioSendLargeBufferAllocate( { CXPLAT_DATAPATH_PROC* DatapathProc = CXPLAT_CONTAINING_RECORD(Pool, CXPLAT_DATAPATH_PROC, RioLargeSendBufferPool); - CXPLAT_DATAPATH* Datapath = DatapathProc->Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath = DatapathProc->Datapath; return RioSendBufferAllocateInternal(Datapath, Size, Tag); } @@ -4509,7 +4517,7 @@ RioSendBufferFree( ) { CXPLAT_RIO_SEND_BUFFER_HEADER* RioHeader = RioSendBufferHeaderFromBuffer(Entry); - CXPLAT_DATAPATH* Datapath = RioHeader->Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath = RioHeader->Datapath; UNREFERENCED_PARAMETER(Pool); @@ -4521,7 +4529,7 @@ RioSendBufferFree( static BOOLEAN CxPlatSendDataCanAllocSendSegment( - _In_ CXPLAT_SEND_DATA* SendData, + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ UINT16 MaxBufferLength ) { @@ -4543,7 +4551,7 @@ CxPlatSendDataCanAllocSendSegment( static BOOLEAN CxPlatSendDataCanAllocSend( - _In_ CXPLAT_SEND_DATA* SendData, + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ UINT16 MaxBufferLength ) { @@ -4556,7 +4564,7 @@ CxPlatSendDataCanAllocSend( static void CxPlatSendDataFinalizeSendBuffer( - _In_ CXPLAT_SEND_DATA* SendData + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) { if (SendData->ClientBuffer.len == 0) { @@ -4598,7 +4606,7 @@ _Success_(return != NULL) static WSABUF* CxPlatSendDataAllocDataBuffer( - _In_ CXPLAT_SEND_DATA* SendData + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) { CXPLAT_DBG_ASSERT(SendData->WsaBufferCount < SendData->Owner->Datapath->MaxSendBatchSize); @@ -4617,7 +4625,7 @@ _Success_(return != NULL) static QUIC_BUFFER* CxPlatSendDataAllocPacketBuffer( - _In_ CXPLAT_SEND_DATA* SendData, + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ UINT16 MaxBufferLength ) { @@ -4632,7 +4640,7 @@ _Success_(return != NULL) static QUIC_BUFFER* CxPlatSendDataAllocSegmentBuffer( - _In_ CXPLAT_SEND_DATA* SendData, + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ UINT16 MaxBufferLength ) { @@ -4667,7 +4675,7 @@ _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* MANGLE(CxPlatSendDataAllocBuffer)( - _In_ CXPLAT_SEND_DATA* SendData, + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ uint16_t MaxBufferLength ) { @@ -4690,7 +4698,7 @@ MANGLE(CxPlatSendDataAllocBuffer)( _IRQL_requires_max_(DISPATCH_LEVEL) void MANGLE(CxPlatSendDataFreeBuffer)( - _In_ CXPLAT_SEND_DATA* SendData, + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ QUIC_BUFFER* Buffer ) { @@ -4721,7 +4729,7 @@ MANGLE(CxPlatSendDataFreeBuffer)( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN MANGLE(CxPlatSendDataIsFull)( - _In_ CXPLAT_SEND_DATA* SendData + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) { return !CxPlatSendDataCanAllocSend(SendData, SendData->SegmentSize); @@ -4729,7 +4737,7 @@ MANGLE(CxPlatSendDataIsFull)( void CxPlatSendDataComplete( - _In_ CXPLAT_SEND_DATA* SendData, + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ ULONG IoResult ) { @@ -4746,36 +4754,36 @@ CxPlatSendDataComplete( if (SocketProc->Parent->Type != CXPLAT_SOCKET_UDP) { SocketProc->Parent->Datapath->TcpHandlers.SendComplete( - SocketProc->Parent, + (CXPLAT_SOCKET*)SocketProc->Parent, SocketProc->Parent->ClientContext, IoResult, SendData->TotalSize); } - CxPlatSendDataFree(SendData); + MANGLE(CxPlatSendDataFree)(SendData); } _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS CxPlatSocketSendWithRio( - _In_ CXPLAT_SEND_DATA* SendData, + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ WSAMSG* WSAMhdr ) { CXPLAT_SOCKET_PROC* SocketProc = SendData->SocketProc; - CXPLAT_DATAPATH* Datapath = SocketProc->Parent->Datapath; + CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->Parent->Datapath; RIO_BUF RemoteAddr = {0}; RIO_BUF Control = {0}; PRIO_CMSG_BUFFER RioCmsg = (PRIO_CMSG_BUFFER)SendData->CtrlBuf; RemoteAddr.BufferId = SendData->RioBufferId; - RemoteAddr.Offset = FIELD_OFFSET(CXPLAT_SEND_DATA, MappedRemoteAddress); + RemoteAddr.Offset = FIELD_OFFSET(CXPLAT_SEND_DATA_INTERNAL, MappedRemoteAddress); RemoteAddr.Length = sizeof(SendData->MappedRemoteAddress); RioCmsg->TotalLength = RIO_CMSG_BASE_SIZE + WSAMhdr->Control.len; Control.BufferId = SendData->RioBufferId; - Control.Offset = FIELD_OFFSET(CXPLAT_SEND_DATA, CtrlBuf); + Control.Offset = FIELD_OFFSET(CXPLAT_SEND_DATA_INTERNAL, CtrlBuf); Control.Length = RioCmsg->TotalLength; // @@ -4808,7 +4816,7 @@ CxPlatSocketSendWithRio( SocketProc->Parent, WsaError, "RIOSendEx"); - CxPlatSendDataFree(SendData); + MANGLE(CxPlatSendDataFree)(SendData); return HRESULT_FROM_WIN32(WsaError); } @@ -4823,7 +4831,7 @@ _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS CxPlatSocketSendInline( _In_ const QUIC_ADDR* LocalAddress, - _In_ CXPLAT_SEND_DATA* SendData + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) { CXPLAT_SOCKET_PROC* SocketProc = SendData->SocketProc; @@ -4835,8 +4843,8 @@ CxPlatSocketSendInline( QUIC_STATUS Status; int Result; DWORD BytesSent; - CXPLAT_DATAPATH* Datapath = SocketProc->Parent->Datapath; - CXPLAT_SOCKET* Socket = SocketProc->Parent; + CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->Parent->Datapath; + CXPLAT_SOCKET_INTERNAL* Socket = SocketProc->Parent; QuicTraceEvent( DatapathSend, @@ -4970,7 +4978,7 @@ CxPlatSocketSendInline( QUIC_STATUS CxPlatSocketSendEnqueue( _In_ const CXPLAT_ROUTE* Route, - _In_ CXPLAT_SEND_DATA* SendData + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) { SendData->LocalAddress = Route->LocalAddress; @@ -4986,9 +4994,9 @@ CxPlatSocketSendEnqueue( _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketSend)( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ const CXPLAT_ROUTE* Route, - _In_ CXPLAT_SEND_DATA* SendData + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) { CXPLAT_DBG_ASSERT(Socket != NULL && Route != NULL && SendData != NULL); @@ -5032,7 +5040,7 @@ CxPlatDataPathSocketProcessQueuedSend( ) { UNREFERENCED_PARAMETER(Cqe); - CXPLAT_SEND_DATA* SendData = CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA, Sqe); + CXPLAT_SEND_DATA_INTERNAL* SendData = CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA_INTERNAL, Sqe); CXPLAT_SOCKET_PROC* SocketProc = SendData->SocketProc; if (CxPlatRundownAcquire(&SocketProc->RundownRef)) { @@ -5051,7 +5059,7 @@ CxPlatDataPathStartRioSends( while (!CxPlatListIsEmpty(&SocketProc->RioSendOverflow) && SocketProc->RioSendCount < RIO_SEND_QUEUE_DEPTH) { CXPLAT_LIST_ENTRY* Entry = CxPlatListRemoveHead(&SocketProc->RioSendOverflow); - CXPLAT_SEND_DATA* SendData = CONTAINING_RECORD(Entry, CXPLAT_SEND_DATA, RioOverflowEntry); + CXPLAT_SEND_DATA_INTERNAL* SendData = CONTAINING_RECORD(Entry, CXPLAT_SEND_DATA_INTERNAL, RioOverflowEntry); // // RIO always queues sends. @@ -5090,14 +5098,14 @@ MANGLE(CxPlatDataPathProcessCqe)( break; case DATAPATH_IO_SEND: - SocketProc = CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA, Sqe)->SocketProc; + SocketProc = CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA_INTERNAL, Sqe)->SocketProc; CxPlatSendDataComplete( - CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA, Sqe), + CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA_INTERNAL, Sqe), RtlNtStatusToDosError((NTSTATUS)Cqe->Internal)); break; case DATAPATH_IO_QUEUE_SEND: - SocketProc = CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA, Sqe)->SocketProc; + SocketProc = CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA_INTERNAL, Sqe)->SocketProc; CxPlatDataPathSocketProcessQueuedSend(Sqe, Cqe); break; @@ -5141,7 +5149,31 @@ MANGLE(CxPlatDataPathProcessCqe)( } } -void InitSocketDataPath(struct CXPLAT_DATAPATH_FUNCTIONS* Funcs) { - Funcs->CxPlatDataPathRecvPacketToRecvData = MANGLE(CxPlatDataPathRecvPacketToRecvData); - -} \ No newline at end of file +const struct CXPLAT_DATAPATH_FUNCTIONS DataPathUserFuncs = { + MANGLE(CxPlatDataPathRecvPacketToRecvData), + MANGLE(CxPlatDataPathRecvDataToRecvPacket), + MANGLE(CxPlatDataPathInitialize), + MANGLE(CxPlatDataPathUninitialize), + MANGLE(CxPlatDataPathUpdateConfig), + MANGLE(CxPlatDataPathGetSupportedFeatures), + MANGLE(CxPlatDataPathIsPaddingPreferred), + MANGLE(CxPlatDataPathGetLocalAddresses), + MANGLE(CxPlatDataPathGetGatewayAddresses), + MANGLE(CxPlatDataPathResolveAddress), + MANGLE(CxPlatSocketCreateUdp), + MANGLE(CxPlatSocketCreateTcp), + MANGLE(CxPlatSocketCreateTcpListener), + MANGLE(CxPlatSocketDelete), + MANGLE(CxPlatSocketUpdateQeo), + MANGLE(CxPlatSocketGetLocalMtu), + MANGLE(CxPlatSocketGetLocalAddress), + MANGLE(CxPlatSocketGetRemoteAddress), + MANGLE(CxPlatRecvDataReturn), + MANGLE(CxPlatSendDataAlloc), + MANGLE(CxPlatSendDataFree), + MANGLE(CxPlatSendDataAllocBuffer), + MANGLE(CxPlatSendDataFreeBuffer), + MANGLE(CxPlatSendDataIsFull), + MANGLE(CxPlatSocketSend), + MANGLE(CxPlatDataPathProcessCqe) + }; From e98d136fccf8db633951a8649eb9c31200d81e42 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Fri, 14 Jul 2023 13:54:54 -0700 Subject: [PATCH 17/87] just unify build of both normal socket and xdp --- src/inc/quic_datapath.h | 2 +- src/platform/CMakeLists.txt | 13 +-- src/platform/datapath_raw.h | 58 +++++++------- src/platform/datapath_raw_socket.c | 26 +++--- src/platform/datapath_raw_socket_win.c | 10 +-- src/platform/datapath_raw_win.c | 106 ++++++++++++------------- src/platform/datapath_raw_xdp_win.c | 32 ++++---- src/platform/datapath_win.c | 64 ++++++++++++--- 8 files changed, 177 insertions(+), 134 deletions(-) diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index d3203f1cbf..318cd4fecd 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -137,7 +137,7 @@ typedef enum DATAPATH_TYPE { typedef struct CXPLAT_DATAPATH_INTERNAL CXPLAT_DATAPATH_INTERNAL; typedef struct CXPLAT_DATAPATH { - CXPLAT_DATAPATH_INTERNAL* User; + CXPLAT_DATAPATH_INTERNAL* User; // TODO: internal to have CXPLAT_DATAPATH_RAW? CXPLAT_DATAPATH_INTERNAL* Xdp; } CXPLAT_DATAPATH; diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt index 73b51a7382..ded90c0519 100644 --- a/src/platform/CMakeLists.txt +++ b/src/platform/CMakeLists.txt @@ -12,12 +12,7 @@ endif() set(SOURCES crypt.c hashtable.c pcp.c platform_worker.c toeplitz.c) if("${CX_PLATFORM}" STREQUAL "windows") - set(SOURCES ${SOURCES} platform_winuser.c storage_winuser.c) - if(QUIC_USE_XDP) - set(SOURCES ${SOURCES} datapath_raw_win.c datapath_raw_socket.c datapath_raw_socket_win.c datapath_raw_xdp_win.c) - else() - set(SOURCES ${SOURCES} datapath_win.c datapath_winuser.c) - endif() + set(SOURCES ${SOURCES} platform_winuser.c storage_winuser.c datapath_win.c datapath_winuser.c datapath_raw_win.c datapath_raw_socket.c datapath_raw_socket_win.c datapath_raw_xdp_win.c) else() set(SOURCES ${SOURCES} inline.c platform_posix.c storage_posix.c cgroup.c) if(CX_PLATFORM STREQUAL "linux" AND NOT CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") @@ -50,7 +45,7 @@ endif() add_library(platform STATIC ${SOURCES}) -if ("${CX_PLATFORM}" STREQUAL "windows" AND QUIC_USE_XDP) +if("${CX_PLATFORM}" STREQUAL "windows") target_link_libraries( platform PUBLIC @@ -68,14 +63,12 @@ target_link_libraries(platform PRIVATE warnings main_binary_link_args) set_property(TARGET platform PROPERTY FOLDER "${QUIC_FOLDER_PREFIX}libraries") -if ("${CX_PLATFORM}" STREQUAL "windows" AND QUIC_USE_XDP) +if ("${CX_PLATFORM}" STREQUAL "windows") target_include_directories( platform PRIVATE ${EXTRA_PLATFORM_INCLUDE_DIRECTORIES} ${PROJECT_SOURCE_DIR}/artifacts/xdp/include) -else() - target_include_directories(platform PRIVATE ${EXTRA_PLATFORM_INCLUDE_DIRECTORIES}) endif() if (MSVC AND (QUIC_TLS STREQUAL "openssl" OR QUIC_TLS STREQUAL "schannel") AND NOT QUIC_ENABLE_SANITIZERS) diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index ec2b08ad6f..3e1d9dc1bf 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -17,7 +17,9 @@ typedef struct CXPLAT_SOCKET_POOL { } CXPLAT_SOCKET_POOL; -typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; +#define MANGLE(x) XDP_##x + +typedef struct CXPLAT_DATAPATH_RAW CXPLAT_DATAPATH_RAW; // // A worker thread for draining queued route resolution operations. @@ -43,7 +45,7 @@ typedef struct QUIC_CACHEALIGN CXPLAT_ROUTE_RESOLUTION_WORKER { CXPLAT_LIST_ENTRY Operations; } CXPLAT_ROUTE_RESOLUTION_WORKER; -typedef struct CXPLAT_DATAPATH { +typedef struct CXPLAT_DATAPATH_RAW { CXPLAT_UDP_DATAPATH_CALLBACKS UdpHandlers; @@ -59,7 +61,7 @@ typedef struct CXPLAT_DATAPATH { #endif BOOLEAN UseTcp; -} CXPLAT_DATAPATH; +} CXPLAT_DATAPATH_RAW; #define ETH_MAC_ADDR_LEN 6 @@ -80,7 +82,8 @@ typedef struct CXPLAT_INTERFACE { } OffloadStatus; } CXPLAT_INTERFACE; -typedef struct CXPLAT_SEND_DATA { +typedef struct CXPLAT_SEND_DATA_INTERNAL { + CXPLAT_SEND_DATA; // // The type of ECN markings needed for send. @@ -89,7 +92,7 @@ typedef struct CXPLAT_SEND_DATA { QUIC_BUFFER Buffer; -} CXPLAT_SEND_DATA; +} CXPLAT_SEND_DATA_INTERNAL; // // Queries the raw datapath stack for the total size needed to allocate the @@ -107,7 +110,7 @@ CxPlatDpRawGetDatapathSize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatDpRawInitialize( - _Inout_ CXPLAT_DATAPATH* Datapath, + _Inout_ CXPLAT_DATAPATH_RAW* Datapath, _In_ uint32_t ClientRecvContextLength, _In_opt_ const QUIC_EXECUTION_CONFIG* Config ); @@ -118,7 +121,7 @@ CxPlatDpRawInitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDpRawUninitialize( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH_RAW* Datapath ); // @@ -127,7 +130,7 @@ CxPlatDpRawUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDataPathUninitializeComplete( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH_RAW* Datapath ); // @@ -136,7 +139,7 @@ CxPlatDataPathUninitializeComplete( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDpRawUpdateConfig( - _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ); @@ -147,7 +150,7 @@ CxPlatDpRawUpdateConfig( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDpRawPlumbRulesOnSocket( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ BOOLEAN IsCreated ); @@ -206,7 +209,7 @@ CxPlatDpRawParseEthernet( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawRxEthernet( - _In_ const CXPLAT_DATAPATH* Datapath, + _In_ const CXPLAT_DATAPATH_RAW* Datapath, _In_reads_(PacketCount) CXPLAT_RECV_DATA** Packets, _In_ uint16_t PacketCount @@ -227,7 +230,7 @@ CxPlatDpRawRxFree( _IRQL_requires_max_(DISPATCH_LEVEL) CXPLAT_SEND_DATA* CxPlatDpRawTxAlloc( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ); @@ -237,7 +240,7 @@ CxPlatDpRawTxAlloc( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawTxFree( - _In_ CXPLAT_SEND_DATA* SendData + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ); // @@ -246,18 +249,19 @@ CxPlatDpRawTxFree( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawTxEnqueue( - _In_ CXPLAT_SEND_DATA* SendData + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ); // // Raw Socket Interface // -typedef struct CXPLAT_SOCKET { +typedef struct CXPLAT_SOCKET_INTERNAL { + CXPLAT_SOCKET; CXPLAT_HASHTABLE_ENTRY Entry; CXPLAT_RUNDOWN_REF Rundown; - CXPLAT_DATAPATH* Datapath; + CXPLAT_DATAPATH_RAW* Datapath; SOCKET AuxSocket; void* CallbackContext; QUIC_ADDR LocalAddress; @@ -271,9 +275,9 @@ typedef struct CXPLAT_SOCKET { uint8_t CibirId[6]; // CIBIR ID data BOOLEAN UseTcp; // Quic over TCP - CXPLAT_SEND_DATA* PausedTcpSend; // Paused TCP send data *before* framing - CXPLAT_SEND_DATA* CachedRstSend; // Cached TCP RST send data *after* framing -} CXPLAT_SOCKET; + CXPLAT_SEND_DATA_INTERNAL* PausedTcpSend; // Paused TCP send data *before* framing + CXPLAT_SEND_DATA_INTERNAL* CachedRstSend; // Cached TCP RST send data *after* framing +} CXPLAT_SOCKET_INTERNAL; BOOLEAN CxPlatSockPoolInitialize( @@ -293,7 +297,7 @@ CxPlatSockPoolUninitialize( inline BOOLEAN CxPlatSocketCompare( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress ) @@ -315,7 +319,7 @@ CxPlatSocketCompare( // // Finds a socket to deliver received packets with the given addresses. // -CXPLAT_SOCKET* +CXPLAT_SOCKET_INTERNAL* CxPlatGetSocket( _In_ const CXPLAT_SOCKET_POOL* Pool, _In_ const QUIC_ADDR* LocalAddress, @@ -325,13 +329,13 @@ CxPlatGetSocket( QUIC_STATUS CxPlatTryAddSocket( _In_ CXPLAT_SOCKET_POOL* Pool, - _In_ CXPLAT_SOCKET* Socket + _In_ CXPLAT_SOCKET_INTERNAL* Socket ); void CxPlatRemoveSocket( _In_ CXPLAT_SOCKET_POOL* Pool, - _In_ CXPLAT_SOCKET* Socket + _In_ CXPLAT_SOCKET_INTERNAL* Socket ); // @@ -351,28 +355,28 @@ typedef enum PACKET_TYPE { _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawSocketAckSyn( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ CXPLAT_RECV_DATA* Packet ); _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawSocketSyn( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ const CXPLAT_ROUTE* Route ); _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawSocketAckFin( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ CXPLAT_RECV_DATA* Packet ); _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatFramingWriteHeaders( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ const CXPLAT_ROUTE* Route, _Inout_ QUIC_BUFFER* Buffer, _In_ CXPLAT_ECN_TYPE ECN, diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index 5c85731826..2865ab597b 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -17,20 +17,20 @@ #pragma warning(disable:4116) // unnamed type definition in parentheses #pragma warning(disable:4100) // unreferenced formal parameter -CXPLAT_SOCKET* +CXPLAT_SOCKET_INTERNAL* CxPlatGetSocket( _In_ const CXPLAT_SOCKET_POOL* Pool, _In_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress ) { - CXPLAT_SOCKET* Socket = NULL; + CXPLAT_SOCKET_INTERNAL* Socket = NULL; CXPLAT_HASHTABLE_LOOKUP_CONTEXT Context; CXPLAT_HASHTABLE_ENTRY* Entry; CxPlatRwLockAcquireShared(&((CXPLAT_SOCKET_POOL*)Pool)->Lock); Entry = CxPlatHashtableLookup(&Pool->Sockets, LocalAddress->Ipv4.sin_port, &Context); while (Entry != NULL) { - CXPLAT_SOCKET* Temp = CXPLAT_CONTAINING_RECORD(Entry, CXPLAT_SOCKET, Entry); + CXPLAT_SOCKET_INTERNAL* Temp = CXPLAT_CONTAINING_RECORD(Entry, CXPLAT_SOCKET_INTERNAL, Entry); if (CxPlatSocketCompare(Temp, LocalAddress, RemoteAddress)) { if (CxPlatRundownAcquire(&Temp->Rundown)) { Socket = Temp; @@ -44,7 +44,7 @@ CxPlatGetSocket( } void -CxPlatResolveRouteComplete( +MANGLE(CxPlatResolveRouteComplete)( _In_ void* Context, _Inout_ CXPLAT_ROUTE* Route, _In_reads_bytes_(6) const uint8_t* PhysicalAddress, @@ -69,7 +69,7 @@ CxPlatResolveRouteComplete( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatUpdateRoute( +MANGLE(CxPlatUpdateRoute)( _Inout_ CXPLAT_ROUTE* DstRoute, _In_ CXPLAT_ROUTE* SrcRoute ) @@ -481,7 +481,7 @@ CxPlatFramingTransportChecksum( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawSocketAckFin( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ CXPLAT_RECV_DATA* Packet ) { @@ -489,7 +489,7 @@ CxPlatDpRawSocketAckFin( CXPLAT_ROUTE* Route = Packet->Route; CXPLAT_SEND_CONFIG SendConfig = { Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(Socket, &SendConfig); + CXPLAT_SEND_DATA_INTERNAL *SendData = (CXPLAT_SEND_DATA_INTERNAL*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); if (SendData == NULL) { return; } @@ -520,7 +520,7 @@ CxPlatDpRawSocketAckFin( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawSocketAckSyn( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ CXPLAT_RECV_DATA* Packet ) { @@ -528,7 +528,7 @@ CxPlatDpRawSocketAckSyn( CXPLAT_ROUTE* Route = Packet->Route; CXPLAT_SEND_CONFIG SendConfig = { Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(Socket, &SendConfig); + CXPLAT_SEND_DATA_INTERNAL *SendData = (CXPLAT_SEND_DATA_INTERNAL*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); if (SendData == NULL) { return; } @@ -577,7 +577,7 @@ CxPlatDpRawSocketAckSyn( TH_ACK); CxPlatDpRawTxEnqueue(SendData); - SendData = CxPlatSendDataAlloc(Socket, &SendConfig); + SendData = (CXPLAT_SEND_DATA_INTERNAL*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); if (SendData == NULL) { return; } @@ -605,14 +605,14 @@ CxPlatDpRawSocketAckSyn( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawSocketSyn( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ const CXPLAT_ROUTE* Route ) { CXPLAT_DBG_ASSERT(Socket->UseTcp); CXPLAT_SEND_CONFIG SendConfig = { (CXPLAT_ROUTE*)Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(Socket, &SendConfig); + CXPLAT_SEND_DATA_INTERNAL *SendData = (CXPLAT_SEND_DATA_INTERNAL*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); if (SendData == NULL) { return; } @@ -639,7 +639,7 @@ CxPlatDpRawSocketSyn( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatFramingWriteHeaders( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ const CXPLAT_ROUTE* Route, _Inout_ QUIC_BUFFER* Buffer, _In_ CXPLAT_ECN_TYPE ECN, diff --git a/src/platform/datapath_raw_socket_win.c b/src/platform/datapath_raw_socket_win.c index 02446c3634..80806f5568 100644 --- a/src/platform/datapath_raw_socket_win.c +++ b/src/platform/datapath_raw_socket_win.c @@ -56,7 +56,7 @@ CxPlatSockPoolUninitialize( void CxPlatRemoveSocket( _In_ CXPLAT_SOCKET_POOL* Pool, - _In_ CXPLAT_SOCKET* Socket + _In_ CXPLAT_SOCKET_INTERNAL* Socket ) { CxPlatRwLockAcquireExclusive(&Pool->Lock); @@ -77,8 +77,8 @@ CxPlatRemoveSocket( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatResolveRoute( - _In_ CXPLAT_SOCKET* Socket, +MANGLE(CxPlatResolveRoute)( + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _Inout_ CXPLAT_ROUTE* Route, _In_ uint8_t PathId, _In_ void* Context, @@ -246,7 +246,7 @@ CxPlatResolveRoute( QUIC_STATUS CxPlatTryAddSocket( _In_ CXPLAT_SOCKET_POOL* Pool, - _In_ CXPLAT_SOCKET* Socket + _In_ CXPLAT_SOCKET_INTERNAL* Socket ) { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; @@ -541,7 +541,7 @@ CxPlatTryAddSocket( Entry = CxPlatHashtableLookup(&Pool->Sockets, Socket->LocalAddress.Ipv4.sin_port, &Context); while (Entry != NULL) { - CXPLAT_SOCKET* Temp = CXPLAT_CONTAINING_RECORD(Entry, CXPLAT_SOCKET, Entry); + CXPLAT_SOCKET_INTERNAL* Temp = CXPLAT_CONTAINING_RECORD(Entry, CXPLAT_SOCKET_INTERNAL, Entry); if (CxPlatSocketCompare(Temp, &Socket->LocalAddress, &Socket->RemoteAddress)) { Status = QUIC_STATUS_ADDRESS_IN_USE; break; diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index 0c4b5bb8d6..caef50eea5 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -45,7 +45,7 @@ CxPlatDataPathRouteWorkerUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatDataPathRouteWorkerInitialize( - _Inout_ CXPLAT_DATAPATH* DataPath + _Inout_ CXPLAT_DATAPATH_RAW* DataPath ) { QUIC_STATUS Status; @@ -104,12 +104,12 @@ CxPlatDataPathRouteWorkerInitialize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatDataPathInitialize( +MANGLE(CxPlatDataPathInitialize)( _In_ uint32_t ClientRecvContextLength, _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, _In_opt_ QUIC_EXECUTION_CONFIG* Config, - _Out_ CXPLAT_DATAPATH** NewDataPath + _Out_ CXPLAT_DATAPATH_RAW** NewDataPath ) { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; @@ -133,7 +133,7 @@ CxPlatDataPathInitialize( return QUIC_STATUS_OUT_OF_MEMORY; } - CXPLAT_DATAPATH* DataPath = CXPLAT_ALLOC_PAGED(DatapathSize, QUIC_POOL_DATAPATH); + CXPLAT_DATAPATH_RAW* DataPath = CXPLAT_ALLOC_PAGED(DatapathSize, QUIC_POOL_DATAPATH); if (DataPath == NULL) { QuicTraceEvent( AllocFailure, @@ -195,8 +195,8 @@ CxPlatDataPathInitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatDataPathUninitialize( - _In_ CXPLAT_DATAPATH* Datapath +MANGLE(CxPlatDataPathUninitialize)( + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { if (Datapath != NULL) { @@ -213,7 +213,7 @@ CxPlatDataPathUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDataPathUninitializeComplete( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { #if DEBUG @@ -228,8 +228,8 @@ CxPlatDataPathUninitializeComplete( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatDataPathUpdateConfig( - _In_ CXPLAT_DATAPATH* Datapath, +MANGLE(CxPlatDataPathUpdateConfig)( + _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ) { @@ -238,8 +238,8 @@ CxPlatDataPathUpdateConfig( _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t -CxPlatDataPathGetSupportedFeatures( - _In_ CXPLAT_DATAPATH* Datapath +MANGLE(CxPlatDataPathGetSupportedFeatures)( + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { return 0; @@ -247,8 +247,8 @@ CxPlatDataPathGetSupportedFeatures( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -CxPlatDataPathIsPaddingPreferred( - _In_ CXPLAT_DATAPATH* Datapath +MANGLE(CxPlatDataPathIsPaddingPreferred)( + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { return FALSE; @@ -257,8 +257,8 @@ CxPlatDataPathIsPaddingPreferred( _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS -CxPlatDataPathGetLocalAddresses( - _In_ CXPLAT_DATAPATH* Datapath, +MANGLE(CxPlatDataPathGetLocalAddresses)( + _In_ CXPLAT_DATAPATH_RAW* Datapath, _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) CXPLAT_ADAPTER_ADDRESS** Addresses, _Out_ uint32_t* AddressesCount @@ -270,8 +270,8 @@ CxPlatDataPathGetLocalAddresses( _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS -CxPlatDataPathGetGatewayAddresses( - _In_ CXPLAT_DATAPATH* Datapath, +MANGLE(CxPlatDataPathGetGatewayAddresses)( + _In_ CXPLAT_DATAPATH_RAW* Datapath, _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) QUIC_ADDR** GatewayAddresses, _Out_ uint32_t* GatewayAddressesCount @@ -313,8 +313,8 @@ CxPlatDataPathPopulateTargetAddress( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatDataPathResolveAddress( - _In_ CXPLAT_DATAPATH* Datapath, +MANGLE(CxPlatDataPathResolveAddress)( + _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_z_ const char* HostName, _Inout_ QUIC_ADDR* Address ) @@ -387,10 +387,10 @@ CxPlatDataPathResolveAddress( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatSocketCreateUdp( - _In_ CXPLAT_DATAPATH* Datapath, +MANGLE(CxPlatSocketCreateUdp)( + _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_ const CXPLAT_UDP_CONFIG* Config, - _Out_ CXPLAT_SOCKET** NewSocket + _Out_ CXPLAT_SOCKET_INTERNAL** NewSocket ) { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; @@ -472,12 +472,12 @@ CxPlatSocketCreateUdp( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatSocketCreateTcp( - _In_ CXPLAT_DATAPATH* Datapath, +MANGLE(CxPlatSocketCreateTcp)( + _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, _In_opt_ void* CallbackContext, - _Out_ CXPLAT_SOCKET** Socket + _Out_ CXPLAT_SOCKET_INTERNAL** Socket ) { return QUIC_STATUS_NOT_SUPPORTED; @@ -485,11 +485,11 @@ CxPlatSocketCreateTcp( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatSocketCreateTcpListener( - _In_ CXPLAT_DATAPATH* Datapath, +MANGLE(CxPlatSocketCreateTcpListener)( + _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ void* RecvCallbackContext, - _Out_ CXPLAT_SOCKET** NewSocket + _Out_ CXPLAT_SOCKET_INTERNAL** NewSocket ) { return QUIC_STATUS_NOT_SUPPORTED; @@ -497,8 +497,8 @@ CxPlatSocketCreateTcpListener( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatSocketDelete( - _In_ CXPLAT_SOCKET* Socket +MANGLE(CxPlatSocketDelete)( + _In_ CXPLAT_SOCKET_INTERNAL* Socket ) { CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); @@ -517,8 +517,8 @@ CxPlatSocketDelete( _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 -CxPlatSocketGetLocalMtu( - _In_ CXPLAT_SOCKET* Socket +MANGLE(CxPlatSocketGetLocalMtu)( + _In_ CXPLAT_SOCKET_INTERNAL* Socket ) { if (Socket->UseTcp) { @@ -530,8 +530,8 @@ CxPlatSocketGetLocalMtu( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatSocketGetLocalAddress( - _In_ CXPLAT_SOCKET* Socket, +MANGLE(CxPlatSocketGetLocalAddress)( + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _Out_ QUIC_ADDR* Address ) { @@ -540,8 +540,8 @@ CxPlatSocketGetLocalAddress( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatSocketGetRemoteAddress( - _In_ CXPLAT_SOCKET* Socket, +MANGLE(CxPlatSocketGetRemoteAddress)( + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _Out_ QUIC_ADDR* Address ) { @@ -551,14 +551,14 @@ CxPlatSocketGetRemoteAddress( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawRxEthernet( - _In_ const CXPLAT_DATAPATH* Datapath, + _In_ const CXPLAT_DATAPATH_RAW* Datapath, _In_reads_(PacketCount) CXPLAT_RECV_DATA** Packets, _In_ uint16_t PacketCount ) { for (uint16_t i = 0; i < PacketCount; i++) { - CXPLAT_SOCKET* Socket = NULL; + CXPLAT_SOCKET_INTERNAL* Socket = NULL; CXPLAT_RECV_DATA* PacketChain = Packets[i]; CXPLAT_DBG_ASSERT(PacketChain->Next == NULL); @@ -596,7 +596,7 @@ CxPlatDpRawRxEthernet( CXPLAT_DBG_ASSERT(Packets[i+1]->Next == NULL); i++; } - Datapath->UdpHandlers.Receive(Socket, Socket->CallbackContext, (CXPLAT_RECV_DATA*)PacketChain); + Datapath->UdpHandlers.Receive((CXPLAT_SOCKET*)Socket, Socket->CallbackContext, (CXPLAT_RECV_DATA*)PacketChain); } else if (PacketChain->Reserved == L4_TYPE_TCP_SYN || PacketChain->Reserved == L4_TYPE_TCP_SYNACK) { CxPlatDpRawSocketAckSyn(Socket, PacketChain); CxPlatDpRawRxFree(PacketChain); @@ -616,7 +616,7 @@ CxPlatDpRawRxEthernet( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatRecvDataReturn( +MANGLE(CxPlatRecvDataReturn)( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ) { @@ -626,8 +626,8 @@ CxPlatRecvDataReturn( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* -CxPlatSendDataAlloc( - _In_ CXPLAT_SOCKET* Socket, +MANGLE(CxPlatSendDataAlloc)( + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) { @@ -637,8 +637,8 @@ CxPlatSendDataAlloc( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* -CxPlatSendDataAllocBuffer( - _In_ CXPLAT_SEND_DATA* SendData, +MANGLE(CxPlatSendDataAllocBuffer)( + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ uint16_t MaxBufferLength ) { @@ -648,8 +648,8 @@ CxPlatSendDataAllocBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatSendDataFree( - _In_ CXPLAT_SEND_DATA* SendData +MANGLE(CxPlatSendDataFree)( + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) { CxPlatDpRawTxFree(SendData); @@ -657,8 +657,8 @@ CxPlatSendDataFree( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatSendDataFreeBuffer( - _In_ CXPLAT_SEND_DATA* SendData, +MANGLE(CxPlatSendDataFreeBuffer)( + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ QUIC_BUFFER* Buffer ) { @@ -667,8 +667,8 @@ CxPlatSendDataFreeBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -CxPlatSendDataIsFull( - _In_ CXPLAT_SEND_DATA* SendData +MANGLE(CxPlatSendDataIsFull)( + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) { return TRUE; @@ -678,10 +678,10 @@ CxPlatSendDataIsFull( _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS -CxPlatSocketSend( - _In_ CXPLAT_SOCKET* Socket, +MANGLE(CxPlatSocketSend)( + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ const CXPLAT_ROUTE* Route, - _In_ CXPLAT_SEND_DATA* SendData + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) { if (Socket->UseTcp && diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index eb44b63e94..75f556bdc9 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -24,7 +24,7 @@ #endif typedef struct XDP_DATAPATH { - CXPLAT_DATAPATH; + CXPLAT_DATAPATH_RAW; DECLSPEC_CACHEALIGN // // Currently, all XDP interfaces share the same config. @@ -100,7 +100,7 @@ typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) XDP_RX_PACKET { } XDP_RX_PACKET; typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) XDP_TX_PACKET { - CXPLAT_SEND_DATA; + CXPLAT_SEND_DATA_INTERNAL; XDP_QUEUE* Queue; CXPLAT_LIST_ENTRY Link; uint8_t FrameBuffer[MAX_ETH_FRAME_SIZE]; @@ -125,7 +125,7 @@ CxPlatXdpExecute( ); CXPLAT_RECV_DATA* -CxPlatDataPathRecvPacketToRecvData( +MANGLE(CxPlatDataPathRecvPacketToRecvData)( _In_ const CXPLAT_RECV_PACKET* const Context ) { @@ -133,7 +133,7 @@ CxPlatDataPathRecvPacketToRecvData( } CXPLAT_RECV_PACKET* -CxPlatDataPathRecvDataToRecvPacket( +MANGLE(CxPlatDataPathRecvDataToRecvPacket)( _In_ const CXPLAT_RECV_DATA* const Datagram ) { @@ -1011,7 +1011,7 @@ CxPlatDpRawGetDatapathSize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatDpRawInitialize( - _Inout_ CXPLAT_DATAPATH* Datapath, + _Inout_ CXPLAT_DATAPATH_RAW* Datapath, _In_ uint32_t ClientRecvContextLength, _In_opt_ const QUIC_EXECUTION_CONFIG* Config ) @@ -1266,14 +1266,14 @@ CxPlatDpRawRelease( CxPlatFree(Interface, IF_TAG); } XdpUnloadApi(Xdp->XdpApiLoadContext, Xdp->XdpApi); - CxPlatDataPathUninitializeComplete((CXPLAT_DATAPATH*)Xdp); + CxPlatDataPathUninitializeComplete((CXPLAT_DATAPATH_RAW*)Xdp); } } _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDpRawUninitialize( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { XDP_DATAPATH* Xdp = (XDP_DATAPATH*)Datapath; @@ -1292,7 +1292,7 @@ CxPlatDpRawUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDpRawUpdateConfig( - _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ) { @@ -1302,8 +1302,8 @@ CxPlatDpRawUpdateConfig( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatSocketUpdateQeo( - _In_ CXPLAT_SOCKET* Socket, +MANGLE(CxPlatSocketUpdateQeo)( + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, _In_ uint32_t OffloadCount @@ -1397,7 +1397,7 @@ CxPlatDpRawClearPortBit( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDpRawPlumbRulesOnSocket( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _In_ BOOLEAN IsCreated ) { @@ -1628,7 +1628,7 @@ CxPlatXdpRx( } if (PacketCount > 0) { - CxPlatDpRawRxEthernet((CXPLAT_DATAPATH*)Xdp, Buffers, (uint16_t)PacketCount); + CxPlatDpRawRxEthernet((CXPLAT_DATAPATH_RAW*)Xdp, Buffers, (uint16_t)PacketCount); } if (XskRingError(&Queue->RxRing) && !Queue->Error) { @@ -1688,7 +1688,7 @@ CxPlatDpRawRxFree( _IRQL_requires_max_(DISPATCH_LEVEL) CXPLAT_SEND_DATA* CxPlatDpRawTxAlloc( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_INTERNAL* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) { @@ -1711,7 +1711,7 @@ CxPlatDpRawTxAlloc( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawTxFree( - _In_ CXPLAT_SEND_DATA* SendData + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) { XDP_TX_PACKET* Packet = (XDP_TX_PACKET*)SendData; @@ -1721,7 +1721,7 @@ CxPlatDpRawTxFree( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawTxEnqueue( - _In_ CXPLAT_SEND_DATA* SendData + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) { XDP_TX_PACKET* Packet = (XDP_TX_PACKET*)SendData; @@ -1916,7 +1916,7 @@ CxPlatXdpExecute( } void -CxPlatDataPathProcessCqe( +MANGLE(CxPlatDataPathProcessCqe)( _In_ CXPLAT_CQE* Cqe ) { diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 8608aa01d7..091945f016 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -194,15 +194,20 @@ CxPlatSocketCreateUdp( // - if not, use xdp // - if server, init socket, then share info to xdp? - if (FALSE /*(server && xdp) || (client && remote is not loopback)*/) { - // use XDP - } else { - Status = DataPathUserFuncs.CxPlatSocketCreateUdp( - Datapath->User, - Config, - NewSocket); - (*NewSocket)->DataPathType = DATAPATH_TYPE_USER; - } + Status = DataPathUserFuncs.CxPlatSocketCreateUdp( + Datapath->User, + Config, + NewSocket); + (*NewSocket)->DataPathType = DATAPATH_TYPE_USER; + + // if (FALSE /*(server && xdp) || (client && remote is not loopback)*/) { + // // use XDP + // } else { + // Status = DataPathUserFuncs.CxPlatSocketCreateUdp( + // Datapath->User, + // Config, + // NewSocket); + // } return Status; } @@ -436,3 +441,44 @@ CxPlatDataPathProcessCqe( // what is the difference between datapath? DataPathUserFuncs.CxPlatDataPathProcessCqe(Cqe); } + + + +// TODO: remove ifdef +#ifdef QUIC_USE_RAW_DATAPATH +void +CxPlatResolveRouteComplete( + _In_ void* Connection, + _Inout_ CXPLAT_ROUTE* Route, + _In_reads_bytes_(6) const uint8_t* PhysicalAddress, + _In_ uint8_t PathId + ) +{ + +} + +// +// Tries to resolve route and neighbor for the given destination address. +// +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatResolveRoute( + _In_ CXPLAT_SOCKET* Socket, + _Inout_ CXPLAT_ROUTE* Route, + _In_ uint8_t PathId, + _In_ void* Context, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback + ) +{ + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatUpdateRoute( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ +} +#endif // QUIC_USE_RAW_DATAPATH From 95fd7b7e9164c6a1d0c0d0f4c343c40dafe54fa6 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Thu, 20 Jul 2023 14:59:14 -0700 Subject: [PATCH 18/87] SOCKET: RAW{...Socket{BASE{addr}}}, DATAPATH: XDP{RAW{DATAPATH*}} <-> DATAPATH{RAW*, BASE{callbacks}} --- src/inc/quic_datapath.h | 175 +++++-- src/platform/datapath_raw.h | 58 +-- src/platform/datapath_raw_socket.c | 31 +- src/platform/datapath_raw_socket_win.c | 309 +----------- src/platform/datapath_raw_win.c | 375 ++++++-------- src/platform/datapath_raw_xdp.h | 16 +- src/platform/datapath_raw_xdp_win.c | 25 +- src/platform/datapath_win.c | 423 +++++++++++----- src/platform/datapath_winuser.c | 663 +++---------------------- src/platform/platform_internal.h | 361 ++++++++++++++ 10 files changed, 1098 insertions(+), 1338 deletions(-) diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 318cd4fecd..4c413b009a 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -126,42 +126,54 @@ PacketSizeFromUdpPayloadSize( // // typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; -typedef enum DATAPATH_TYPE { - DATAPATH_TYPE_NONE, - DATAPATH_TYPE_USER, - DATAPATH_TYPE_XDP - // DATAPATH_TYPE_KERNEL, - // DATAPATH_TYPE_DPDK, -} DATAPATH_TYPE; -typedef struct CXPLAT_DATAPATH_INTERNAL CXPLAT_DATAPATH_INTERNAL; +typedef struct CXPLAT_DATAPATH_BASE CXPLAT_DATAPATH_BASE; +typedef struct CXPLAT_DATAPATH_RAW CXPLAT_DATAPATH_RAW; +typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; -typedef struct CXPLAT_DATAPATH { - CXPLAT_DATAPATH_INTERNAL* User; // TODO: internal to have CXPLAT_DATAPATH_RAW? - CXPLAT_DATAPATH_INTERNAL* Xdp; -} CXPLAT_DATAPATH; // // Represents a UDP or TCP abstraction. // -typedef struct CXPLAT_SOCKET_INTERNAL CXPLAT_SOCKET_INTERNAL; +typedef struct CXPLAT_SOCKET_RAW CXPLAT_SOCKET_RAW; +typedef struct CXPLAT_SOCKET CXPLAT_SOCKET; +typedef struct CXPLAT_SOCKET_BASE CXPLAT_SOCKET_BASE; -typedef struct CXPLAT_SOCKET { - DATAPATH_TYPE DataPathType; -} CXPLAT_SOCKET; + +typedef struct CXPLAT_SOCKET_BASE { + // + // The local address and port. + // + QUIC_ADDR LocalAddress; + + // + // The remote address and port. + // + QUIC_ADDR RemoteAddress; +} CXPLAT_SOCKET_BASE; + +typedef struct CXPLAT_UDP_CONFIG CXPLAT_UDP_CONFIG; // // Can be defined to whatever the client needs. // typedef struct CXPLAT_RECV_PACKET CXPLAT_RECV_PACKET; +typedef enum CXPLAT_BUFFER_FROM { + CXPLAT_BUFFER_FROM_UNKNOWN = 0, + CXPLAT_BUFFER_FROM_USER, + CXPLAT_BUFFER_FROM_KERNEL, + CXPLAT_BUFFER_FROM_XDP, + // DPDK? +} CXPLAT_BUFFER_FROM; + // // Structure that maintains the 'per send' context. // typedef struct CXPLAT_SEND_DATA_INTERNAL CXPLAT_SEND_DATA_INTERNAL; typedef struct CXPLAT_SEND_DATA { - DATAPATH_TYPE DataPathType; + CXPLAT_BUFFER_FROM BufferFrom : 2; } CXPLAT_SEND_DATA; // @@ -254,7 +266,8 @@ typedef struct CXPLAT_RECV_DATA { // uint16_t Allocated : 1; // Used for debugging. Set to FALSE on free. uint16_t QueuedOnConnection : 1; // Used for debugging. - uint16_t Reserved : 6; + CXPLAT_BUFFER_FROM BufferFrom : 2; + uint16_t Reserved : 4; uint16_t ReservedEx : 8; } CXPLAT_RECV_DATA; @@ -809,50 +822,48 @@ typedef struct CXPLAT_DATAPATH_FUNCTIONS { CXPLAT_RECV_DATA* (*CxPlatDataPathRecvPacketToRecvData)(const CXPLAT_RECV_PACKET* const Context); CXPLAT_RECV_PACKET* (*CxPlatDataPathRecvDataToRecvPacket)(const CXPLAT_RECV_DATA* const Datagram); QUIC_STATUS (*CxPlatDataPathInitialize)(_In_ uint32_t ClientRecvContextLength, - _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, - _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, _In_opt_ QUIC_EXECUTION_CONFIG* Config, - _Out_ CXPLAT_DATAPATH_INTERNAL** NewDataPath); - void (*CxPlatDataPathUninitialize)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath); - void (*CxPlatDataPathUpdateConfig)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + _Out_ CXPLAT_DATAPATH* DataPath); + void (*CxPlatDataPathUninitialize)(_In_ CXPLAT_DATAPATH* Datapath); + void (*CxPlatDataPathUpdateConfig)(_In_ CXPLAT_DATAPATH* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config); - uint32_t (*CxPlatDataPathGetSupportedFeatures)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath); - BOOLEAN (*CxPlatDataPathIsPaddingPreferred)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath); - QUIC_STATUS (*CxPlatDataPathGetLocalAddresses)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + uint32_t (*CxPlatDataPathGetSupportedFeatures)(_In_ CXPLAT_DATAPATH* Datapath); + BOOLEAN (*CxPlatDataPathIsPaddingPreferred)(_In_ CXPLAT_DATAPATH* Datapath); + QUIC_STATUS (*CxPlatDataPathGetLocalAddresses)(_In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) CXPLAT_ADAPTER_ADDRESS** Addresses, _Out_ uint32_t* AddressesCount); - QUIC_STATUS (*CxPlatDataPathGetGatewayAddresses)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + QUIC_STATUS (*CxPlatDataPathGetGatewayAddresses)(_In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) QUIC_ADDR** GatewayAddresses, _Out_ uint32_t* GatewayAddressesCount); - QUIC_STATUS (*CxPlatDataPathResolveAddress)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + QUIC_STATUS (*CxPlatDataPathResolveAddress)(_In_ CXPLAT_DATAPATH* Datapath, _In_z_ const char* HostName, _Inout_ QUIC_ADDR* Address); - QUIC_STATUS (*CxPlatSocketCreateUdp)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + QUIC_STATUS (*CxPlatSocketCreateUdp)(_In_ CXPLAT_DATAPATH* Datapath, _In_ const CXPLAT_UDP_CONFIG* Config, - _Out_ CXPLAT_SOCKET** NewSocket); - QUIC_STATUS (*CxPlatSocketCreateTcp)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + _Out_ CXPLAT_SOCKET* Socket); + QUIC_STATUS (*CxPlatSocketCreateTcp)(_In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, _In_opt_ void* CallbackContext, _Out_ CXPLAT_SOCKET** Socket); - QUIC_STATUS (*CxPlatSocketCreateTcpListener)(_In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + QUIC_STATUS (*CxPlatSocketCreateTcpListener)(_In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ void* RecvCallbackContext, _Out_ CXPLAT_SOCKET** NewSocket); - void (*CxPlatSocketDelete)(_In_ CXPLAT_SOCKET_INTERNAL* Socket); - QUIC_STATUS (*CxPlatSocketUpdateQeo)(_In_ CXPLAT_SOCKET_INTERNAL* Socket, + void (*CxPlatSocketDelete)(_In_ CXPLAT_SOCKET* Socket); + QUIC_STATUS (*CxPlatSocketUpdateQeo)(_In_ CXPLAT_SOCKET* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, _In_ uint32_t OffloadCount); - UINT16 (*CxPlatSocketGetLocalMtu)(_In_ CXPLAT_SOCKET_INTERNAL* Socket); - void (*CxPlatSocketGetLocalAddress)(_In_ CXPLAT_SOCKET_INTERNAL* Socket, + UINT16 (*CxPlatSocketGetLocalMtu)(_In_ CXPLAT_SOCKET* Socket); + void (*CxPlatSocketGetLocalAddress)(_In_ CXPLAT_SOCKET* Socket, _Out_ QUIC_ADDR* Address); - void (*CxPlatSocketGetRemoteAddress)(_In_ CXPLAT_SOCKET_INTERNAL* Socket, + void (*CxPlatSocketGetRemoteAddress)(_In_ CXPLAT_SOCKET* Socket, _Out_ QUIC_ADDR* Address); void (*CxPlatRecvDataReturn)(_In_opt_ CXPLAT_RECV_DATA* RecvDataChain); - CXPLAT_SEND_DATA* (*CxPlatSendDataAlloc)(_In_ CXPLAT_SOCKET_INTERNAL* Socket, + CXPLAT_SEND_DATA* (*CxPlatSendDataAlloc)(_In_ CXPLAT_SOCKET* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config); void (*CxPlatSendDataFree)(_In_ CXPLAT_SEND_DATA_INTERNAL* SendData); QUIC_BUFFER* (*CxPlatSendDataAllocBuffer)(_In_ CXPLAT_SEND_DATA_INTERNAL* SendData, @@ -860,13 +871,99 @@ typedef struct CXPLAT_DATAPATH_FUNCTIONS { void (*CxPlatSendDataFreeBuffer)(_In_ CXPLAT_SEND_DATA_INTERNAL* SendData, _In_ QUIC_BUFFER* Buffer); BOOLEAN (*CxPlatSendDataIsFull)(_In_ CXPLAT_SEND_DATA_INTERNAL* SendData); - QUIC_STATUS (*CxPlatSocketSend)(_In_ CXPLAT_SOCKET_INTERNAL* Socket, + QUIC_STATUS (*CxPlatSocketSend)(_In_ CXPLAT_SOCKET* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA_INTERNAL* SendData); void (*CxPlatDataPathProcessCqe)(_In_ CXPLAT_CQE* Cqe); } CXPLAT_DATAPATH_FUNCTIONS; extern const struct CXPLAT_DATAPATH_FUNCTIONS DataPathUserFuncs; +extern const struct CXPLAT_DATAPATH_FUNCTIONS DataPathXdpFuncs; // RawFuncs? + +typedef struct CXPLAT_DATAPATH_BASE { + // + // The UDP callback function pointers. + // + CXPLAT_UDP_DATAPATH_CALLBACKS UdpHandlers; + + // + // The TCP callback function pointers. + // + CXPLAT_TCP_DATAPATH_CALLBACKS TcpHandlers; +} CXPLAT_DATAPATH_BASE; + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatInitRawSocket( + _In_ CXPLAT_DATAPATH_RAW* DataPath, + _In_ const CXPLAT_UDP_CONFIG* Config, + _Out_ CXPLAT_SOCKET_RAW* NewSocket + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatRawSocketDelete( + _In_ CXPLAT_SOCKET_RAW* Socket + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatInitRawDataPath( + _In_ uint32_t ClientRecvContextLength, + _In_opt_ QUIC_EXECUTION_CONFIG* Config, + _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, + _Out_ CXPLAT_DATAPATH_RAW* DataPath + ); + +// TODO: rename as generic for raw + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +XDP_CxPlatDataPathUninitialize( + _In_ CXPLAT_DATAPATH_RAW* Datapath + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +XDP_CxPlatDataPathUpdateConfig( + _In_ CXPLAT_DATAPATH_RAW* Datapath, + _In_ QUIC_EXECUTION_CONFIG* Config + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +uint32_t +XDP_CxPlatDataPathGetSupportedFeatures( + _In_ CXPLAT_DATAPATH_RAW* Datapath + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +XDP_CxPlatSocketUpdateQeo( + _In_ CXPLAT_SOCKET_RAW* Socket, + _In_reads_(OffloadCount) + const CXPLAT_QEO_CONNECTION* Offloads, + _In_ uint32_t OffloadCount + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +UINT16 +XDP_CxPlatSocketGetLocalMtu( + _In_ CXPLAT_SOCKET_RAW* Socket + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +XDP_CxPlatRecvDataReturn( + _In_opt_ CXPLAT_RECV_DATA* RecvDataChain + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +CXPLAT_SEND_DATA* +XDP_CxPlatSendDataAlloc( + _In_ CXPLAT_SOCKET_RAW* Socket, + _Inout_ CXPLAT_SEND_CONFIG* Config + ); #if defined(__cplusplus) } diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index 3e1d9dc1bf..61366cc18e 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -19,8 +19,6 @@ typedef struct CXPLAT_SOCKET_POOL { #define MANGLE(x) XDP_##x -typedef struct CXPLAT_DATAPATH_RAW CXPLAT_DATAPATH_RAW; - // // A worker thread for draining queued route resolution operations. // @@ -46,8 +44,9 @@ typedef struct QUIC_CACHEALIGN CXPLAT_ROUTE_RESOLUTION_WORKER { } CXPLAT_ROUTE_RESOLUTION_WORKER; typedef struct CXPLAT_DATAPATH_RAW { - - CXPLAT_UDP_DATAPATH_CALLBACKS UdpHandlers; + // FIXME: want to inline CXPLAT_DATAPATH + const CXPLAT_DATAPATH *ParentDataPath; + // CXPLAT_UDP_DATAPATH_CALLBACKS UdpHandlers; // common? CXPLAT_SOCKET_POOL SocketPool; @@ -61,6 +60,7 @@ typedef struct CXPLAT_DATAPATH_RAW { #endif BOOLEAN UseTcp; + // CXPLAT_DATAPATH; } CXPLAT_DATAPATH_RAW; #define ETH_MAC_ADDR_LEN 6 @@ -94,16 +94,6 @@ typedef struct CXPLAT_SEND_DATA_INTERNAL { } CXPLAT_SEND_DATA_INTERNAL; -// -// Queries the raw datapath stack for the total size needed to allocate the -// datapath structure. -// -_IRQL_requires_max_(PASSIVE_LEVEL) -size_t -CxPlatDpRawGetDatapathSize( - _In_opt_ const QUIC_EXECUTION_CONFIG* Config - ); - // // Initializes the raw datapath stack. // @@ -150,7 +140,7 @@ CxPlatDpRawUpdateConfig( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDpRawPlumbRulesOnSocket( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ BOOLEAN IsCreated ); @@ -230,7 +220,7 @@ CxPlatDpRawRxFree( _IRQL_requires_max_(DISPATCH_LEVEL) CXPLAT_SEND_DATA* CxPlatDpRawTxAlloc( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ); @@ -256,16 +246,20 @@ CxPlatDpRawTxEnqueue( // Raw Socket Interface // -typedef struct CXPLAT_SOCKET_INTERNAL { - CXPLAT_SOCKET; +// typedef struct CXPLAT_SOCKET_INTERNAL { +// CXPLAT_SOCKET; +// } CXPLAT_SOCKET_INTERNAL; + +typedef struct CXPLAT_SOCKET_RAW { + // const CXPLAT_SOCKET_BASE* ParentSocket; CXPLAT_HASHTABLE_ENTRY Entry; CXPLAT_RUNDOWN_REF Rundown; - CXPLAT_DATAPATH_RAW* Datapath; - SOCKET AuxSocket; + CXPLAT_DATAPATH_RAW* RawDatapath; // + SOCKET AuxSocket; // TODO: remove? void* CallbackContext; - QUIC_ADDR LocalAddress; - QUIC_ADDR RemoteAddress; + // QUIC_ADDR LocalAddress; // + // QUIC_ADDR RemoteAddress; // BOOLEAN Wildcard; // Using a wildcard local address. Optimization // to avoid always reading LocalAddress. BOOLEAN Connected; // Bound to a remote address @@ -277,7 +271,9 @@ typedef struct CXPLAT_SOCKET_INTERNAL { CXPLAT_SEND_DATA_INTERNAL* PausedTcpSend; // Paused TCP send data *before* framing CXPLAT_SEND_DATA_INTERNAL* CachedRstSend; // Cached TCP RST send data *after* framing -} CXPLAT_SOCKET_INTERNAL; + + CXPLAT_SOCKET; // _INTRENAL? +} CXPLAT_SOCKET_RAW; BOOLEAN CxPlatSockPoolInitialize( @@ -297,7 +293,7 @@ CxPlatSockPoolUninitialize( inline BOOLEAN CxPlatSocketCompare( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress ) @@ -319,7 +315,7 @@ CxPlatSocketCompare( // // Finds a socket to deliver received packets with the given addresses. // -CXPLAT_SOCKET_INTERNAL* +CXPLAT_SOCKET_RAW* CxPlatGetSocket( _In_ const CXPLAT_SOCKET_POOL* Pool, _In_ const QUIC_ADDR* LocalAddress, @@ -329,13 +325,13 @@ CxPlatGetSocket( QUIC_STATUS CxPlatTryAddSocket( _In_ CXPLAT_SOCKET_POOL* Pool, - _In_ CXPLAT_SOCKET_INTERNAL* Socket + _In_ CXPLAT_SOCKET_RAW* Socket ); void CxPlatRemoveSocket( _In_ CXPLAT_SOCKET_POOL* Pool, - _In_ CXPLAT_SOCKET_INTERNAL* Socket + _In_ CXPLAT_SOCKET_RAW* Socket ); // @@ -355,28 +351,28 @@ typedef enum PACKET_TYPE { _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawSocketAckSyn( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ CXPLAT_RECV_DATA* Packet ); _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawSocketSyn( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route ); _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawSocketAckFin( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ CXPLAT_RECV_DATA* Packet ); _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatFramingWriteHeaders( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route, _Inout_ QUIC_BUFFER* Buffer, _In_ CXPLAT_ECN_TYPE ECN, diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index 2865ab597b..fbb4082830 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -17,20 +17,37 @@ #pragma warning(disable:4116) // unnamed type definition in parentheses #pragma warning(disable:4100) // unreferenced formal parameter -CXPLAT_SOCKET_INTERNAL* + +uint32_t +CxPlatGetRawSocketSize () { + return sizeof(CXPLAT_SOCKET_RAW); +} + +// TODO: rename +CXPLAT_SOCKET* +CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket) { + return (CXPLAT_SOCKET*)(Socket + 1); +} +// TODO: rename +CXPLAT_SOCKET_RAW* +CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { + return (CXPLAT_SOCKET_RAW*)Socket - 1; +} + +CXPLAT_SOCKET_RAW* CxPlatGetSocket( _In_ const CXPLAT_SOCKET_POOL* Pool, _In_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress ) { - CXPLAT_SOCKET_INTERNAL* Socket = NULL; + CXPLAT_SOCKET_RAW* Socket = NULL; CXPLAT_HASHTABLE_LOOKUP_CONTEXT Context; CXPLAT_HASHTABLE_ENTRY* Entry; CxPlatRwLockAcquireShared(&((CXPLAT_SOCKET_POOL*)Pool)->Lock); Entry = CxPlatHashtableLookup(&Pool->Sockets, LocalAddress->Ipv4.sin_port, &Context); while (Entry != NULL) { - CXPLAT_SOCKET_INTERNAL* Temp = CXPLAT_CONTAINING_RECORD(Entry, CXPLAT_SOCKET_INTERNAL, Entry); + CXPLAT_SOCKET_RAW* Temp = CXPLAT_CONTAINING_RECORD(Entry, CXPLAT_SOCKET_RAW, Entry); if (CxPlatSocketCompare(Temp, LocalAddress, RemoteAddress)) { if (CxPlatRundownAcquire(&Temp->Rundown)) { Socket = Temp; @@ -481,7 +498,7 @@ CxPlatFramingTransportChecksum( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawSocketAckFin( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ CXPLAT_RECV_DATA* Packet ) { @@ -520,7 +537,7 @@ CxPlatDpRawSocketAckFin( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawSocketAckSyn( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ CXPLAT_RECV_DATA* Packet ) { @@ -605,7 +622,7 @@ CxPlatDpRawSocketAckSyn( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawSocketSyn( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route ) { @@ -639,7 +656,7 @@ CxPlatDpRawSocketSyn( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatFramingWriteHeaders( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route, _Inout_ QUIC_BUFFER* Buffer, _In_ CXPLAT_ECN_TYPE ECN, diff --git a/src/platform/datapath_raw_socket_win.c b/src/platform/datapath_raw_socket_win.c index 80806f5568..64ddd14bbe 100644 --- a/src/platform/datapath_raw_socket_win.c +++ b/src/platform/datapath_raw_socket_win.c @@ -56,7 +56,7 @@ CxPlatSockPoolUninitialize( void CxPlatRemoveSocket( _In_ CXPLAT_SOCKET_POOL* Pool, - _In_ CXPLAT_SOCKET_INTERNAL* Socket + _In_ CXPLAT_SOCKET_RAW* Socket ) { CxPlatRwLockAcquireExclusive(&Pool->Lock); @@ -78,13 +78,14 @@ CxPlatRemoveSocket( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatResolveRoute)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET* Sock, _Inout_ CXPLAT_ROUTE* Route, _In_ uint8_t PathId, _In_ void* Context, _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback ) { + CXPLAT_SOCKET_RAW* Socket = (CXPLAT_SOCKET_RAW*)Sock; NETIO_STATUS Status = ERROR_SUCCESS; MIB_IPFORWARD_ROW2 IpforwardRow = {0}; CXPLAT_ROUTE_STATE State = Route->State; @@ -150,8 +151,8 @@ MANGLE(CxPlatResolveRoute)( // // Find the interface that matches the route we just looked up. // - CXPLAT_LIST_ENTRY* Entry = Socket->Datapath->Interfaces.Flink; - for (; Entry != &Socket->Datapath->Interfaces; Entry = Entry->Flink) { + CXPLAT_LIST_ENTRY* Entry = Socket->RawDatapath->Interfaces.Flink; + for (; Entry != &Socket->RawDatapath->Interfaces; Entry = Entry->Flink) { CXPLAT_INTERFACE* Interface = CONTAINING_RECORD(Entry, CXPLAT_INTERFACE, Link); if (Interface->IfIndex == IpforwardRow.InterfaceIndex) { CXPLAT_DBG_ASSERT(sizeof(Interface->PhysicalAddress) == sizeof(Route->LocalLinkLayerAddress)); @@ -207,7 +208,7 @@ MANGLE(CxPlatResolveRoute)( Route->NextHopLinkLayerAddress, IpnetRow.PhysicalAddress, sizeof(Route->NextHopLinkLayerAddress)) == 0)) { - CXPLAT_ROUTE_RESOLUTION_WORKER* Worker = Socket->Datapath->RouteResolutionWorker; + CXPLAT_ROUTE_RESOLUTION_WORKER* Worker = Socket->RawDatapath->RouteResolutionWorker; CXPLAT_ROUTE_RESOLUTION_OPERATION* Operation = CxPlatPoolAlloc(&Worker->OperationPool); if (Operation == NULL) { QuicTraceEvent( @@ -246,302 +247,18 @@ MANGLE(CxPlatResolveRoute)( QUIC_STATUS CxPlatTryAddSocket( _In_ CXPLAT_SOCKET_POOL* Pool, - _In_ CXPLAT_SOCKET_INTERNAL* Socket + _In_ CXPLAT_SOCKET_RAW* Socket ) { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - int Result; CXPLAT_HASHTABLE_LOOKUP_CONTEXT Context; CXPLAT_HASHTABLE_ENTRY* Entry; - QUIC_ADDR MappedAddress = {0}; - SOCKET TempUdpSocket = INVALID_SOCKET; - int AssignedLocalAddressLength; - - // - // Get (and reserve) a transport layer port from the OS networking stack by - // binding an auxiliary (dual stack) socket. - // - - Socket->AuxSocket = - socket( - AF_INET6, - Socket->UseTcp ? SOCK_STREAM : SOCK_DGRAM, - Socket->UseTcp ? IPPROTO_TCP : IPPROTO_UDP); - if (Socket->AuxSocket == INVALID_SOCKET) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "socket"); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - - int Option = FALSE; - Result = - setsockopt( - Socket->AuxSocket, - IPPROTO_IPV6, - IPV6_V6ONLY, - (char*)&Option, - sizeof(Option)); - if (Result == SOCKET_ERROR) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "Set IPV6_V6ONLY"); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - - if (Socket->CibirIdLength) { - Option = TRUE; - Result = - setsockopt( - Socket->AuxSocket, - SOL_SOCKET, - SO_REUSEADDR, - (char*)&Option, - sizeof(Option)); - if (Result == SOCKET_ERROR) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "Set SO_REUSEADDR"); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - } - - CxPlatConvertToMappedV6(&Socket->LocalAddress, &MappedAddress); -#if QUIC_ADDRESS_FAMILY_INET6 != AF_INET6 - if (MappedAddress.Ipv6.sin6_family == QUIC_ADDRESS_FAMILY_INET6) { - MappedAddress.Ipv6.sin6_family = AF_INET6; - } -#endif CxPlatRwLockAcquireExclusive(&Pool->Lock); - Result = - bind( - Socket->AuxSocket, - (struct sockaddr*)&MappedAddress, - sizeof(MappedAddress)); - if (Result == SOCKET_ERROR) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "bind"); - CxPlatRwLockReleaseExclusive(&Pool->Lock); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - - if (Socket->Connected) { - CxPlatZeroMemory(&MappedAddress, sizeof(MappedAddress)); - CxPlatConvertToMappedV6(&Socket->RemoteAddress, &MappedAddress); - -#if QUIC_ADDRESS_FAMILY_INET6 != AF_INET6 - if (MappedAddress.Ipv6.sin6_family == QUIC_ADDRESS_FAMILY_INET6) { - MappedAddress.Ipv6.sin6_family = AF_INET6; - } -#endif - if (Socket->UseTcp) { - // - // Create a temporary UDP socket bound to a wildcard port - // and connect this socket to the remote address. - // By doing this, the OS will select a local address for us. - // - uint16_t LocalPortChosen = 0; - QUIC_ADDR TempLocalAddress = {0}; - AssignedLocalAddressLength = sizeof(TempLocalAddress); - Result = - getsockname( - Socket->AuxSocket, - (struct sockaddr*)&TempLocalAddress, - &AssignedLocalAddressLength); - if (Result == SOCKET_ERROR) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "getsockname"); - CxPlatRwLockReleaseExclusive(&Pool->Lock); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - LocalPortChosen = TempLocalAddress.Ipv4.sin_port; - TempUdpSocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); - if (TempUdpSocket == INVALID_SOCKET) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "temp udp socket"); - CxPlatRwLockReleaseExclusive(&Pool->Lock); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - - Option = FALSE; - Result = - setsockopt( - TempUdpSocket, - IPPROTO_IPV6, - IPV6_V6ONLY, - (char*)&Option, - sizeof(Option)); - if (Result == SOCKET_ERROR) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "Set IPV6_V6ONLY (temp udp socket)"); - CxPlatRwLockReleaseExclusive(&Pool->Lock); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - - CxPlatZeroMemory(&TempLocalAddress, sizeof(TempLocalAddress)); - CxPlatConvertToMappedV6(&Socket->LocalAddress, &TempLocalAddress); - TempLocalAddress.Ipv4.sin_port = 0; - Result = - bind( - TempUdpSocket, - (struct sockaddr*)&TempLocalAddress, - sizeof(TempLocalAddress)); - if (Result == SOCKET_ERROR) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "bind (temp udp socket)"); - CxPlatRwLockReleaseExclusive(&Pool->Lock); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - - Result = - connect( - TempUdpSocket, - (struct sockaddr*)&MappedAddress, - sizeof(MappedAddress)); - if (Result == SOCKET_ERROR) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "connect failed (temp udp socket)"); - CxPlatRwLockReleaseExclusive(&Pool->Lock); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - - AssignedLocalAddressLength = sizeof(Socket->LocalAddress); - Result = - getsockname( - TempUdpSocket, - (struct sockaddr*)&Socket->LocalAddress, - &AssignedLocalAddressLength); - if (Result == SOCKET_ERROR) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "getsockname (temp udp socket)"); - CxPlatRwLockReleaseExclusive(&Pool->Lock); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - CxPlatConvertFromMappedV6(&Socket->LocalAddress, &Socket->LocalAddress); - Socket->LocalAddress.Ipv4.sin_port = LocalPortChosen; - CXPLAT_FRE_ASSERT(Socket->LocalAddress.Ipv4.sin_port != 0); - } else { - Result = - connect( - Socket->AuxSocket, - (struct sockaddr*)&MappedAddress, - sizeof(MappedAddress)); - if (Result == SOCKET_ERROR) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "connect failed"); - CxPlatRwLockReleaseExclusive(&Pool->Lock); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - AssignedLocalAddressLength = sizeof(Socket->LocalAddress); - Result = - getsockname( - Socket->AuxSocket, - (struct sockaddr*)&Socket->LocalAddress, - &AssignedLocalAddressLength); - if (Result == SOCKET_ERROR) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "getsockname"); - CxPlatRwLockReleaseExclusive(&Pool->Lock); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - CxPlatConvertFromMappedV6(&Socket->LocalAddress, &Socket->LocalAddress); - } - } else { - AssignedLocalAddressLength = sizeof(Socket->LocalAddress); - Result = - getsockname( - Socket->AuxSocket, - (struct sockaddr*)&Socket->LocalAddress, - &AssignedLocalAddressLength); - if (Result == SOCKET_ERROR) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "getsockname"); - CxPlatRwLockReleaseExclusive(&Pool->Lock); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - CxPlatConvertFromMappedV6(&Socket->LocalAddress, &Socket->LocalAddress); - } - Entry = CxPlatHashtableLookup(&Pool->Sockets, Socket->LocalAddress.Ipv4.sin_port, &Context); while (Entry != NULL) { - CXPLAT_SOCKET_INTERNAL* Temp = CXPLAT_CONTAINING_RECORD(Entry, CXPLAT_SOCKET_INTERNAL, Entry); + CXPLAT_SOCKET_RAW* Temp = CXPLAT_CONTAINING_RECORD(Entry, CXPLAT_SOCKET_RAW, Entry); if (CxPlatSocketCompare(Temp, &Socket->LocalAddress, &Socket->RemoteAddress)) { Status = QUIC_STATUS_ADDRESS_IN_USE; break; @@ -554,15 +271,5 @@ CxPlatTryAddSocket( CxPlatRwLockReleaseExclusive(&Pool->Lock); -Error: - - if (QUIC_FAILED(Status) && Socket->AuxSocket != INVALID_SOCKET) { - closesocket(Socket->AuxSocket); - } - - if (TempUdpSocket != INVALID_SOCKET) { - closesocket(TempUdpSocket); - } - return Status; } diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index caef50eea5..7766366821 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -102,53 +102,27 @@ CxPlatDataPathRouteWorkerInitialize( return Status; } + _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -MANGLE(CxPlatDataPathInitialize)( +CxPlatInitRawDataPath( _In_ uint32_t ClientRecvContextLength, - _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, - _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, _In_opt_ QUIC_EXECUTION_CONFIG* Config, - _Out_ CXPLAT_DATAPATH_RAW** NewDataPath + _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, + _Out_ CXPLAT_DATAPATH_RAW* DataPath ) { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - const size_t DatapathSize = CxPlatDpRawGetDatapathSize(Config); + // const size_t DatapathSize = CxPlatDpRawGetDatapathSize(Config); BOOLEAN DpRawInitialized = FALSE; BOOLEAN SockPoolInitialized = FALSE; - CXPLAT_FRE_ASSERT(DatapathSize > sizeof(CXPLAT_DATAPATH)); - - UNREFERENCED_PARAMETER(TcpCallbacks); - - if (NewDataPath == NULL) { - return QUIC_STATUS_INVALID_PARAMETER; - } - if (UdpCallbacks != NULL) { - if (UdpCallbacks->Receive == NULL || UdpCallbacks->Unreachable == NULL) { - return QUIC_STATUS_INVALID_PARAMETER; - } - } + // CXPLAT_FRE_ASSERT(DatapathSize > sizeof(CXPLAT_DATAPATH)); - if (!CxPlatWorkersLazyStart(Config)) { - return QUIC_STATUS_OUT_OF_MEMORY; - } - - CXPLAT_DATAPATH_RAW* DataPath = CXPLAT_ALLOC_PAGED(DatapathSize, QUIC_POOL_DATAPATH); if (DataPath == NULL) { - QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_DATAPATH", - DatapathSize); - return QUIC_STATUS_OUT_OF_MEMORY; + return QUIC_STATUS_INVALID_PARAMETER; } - CxPlatZeroMemory(DataPath, DatapathSize); CXPLAT_FRE_ASSERT(CxPlatRundownAcquire(&CxPlatWorkerRundown)); - if (UdpCallbacks) { - DataPath->UdpHandlers = *UdpCallbacks; - } - if (Config && (Config->Flags & QUIC_EXECUTION_CONFIG_FLAG_QTIP)) { DataPath->UseTcp = TRUE; } @@ -170,25 +144,24 @@ MANGLE(CxPlatDataPathInitialize)( goto Error; } - *NewDataPath = DataPath; - DataPath = NULL; + DataPath->ParentDataPath = ParentDataPath; Error: - if (DataPath != NULL) { -#if DEBUG - DataPath->Uninitialized = TRUE; -#endif - if (DpRawInitialized) { - CxPlatDpRawUninitialize(DataPath); - } else { - if (SockPoolInitialized) { - CxPlatSockPoolUninitialize(&DataPath->SocketPool); - } - CXPLAT_FREE(DataPath, QUIC_POOL_DATAPATH); - CxPlatRundownRelease(&CxPlatWorkerRundown); - } - } +// if (DataPath != NULL) { +// #if DEBUG +// DataPath->Uninitialized = TRUE; +// #endif +// if (DpRawInitialized) { +// CxPlatDpRawUninitialize(DataPath); +// } else { +// if (SockPoolInitialized) { +// CxPlatSockPoolUninitialize(&DataPath->SocketPool); +// } +// CXPLAT_FREE(DataPath, QUIC_POOL_DATAPATH); +// CxPlatRundownRelease(&CxPlatWorkerRundown); +// } +// } return Status; } @@ -248,7 +221,7 @@ MANGLE(CxPlatDataPathGetSupportedFeatures)( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN MANGLE(CxPlatDataPathIsPaddingPreferred)( - _In_ CXPLAT_DATAPATH_RAW* Datapath + _In_ CXPLAT_DATAPATH* Datapath ) { return FALSE; @@ -258,7 +231,7 @@ _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS MANGLE(CxPlatDataPathGetLocalAddresses)( - _In_ CXPLAT_DATAPATH_RAW* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) CXPLAT_ADAPTER_ADDRESS** Addresses, _Out_ uint32_t* AddressesCount @@ -271,7 +244,7 @@ _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS MANGLE(CxPlatDataPathGetGatewayAddresses)( - _In_ CXPLAT_DATAPATH_RAW* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) QUIC_ADDR** GatewayAddresses, _Out_ uint32_t* GatewayAddressesCount @@ -280,190 +253,102 @@ MANGLE(CxPlatDataPathGetGatewayAddresses)( return QUIC_STATUS_NOT_SUPPORTED; } -void -CxPlatDataPathPopulateTargetAddress( - _In_ ADDRESS_FAMILY Family, - _In_ ADDRINFOW *Ai, - _Out_ SOCKADDR_INET* Address - ) -{ - if (Ai->ai_addr->sa_family == QUIC_ADDRESS_FAMILY_INET6) { - // - // Is this a mapped ipv4 one? - // - PSOCKADDR_IN6 SockAddr6 = (PSOCKADDR_IN6)Ai->ai_addr; - - if (Family == QUIC_ADDRESS_FAMILY_UNSPEC && IN6ADDR_ISV4MAPPED(SockAddr6)) - { - PSOCKADDR_IN SockAddr4 = &Address->Ipv4; - // - // Get the ipv4 address from the mapped address. - // - SockAddr4->sin_family = QUIC_ADDRESS_FAMILY_INET; - SockAddr4->sin_addr = - *(IN_ADDR UNALIGNED *) - IN6_GET_ADDR_V4MAPPED(&SockAddr6->sin6_addr); - SockAddr4->sin_port = SockAddr6->sin6_port; - return; - } - } - - CxPlatCopyMemory(Address, Ai->ai_addr, Ai->ai_addrlen); -} +// void +// CxPlatDataPathPopulateTargetAddress( +// _In_ ADDRESS_FAMILY Family, +// _In_ ADDRINFOW *Ai, +// _Out_ SOCKADDR_INET* Address +// ) +// { +// if (Ai->ai_addr->sa_family == QUIC_ADDRESS_FAMILY_INET6) { +// // +// // Is this a mapped ipv4 one? +// // +// PSOCKADDR_IN6 SockAddr6 = (PSOCKADDR_IN6)Ai->ai_addr; + +// if (Family == QUIC_ADDRESS_FAMILY_UNSPEC && IN6ADDR_ISV4MAPPED(SockAddr6)) +// { +// PSOCKADDR_IN SockAddr4 = &Address->Ipv4; +// // +// // Get the ipv4 address from the mapped address. +// // +// SockAddr4->sin_family = QUIC_ADDRESS_FAMILY_INET; +// SockAddr4->sin_addr = +// *(IN_ADDR UNALIGNED *) +// IN6_GET_ADDR_V4MAPPED(&SockAddr6->sin6_addr); +// SockAddr4->sin_port = SockAddr6->sin6_port; +// return; +// } +// } + +// CxPlatCopyMemory(Address, Ai->ai_addr, Ai->ai_addrlen); +// } _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -MANGLE(CxPlatDataPathResolveAddress)( - _In_ CXPLAT_DATAPATH_RAW* Datapath, - _In_z_ const char* HostName, - _Inout_ QUIC_ADDR* Address - ) -{ - QUIC_STATUS Status; - PWSTR HostNameW = NULL; - ADDRINFOW Hints = { 0 }; - ADDRINFOW *Ai; - - Status = - CxPlatUtf8ToWideChar( - HostName, - QUIC_POOL_PLATFORM_TMP_ALLOC, - &HostNameW); - if (QUIC_FAILED(Status)) { - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - Status, - "Convert HostName to unicode"); - goto Exit; - } - - // - // Prepopulate hint with input family. It might be unspecified. - // - Hints.ai_family = Address->si_family; - - // - // Try numeric name first. - // - Hints.ai_flags = AI_NUMERICHOST; - if (GetAddrInfoW(HostNameW, NULL, &Hints, &Ai) == 0) { - CxPlatDataPathPopulateTargetAddress((ADDRESS_FAMILY)Hints.ai_family, Ai, Address); - FreeAddrInfoW(Ai); - Status = QUIC_STATUS_SUCCESS; - goto Exit; - } - - // - // Try canonical host name. - // - Hints.ai_flags = AI_CANONNAME; - if (GetAddrInfoW(HostNameW, NULL, &Hints, &Ai) == 0) { - CxPlatDataPathPopulateTargetAddress((ADDRESS_FAMILY)Hints.ai_family, Ai, Address); - FreeAddrInfoW(Ai); - Status = QUIC_STATUS_SUCCESS; - goto Exit; - } - - QuicTraceEvent( - LibraryError, - "[ lib] ERROR, %s.", - "Resolving hostname to IP"); - QuicTraceLogError( - DatapathResolveHostNameFailed, - "[%p] Couldn't resolve hostname '%s' to an IP address", - Datapath, - HostName); - Status = HRESULT_FROM_WIN32(WSAHOST_NOT_FOUND); - -Exit: - - if (HostNameW != NULL) { - CXPLAT_FREE(HostNameW, QUIC_POOL_PLATFORM_TMP_ALLOC); - } - - return Status; -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -MANGLE(CxPlatSocketCreateUdp)( - _In_ CXPLAT_DATAPATH_RAW* Datapath, +CxPlatInitRawSocket( + _In_ CXPLAT_DATAPATH_RAW* Raw, _In_ const CXPLAT_UDP_CONFIG* Config, - _Out_ CXPLAT_SOCKET_INTERNAL** NewSocket + // _Out_ CXPLAT_SOCKET_BASE** NewSocket + _Out_ CXPLAT_SOCKET_RAW* Socket ) { + // TODO: remove Socket->Remote/LocalAddress. Use (*NewSocket)->Remote/LocalAddress instead. + #pragma warning(push) + #pragma warning(disable:6001) // Using uninitialized memory + CXPLAT_DBG_ASSERT(Socket != NULL); + #pragma warning(pop) QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - *NewSocket = CXPLAT_ALLOC_PAGED(sizeof(CXPLAT_SOCKET), QUIC_POOL_SOCKET); - if (*NewSocket == NULL) { - QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_SOCKET", - sizeof(CXPLAT_SOCKET)); - Status = QUIC_STATUS_OUT_OF_MEMORY; - goto Error; - } - - QuicTraceEvent( - DatapathCreated, - "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", - *NewSocket, - CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress), - CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress)); - - CxPlatZeroMemory(*NewSocket, sizeof(CXPLAT_SOCKET)); - CxPlatRundownInitialize(&(*NewSocket)->Rundown); - (*NewSocket)->Datapath = Datapath; - (*NewSocket)->CallbackContext = Config->CallbackContext; - (*NewSocket)->CibirIdLength = Config->CibirIdLength; - (*NewSocket)->CibirIdOffsetSrc = Config->CibirIdOffsetSrc; - (*NewSocket)->CibirIdOffsetDst = Config->CibirIdOffsetDst; - (*NewSocket)->UseTcp = Datapath->UseTcp; + // CxPlatZeroMemory(Socket, sizeof(CXPLAT_SOCKET_RAW)); + CxPlatRundownInitialize(&Socket->Rundown); + Socket->RawDatapath = Raw; + Socket->CallbackContext = Config->CallbackContext; + Socket->CibirIdLength = Config->CibirIdLength; + Socket->CibirIdOffsetSrc = Config->CibirIdOffsetSrc; + Socket->CibirIdOffsetDst = Config->CibirIdOffsetDst; + Socket->UseTcp = Raw->UseTcp; if (Config->CibirIdLength) { - memcpy((*NewSocket)->CibirId, Config->CibirId, Config->CibirIdLength); + memcpy(Socket->CibirId, Config->CibirId, Config->CibirIdLength); } + // TODO: remove Socket address settings if (Config->RemoteAddress) { CXPLAT_FRE_ASSERT(!QuicAddrIsWildCard(Config->RemoteAddress)); // No wildcard remote addresses allowed. - (*NewSocket)->Connected = TRUE; - (*NewSocket)->RemoteAddress = *Config->RemoteAddress; + Socket->Connected = TRUE; } if (Config->LocalAddress) { - (*NewSocket)->LocalAddress = *Config->LocalAddress; if (QuicAddrIsWildCard(Config->LocalAddress)) { - if (!(*NewSocket)->Connected) { - (*NewSocket)->Wildcard = TRUE; + if (!Socket->Connected) { + Socket->Wildcard = TRUE; } } else { - CXPLAT_FRE_ASSERT((*NewSocket)->Connected); // Assumes only connected sockets fully specify local address + CXPLAT_FRE_ASSERT(Socket->Connected); // Assumes only connected sockets fully specify local address } } else { - QuicAddrSetFamily(&(*NewSocket)->LocalAddress, QUIC_ADDRESS_FAMILY_INET6); - if (!(*NewSocket)->Connected) { - (*NewSocket)->Wildcard = TRUE; + if (!Socket->Connected) { + Socket->Wildcard = TRUE; } } - CXPLAT_FRE_ASSERT((*NewSocket)->Wildcard ^ (*NewSocket)->Connected); // Assumes either a pure wildcard listener or a + CXPLAT_FRE_ASSERT(Socket->Wildcard ^ Socket->Connected); // Assumes either a pure wildcard listener or a // connected socket; not both. - Status = CxPlatTryAddSocket(&Datapath->SocketPool, *NewSocket); + Status = CxPlatTryAddSocket(&Raw->SocketPool, Socket); if (QUIC_FAILED(Status)) { goto Error; } - CxPlatDpRawPlumbRulesOnSocket(*NewSocket, TRUE); + CxPlatDpRawPlumbRulesOnSocket(Socket, TRUE); Error: if (QUIC_FAILED(Status)) { - if (*NewSocket != NULL) { - CxPlatRundownUninitialize(&(*NewSocket)->Rundown); - CXPLAT_FREE(*NewSocket, QUIC_POOL_SOCKET); - *NewSocket = NULL; + if (Socket != NULL) { + CxPlatRundownUninitialize(&Socket->Rundown); + CXPLAT_FREE(Socket, QUIC_POOL_SOCKET); + Socket = NULL; } } @@ -473,11 +358,11 @@ MANGLE(CxPlatSocketCreateUdp)( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketCreateTcp)( - _In_ CXPLAT_DATAPATH_RAW* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, _In_opt_ void* CallbackContext, - _Out_ CXPLAT_SOCKET_INTERNAL** Socket + _Out_ CXPLAT_SOCKET_RAW** Socket ) { return QUIC_STATUS_NOT_SUPPORTED; @@ -486,23 +371,43 @@ MANGLE(CxPlatSocketCreateTcp)( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketCreateTcpListener)( - _In_ CXPLAT_DATAPATH_RAW* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ void* RecvCallbackContext, - _Out_ CXPLAT_SOCKET_INTERNAL** NewSocket + _Out_ CXPLAT_SOCKET_RAW** NewSocket ) { return QUIC_STATUS_NOT_SUPPORTED; } +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatRawSocketDelete( + _In_ CXPLAT_SOCKET_RAW* Socket + ) +{ + CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); + CxPlatRemoveSocket(&Socket->RawDatapath->SocketPool, Socket); + CxPlatRundownReleaseAndWait(&Socket->Rundown); + if (Socket->PausedTcpSend) { + CxPlatDpRawTxFree(Socket->PausedTcpSend); + } + + if (Socket->CachedRstSend) { + CxPlatDpRawTxEnqueue(Socket->CachedRstSend); + } + + CXPLAT_FREE(Socket, QUIC_POOL_SOCKET); +} + _IRQL_requires_max_(PASSIVE_LEVEL) void MANGLE(CxPlatSocketDelete)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket + _In_ CXPLAT_SOCKET_RAW* Socket ) { CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); - CxPlatRemoveSocket(&Socket->Datapath->SocketPool, Socket); + CxPlatRemoveSocket(&Socket->RawDatapath->SocketPool, Socket); CxPlatRundownReleaseAndWait(&Socket->Rundown); if (Socket->PausedTcpSend) { CxPlatDpRawTxFree(Socket->PausedTcpSend); @@ -518,7 +423,7 @@ MANGLE(CxPlatSocketDelete)( _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 MANGLE(CxPlatSocketGetLocalMtu)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket + _In_ CXPLAT_SOCKET_RAW* Socket ) { if (Socket->UseTcp) { @@ -528,26 +433,6 @@ MANGLE(CxPlatSocketGetLocalMtu)( } } -_IRQL_requires_max_(DISPATCH_LEVEL) -void -MANGLE(CxPlatSocketGetLocalAddress)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, - _Out_ QUIC_ADDR* Address - ) -{ - *Address = Socket->LocalAddress; -} - -_IRQL_requires_max_(DISPATCH_LEVEL) -void -MANGLE(CxPlatSocketGetRemoteAddress)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, - _Out_ QUIC_ADDR* Address - ) -{ - *Address = Socket->RemoteAddress; -} - _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawRxEthernet( @@ -558,7 +443,7 @@ CxPlatDpRawRxEthernet( ) { for (uint16_t i = 0; i < PacketCount; i++) { - CXPLAT_SOCKET_INTERNAL* Socket = NULL; + CXPLAT_SOCKET_RAW* Socket = NULL; CXPLAT_RECV_DATA* PacketChain = Packets[i]; CXPLAT_DBG_ASSERT(PacketChain->Next == NULL); @@ -596,7 +481,7 @@ CxPlatDpRawRxEthernet( CXPLAT_DBG_ASSERT(Packets[i+1]->Next == NULL); i++; } - Datapath->UdpHandlers.Receive((CXPLAT_SOCKET*)Socket, Socket->CallbackContext, (CXPLAT_RECV_DATA*)PacketChain); + Datapath->ParentDataPath->UdpHandlers.Receive((CXPLAT_SOCKET*)Socket, Socket->CallbackContext, (CXPLAT_RECV_DATA*)PacketChain); } else if (PacketChain->Reserved == L4_TYPE_TCP_SYN || PacketChain->Reserved == L4_TYPE_TCP_SYNACK) { CxPlatDpRawSocketAckSyn(Socket, PacketChain); CxPlatDpRawRxFree(PacketChain); @@ -627,7 +512,7 @@ _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* MANGLE(CxPlatSendDataAlloc)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) { @@ -679,7 +564,7 @@ MANGLE(CxPlatSendDataIsFull)( _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketSend)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) @@ -784,3 +669,27 @@ CXPLAT_THREAD_CALLBACK(CxPlatRouteResolutionWorkerThread, Context) return 0; } + +CXPLAT_RECV_DATA* +MANGLE(CxPlatDataPathRecvPacketToRecvData)( + _In_ const CXPLAT_RECV_PACKET* const RecvPacket + ); + +CXPLAT_RECV_PACKET* +MANGLE(CxPlatDataPathRecvDataToRecvPacket)( + _In_ const CXPLAT_RECV_DATA* const RecvData + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +MANGLE(CxPlatSocketUpdateQeo)( + _In_ CXPLAT_SOCKET_RAW* Socket, + _In_reads_(OffloadCount) + const CXPLAT_QEO_CONNECTION* Offloads, + _In_ uint32_t OffloadCount + ); + +void +MANGLE(CxPlatDataPathProcessCqe)( + _In_ CXPLAT_CQE* Cqe + ); diff --git a/src/platform/datapath_raw_xdp.h b/src/platform/datapath_raw_xdp.h index c80c83e3ba..a749126a0c 100644 --- a/src/platform/datapath_raw_xdp.h +++ b/src/platform/datapath_raw_xdp.h @@ -28,19 +28,19 @@ typedef struct XDP_QUEUE XDP_QUEUE; // // Type of IO. // -typedef enum DATAPATH_IO_TYPE { - DATAPATH_IO_SIGNATURE = 'XDPD', - DATAPATH_IO_RECV = DATAPATH_IO_SIGNATURE + 1, - DATAPATH_IO_SEND = DATAPATH_IO_SIGNATURE + 2 -} DATAPATH_IO_TYPE; +typedef enum DATAPATH_XDP_IO_TYPE { + DATAPATH_XDP_IO_SIGNATURE = 'XDPD', + DATAPATH_XDP_IO_RECV = DATAPATH_XDP_IO_SIGNATURE + 1, + DATAPATH_XDP_IO_SEND = DATAPATH_XDP_IO_SIGNATURE + 2 +} DATAPATH_XDP_IO_TYPE; // // IO header for SQE->CQE based completions. // -typedef struct DATAPATH_IO_SQE { - DATAPATH_IO_TYPE IoType; +typedef struct DATAPATH_XDP_IO_SQE { + DATAPATH_XDP_IO_TYPE IoType; DATAPATH_SQE DatapathSqe; -} DATAPATH_IO_SQE; +} DATAPATH_XDP_IO_SQE; typedef struct QUIC_CACHEALIGN XDP_WORKER { CXPLAT_EXECUTION_CONTEXT Ec; diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 75f556bdc9..7b233e5363 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -30,7 +30,7 @@ typedef struct XDP_DATAPATH { // Currently, all XDP interfaces share the same config. // CXPLAT_REF_COUNT RefCount; - uint32_t WorkerCount; + uint32_t WorkerCount; // TODO: merge to ProcessorCount uint32_t RxBufferCount; uint32_t RxRingSize; uint32_t TxBufferCount; @@ -63,13 +63,13 @@ typedef struct XDP_QUEUE { struct XDP_QUEUE* Next; uint8_t* RxBuffers; HANDLE RxXsk; - DATAPATH_IO_SQE RxIoSqe; + DATAPATH_XDP_IO_SQE RxIoSqe; XSK_RING RxFillRing; XSK_RING RxRing; HANDLE RxProgram; uint8_t* TxBuffers; HANDLE TxXsk; - DATAPATH_IO_SQE TxIoSqe; + DATAPATH_XDP_IO_SQE TxIoSqe; XSK_RING TxRing; XSK_RING TxCompletionRing; BOOLEAN RxQueued; @@ -561,9 +561,9 @@ CxPlatDpRawInterfaceInitialize( CxPlatListInitializeHead(&Queue->TxQueue); CxPlatListInitializeHead(&Queue->WorkerTxQueue); CxPlatDatapathSqeInitialize(&Queue->RxIoSqe.DatapathSqe, CXPLAT_CQE_TYPE_SOCKET_IO); - Queue->RxIoSqe.IoType = DATAPATH_IO_RECV; + Queue->RxIoSqe.IoType = DATAPATH_XDP_IO_RECV; CxPlatDatapathSqeInitialize(&Queue->TxIoSqe.DatapathSqe, CXPLAT_CQE_TYPE_SOCKET_IO); - Queue->TxIoSqe.IoType = DATAPATH_IO_SEND; + Queue->TxIoSqe.IoType = DATAPATH_XDP_IO_SEND; // // RX datapath. @@ -1008,6 +1008,7 @@ CxPlatDpRawGetDatapathSize( return sizeof(XDP_DATAPATH) + (WorkerCount * sizeof(XDP_WORKER)); } + _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatDpRawInitialize( @@ -1303,7 +1304,7 @@ CxPlatDpRawUpdateConfig( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketUpdateQeo)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, _In_ uint32_t OffloadCount @@ -1397,11 +1398,12 @@ CxPlatDpRawClearPortBit( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDpRawPlumbRulesOnSocket( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Sock, _In_ BOOLEAN IsCreated ) { - XDP_DATAPATH* Xdp = (XDP_DATAPATH*)Socket->Datapath; + CXPLAT_SOCKET_RAW* Socket = (CXPLAT_SOCKET_RAW*)Sock; + XDP_DATAPATH* Xdp = (XDP_DATAPATH*)Socket->RawDatapath; if (Socket->Wildcard) { XDP_RULE Rules[3] = {0}; uint8_t RulesSize = 0; @@ -1597,6 +1599,7 @@ CxPlatXdpRx( if (Packet->Buffer) { Packet->Allocated = TRUE; Packet->Queue = Queue; + Packet->BufferFrom = CXPLAT_BUFFER_FROM_XDP; Buffers[PacketCount++] = (CXPLAT_RECV_DATA*)Packet; } else { CxPlatListPushEntry(&Queue->WorkerRxPool, (CXPLAT_SLIST_ENTRY*)Packet); @@ -1688,7 +1691,7 @@ CxPlatDpRawRxFree( _IRQL_requires_max_(DISPATCH_LEVEL) CXPLAT_SEND_DATA* CxPlatDpRawTxAlloc( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) { @@ -1921,8 +1924,8 @@ MANGLE(CxPlatDataPathProcessCqe)( ) { if (CxPlatCqeType(Cqe) == CXPLAT_CQE_TYPE_SOCKET_IO) { - DATAPATH_IO_SQE* Sqe = - CONTAINING_RECORD(CxPlatCqeUserData(Cqe), DATAPATH_IO_SQE, DatapathSqe); + DATAPATH_XDP_IO_SQE* Sqe = + CONTAINING_RECORD(CxPlatCqeUserData(Cqe), DATAPATH_XDP_IO_SQE, DatapathSqe); XDP_QUEUE* Queue; if (Sqe->IoType == DATAPATH_IO_RECV) { diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 091945f016..2414535a44 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -52,28 +52,95 @@ CxPlatDataPathInitialize( _Out_ CXPLAT_DATAPATH** NewDataPath ) { + // allocate for CXPLAT_DATAPATH as Datapath + // call CxPlatDataPathInitialize with Datapath + // allocate for CXPLAT_DATAPATH_RAW as RawDatapath + // RawDatapath->ParentDatapath = Datapath + // call CxPlatInitRawDataPath with RawDatapath + // Init all Datapath // QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - - CXPLAT_DATAPATH* DataPath = (CXPLAT_DATAPATH*)CXPLAT_ALLOC_PAGED(sizeof(CXPLAT_DATAPATH), QUIC_POOL_DATAPATH); + if (UdpCallbacks != NULL) { + if (UdpCallbacks->Receive == NULL || UdpCallbacks->Unreachable == NULL) { + Status = QUIC_STATUS_INVALID_PARAMETER; + goto Error; + } + } + if (TcpCallbacks != NULL) { + if (TcpCallbacks->Accept == NULL || + TcpCallbacks->Connect == NULL || + TcpCallbacks->Receive == NULL || + TcpCallbacks->SendComplete == NULL) { + Status = QUIC_STATUS_INVALID_PARAMETER; + goto Error; + } + } + if (!CxPlatWorkersLazyStart(Config)) { + Status = QUIC_STATUS_OUT_OF_MEMORY; + goto Error; + } + uint32_t ProcessorCount; + if (Config && Config->ProcessorCount) { + ProcessorCount = Config->ProcessorCount; + } else { + ProcessorCount = CxPlatProcMaxCount(); + } + uint32_t DatapathLength = + sizeof(CXPLAT_DATAPATH) + + ProcessorCount * sizeof(CXPLAT_DATAPATH_PROC); + CXPLAT_DATAPATH* DataPath = (CXPLAT_DATAPATH*)CXPLAT_ALLOC_PAGED(DatapathLength, QUIC_POOL_DATAPATH); if (DataPath == NULL) { + QuicTraceEvent( + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "CXPLAT_DATAPATH", + DatapathLength); Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; } - CxPlatZeroMemory(DataPath, sizeof(CXPLAT_DATAPATH)); + CxPlatZeroMemory(DataPath, DatapathLength); + if (UdpCallbacks) { + DataPath->UdpHandlers = *UdpCallbacks; + } + if (TcpCallbacks) { + DataPath->TcpHandlers = *TcpCallbacks; + } + DataPath->ProcCount = (uint16_t)ProcessorCount; Status = DataPathUserFuncs.CxPlatDataPathInitialize( ClientRecvContextLength, - UdpCallbacks, - TcpCallbacks, Config, - &DataPath->User); + DataPath); + if (QUIC_FAILED(Status)) { + goto Error; + } - // TODO: xdp + const size_t RawDatapathSize = CxPlatDpRawGetDatapathSize(Config); + CXPLAT_DATAPATH_RAW* RawDataPath = CXPLAT_ALLOC_PAGED(RawDatapathSize, QUIC_POOL_DATAPATH); + if (RawDataPath == NULL) { + QuicTraceEvent( + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "CXPLAT_DATAPATH", + RawDatapathSize); + return QUIC_STATUS_OUT_OF_MEMORY; + } + CxPlatZeroMemory(RawDataPath, RawDatapathSize); + Status = CxPlatInitRawDataPath( + ClientRecvContextLength, + Config, + DataPath, + RawDataPath); + if (QUIC_FAILED(Status)) { + goto Error; + } + + DataPath->RawDataPath = RawDataPath; *NewDataPath = DataPath; Error: + // TODO: error handling return Status; } @@ -84,11 +151,15 @@ CxPlatDataPathUninitialize( _In_ CXPLAT_DATAPATH* Datapath ) { - DataPathUserFuncs.CxPlatDataPathUninitialize(Datapath->User); - if (Datapath->Xdp) { - // DataPathXdpFuncs.CxPlatDataPathUninitialize(Datapath->User); - } - CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); + DataPathUserFuncs.CxPlatDataPathUninitialize(Datapath); + if (Datapath->RawDataPath) { + XDP_CxPlatDataPathUninitialize(Datapath->RawDataPath); + } + + // if (Datapath->Xdp) { + // // DataPathXdpFuncs.CxPlatDataPathUninitialize(Datapath->User); + // } + // CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -98,10 +169,13 @@ CxPlatDataPathUpdateConfig( _In_ QUIC_EXECUTION_CONFIG* Config ) { - DataPathUserFuncs.CxPlatDataPathUpdateConfig(Datapath->User, Config); - if (Datapath->Xdp) { - // DataPathXdpFuncs.CxPlatDataPathUpdateConfig(Datapath->Xdp, Config); + DataPathUserFuncs.CxPlatDataPathUpdateConfig(Datapath, Config); + if (Datapath->RawDataPath) { + XDP_CxPlatDataPathUpdateConfig(Datapath->RawDataPath, Config); } + // if (Datapath->Xdp) { + // // DataPathXdpFuncs.CxPlatDataPathUpdateConfig(Datapath->Xdp, Config); + // } } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -110,9 +184,9 @@ CxPlatDataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH* Datapath ) { - // Which feature should be taken? - return DataPathUserFuncs.CxPlatDataPathGetSupportedFeatures(Datapath->User); - // TODO: xdp + // FIXME: Which feature should be taken? + // return DataPathUserFuncs.CxPlatDataPathGetSupportedFeatures(Datapath); + return 0; } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -121,10 +195,9 @@ CxPlatDataPathIsPaddingPreferred( _In_ CXPLAT_DATAPATH* Datapath ) { - // Which flag should be taken? - return DataPathUserFuncs.CxPlatDataPathIsPaddingPreferred(Datapath->User); - // TODO: xdp - + // FIXME: Which flag should be taken? + // return DataPathUserFuncs.CxPlatDataPathIsPaddingPreferred(Datapath); + return 0; } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -139,7 +212,7 @@ CxPlatDataPathGetLocalAddresses( { // which datapath should be used? return DataPathUserFuncs.CxPlatDataPathGetLocalAddresses( - Datapath->User, + Datapath, Addresses, AddressesCount); // TODO: xdp @@ -155,12 +228,43 @@ CxPlatDataPathGetGatewayAddresses( _Out_ uint32_t* GatewayAddressesCount ) { - // which datapath should be used? + // XDP doesn't support, but later which should be called? return DataPathUserFuncs.CxPlatDataPathGetGatewayAddresses( - Datapath->User, + Datapath, GatewayAddresses, GatewayAddressesCount); - // TODO: xdp +} + +// private func +void +CxPlatDataPathPopulateTargetAddress( + _In_ ADDRESS_FAMILY Family, + _In_ ADDRINFOW *Ai, + _Out_ SOCKADDR_INET* Address + ) +{ + if (Ai->ai_addr->sa_family == QUIC_ADDRESS_FAMILY_INET6) { + // + // Is this a mapped ipv4 one? + // + PSOCKADDR_IN6 SockAddr6 = (PSOCKADDR_IN6)Ai->ai_addr; + + if (Family == QUIC_ADDRESS_FAMILY_UNSPEC && IN6ADDR_ISV4MAPPED(SockAddr6)) + { + PSOCKADDR_IN SockAddr4 = &Address->Ipv4; + // + // Get the ipv4 address from the mapped address. + // + SockAddr4->sin_family = QUIC_ADDRESS_FAMILY_INET; + SockAddr4->sin_addr = + *(IN_ADDR UNALIGNED *) + IN6_GET_ADDR_V4MAPPED(&SockAddr6->sin6_addr); + SockAddr4->sin_port = SockAddr6->sin6_port; + return; + } + } + + CxPlatCopyMemory(Address, Ai->ai_addr, Ai->ai_addrlen); } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -171,12 +275,70 @@ CxPlatDataPathResolveAddress( _Inout_ QUIC_ADDR* Address ) { - // TODO: both datapath adopt same procedure - // can be flatten here and may no need for calling into internal datapath - return DataPathUserFuncs.CxPlatDataPathResolveAddress( - Datapath->User, - HostName, - Address); + QUIC_STATUS Status; + PWSTR HostNameW = NULL; + ADDRINFOW Hints = { 0 }; + ADDRINFOW *Ai; + + Status = + CxPlatUtf8ToWideChar( + HostName, + QUIC_POOL_PLATFORM_TMP_ALLOC, + &HostNameW); + if (QUIC_FAILED(Status)) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "Convert HostName to unicode"); + goto Exit; + } + + // + // Prepopulate hint with input family. It might be unspecified. + // + Hints.ai_family = Address->si_family; + + // + // Try numeric name first. + // + Hints.ai_flags = AI_NUMERICHOST; + if (GetAddrInfoW(HostNameW, NULL, &Hints, &Ai) == 0) { + CxPlatDataPathPopulateTargetAddress((ADDRESS_FAMILY)Hints.ai_family, Ai, Address); + FreeAddrInfoW(Ai); + Status = QUIC_STATUS_SUCCESS; + goto Exit; + } + + // + // Try canonical host name. + // + Hints.ai_flags = AI_CANONNAME; + if (GetAddrInfoW(HostNameW, NULL, &Hints, &Ai) == 0) { + CxPlatDataPathPopulateTargetAddress((ADDRESS_FAMILY)Hints.ai_family, Ai, Address); + FreeAddrInfoW(Ai); + Status = QUIC_STATUS_SUCCESS; + goto Exit; + } + + QuicTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "Resolving hostname to IP"); + QuicTraceLogError( + DatapathResolveHostNameFailed, + "[%p] Couldn't resolve hostname '%s' to an IP address", + Datapath, + HostName); + Status = HRESULT_FROM_WIN32(WSAHOST_NOT_FOUND); + +Exit: + + if (HostNameW != NULL) { + CXPLAT_FREE(HostNameW, QUIC_POOL_PLATFORM_TMP_ALLOC); + } + + return Status; } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -189,24 +351,62 @@ CxPlatSocketCreateUdp( { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - // - if client, use one out of 2 datapath - // - if Config->RemoteAddress is loopback, use user - // - if not, use xdp - // - if server, init socket, then share info to xdp? + // Raw (Sock (Base (addrs))) + // alloc memory by sizeof RAW + // call CxPlatSocketCreateUdp with NewSocket as Sock + // Call CxPlatInitRawSocket with NewSocket as Raw + BOOLEAN IsServerSocket = Config->RemoteAddress == NULL; + uint16_t SocketCount = IsServerSocket ? Datapath->ProcCount : 1; + uint32_t RawSocketLength = CxPlatGetRawSocketSize() + SocketCount * sizeof(CXPLAT_SOCKET_PROC); + CXPLAT_SOCKET_RAW* RawSocket = CXPLAT_ALLOC_PAGED(RawSocketLength, QUIC_POOL_SOCKET); + if (RawSocket == NULL) { + QuicTraceEvent( + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "CXPLAT_SOCKET", + RawSocketLength); + Status = QUIC_STATUS_OUT_OF_MEMORY; + goto Error; + } + + ZeroMemory(RawSocket, RawSocketLength); + CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); + + QuicTraceEvent( + DatapathCreated, + "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", + Socket, + CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress), + CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress)); + Status = DataPathUserFuncs.CxPlatSocketCreateUdp( - Datapath->User, + Datapath, Config, - NewSocket); - (*NewSocket)->DataPathType = DATAPATH_TYPE_USER; - - // if (FALSE /*(server && xdp) || (client && remote is not loopback)*/) { - // // use XDP - // } else { - // Status = DataPathUserFuncs.CxPlatSocketCreateUdp( - // Datapath->User, - // Config, - // NewSocket); + Socket); + if (QUIC_FAILED(Status)) { + goto Error; + } + + if (Datapath) { + Status = CxPlatInitRawSocket( + Datapath->RawDataPath, + Config, + RawSocket); + if (QUIC_FAILED(Status)) { + // just ignore with logging? + goto Error; + } + } + + *NewSocket = (CXPLAT_SOCKET*)Socket; + RawSocket = NULL; + +Error: + // TODO: error handling + // if (RawSocket) { + // // invalid type + // CxPlatSocketDelete((CXPLAT_SOCKET*)RawSocket); // } return Status; @@ -243,13 +443,10 @@ CxPlatSocketDelete( _In_ CXPLAT_SOCKET* Socket ) { - if (Socket->DataPathType == DATAPATH_TYPE_USER) { - DataPathUserFuncs.CxPlatSocketDelete((CXPLAT_SOCKET_INTERNAL*)Socket); - } else if (Socket->DataPathType == DATAPATH_TYPE_XDP) { - // use XDP - } else { - CXPLAT_DBG_ASSERT(FALSE); - } + // TODO: bubble up common logic + CxPlatRawSocketDelete(CxPlatSocketToRaw(Socket)); + DataPathUserFuncs.CxPlatSocketDelete(Socket); + // free(Socket); } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -261,9 +458,10 @@ CxPlatSocketUpdateQeo( _In_ uint32_t OffloadCount ) { - UNREFERENCED_PARAMETER(Socket); - UNREFERENCED_PARAMETER(Offloads); - UNREFERENCED_PARAMETER(OffloadCount); + if (Socket->Datapath && Socket->Datapath->RawDataPath) { + CXPLAT_SOCKET_RAW* RawSocket = CxPlatSocketToRaw(Socket); + return XDP_CxPlatSocketUpdateQeo(RawSocket, Offloads, OffloadCount); + } return QUIC_STATUS_NOT_SUPPORTED; } @@ -273,14 +471,17 @@ CxPlatSocketGetLocalMtu( _In_ CXPLAT_SOCKET* Socket ) { - if (Socket->DataPathType == DATAPATH_TYPE_USER) { - return DataPathUserFuncs.CxPlatSocketGetLocalMtu((CXPLAT_SOCKET_INTERNAL*)Socket); - } else if (Socket->DataPathType == DATAPATH_TYPE_XDP) { - // use XDP - } else { - CXPLAT_DBG_ASSERT(FALSE); + // if RemoteAddress is "lo", use Socket mtu + // else if RawSocket is availabe, use RawSocket Mtu + // else use Socket Mtu + if (Socket->Datapath && Socket->Datapath->RawDataPath && + !((Socket->RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET && + Socket->RemoteAddress.Ipv4.sin_addr.S_un.S_addr == htonl(INADDR_LOOPBACK)) || + (Socket->RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET6 && + IN6_IS_ADDR_LOOPBACK(&Socket->RemoteAddress.Ipv6.sin6_addr)))) { + XDP_CxPlatSocketGetLocalMtu(CxPlatSocketToRaw(Socket)); } - return 0; + return Socket->Mtu; } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -290,10 +491,8 @@ CxPlatSocketGetLocalAddress( _Out_ QUIC_ADDR* Address ) { - // TODO: inline - DataPathUserFuncs.CxPlatSocketGetLocalAddress( - (CXPLAT_SOCKET_INTERNAL*)Socket, - Address); + CXPLAT_DBG_ASSERT(Socket != NULL); + *Address = Socket->LocalAddress; } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -303,10 +502,8 @@ CxPlatSocketGetRemoteAddress( _Out_ QUIC_ADDR* Address ) { - // TODO: inline - DataPathUserFuncs.CxPlatSocketGetRemoteAddress( - (CXPLAT_SOCKET_INTERNAL*)Socket, - Address); + CXPLAT_DBG_ASSERT(Socket != NULL); + *Address = Socket->RemoteAddress; } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -315,7 +512,16 @@ CxPlatRecvDataReturn( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ) { - // TODO: CXPLAT_RECV_DATA to have flag to indicate which datapath it belongs to + // TODO: CXPLAT_RECV_DATA doesn't have RawDatapath existence. + // need flag to check which datapath is used. + CXPLAT_DBG_ASSERT(RecvDataChain != NULL); + if (RecvDataChain->BufferFrom == CXPLAT_BUFFER_FROM_USER) { + DataPathUserFuncs.CxPlatRecvDataReturn(RecvDataChain); + } else if (RecvDataChain->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { + XDP_CxPlatRecvDataReturn(RecvDataChain); + } else { + CXPLAT_DBG_ASSERT(FALSE); + } } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -327,16 +533,20 @@ CxPlatSendDataAlloc( ) { CXPLAT_SEND_DATA* SendData = NULL; - if (Socket->DataPathType == DATAPATH_TYPE_USER) { - SendData = DataPathUserFuncs.CxPlatSendDataAlloc( - (CXPLAT_SOCKET_INTERNAL*)Socket, - Config); - SendData->DataPathType = DATAPATH_TYPE_USER; - } else if (Socket->DataPathType == DATAPATH_TYPE_XDP) { - // use XDP - // SendData->DataPathType = DATAPATH_TYPE_USER; + if (Socket->Datapath && Socket->Datapath->RawDataPath && + !((Socket->RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET && + Socket->RemoteAddress.Ipv4.sin_addr.S_un.S_addr == htonl(INADDR_LOOPBACK)) || + (Socket->RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET6 && + IN6_IS_ADDR_LOOPBACK(&Socket->RemoteAddress.Ipv6.sin6_addr)))) { + SendData = XDP_CxPlatSendDataAlloc(CxPlatSocketToRaw(Socket), Config); + if (SendData) { + SendData->BufferFrom = CXPLAT_BUFFER_FROM_XDP; + } } else { - CXPLAT_DBG_ASSERT(FALSE); + SendData = DataPathUserFuncs.CxPlatSendDataAlloc(Socket, Config); + if (SendData) { + SendData->BufferFrom = CXPLAT_BUFFER_FROM_USER; + } } return SendData; } @@ -347,11 +557,10 @@ CxPlatSendDataFree( _In_ CXPLAT_SEND_DATA* SendData ) { - if (SendData->DataPathType == DATAPATH_TYPE_USER) { - DataPathUserFuncs.CxPlatSendDataFree( - (CXPLAT_SEND_DATA_INTERNAL*)SendData); - } else if (SendData->DataPathType == DATAPATH_TYPE_XDP) { - // use XDP + if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { + + } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { + } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -365,12 +574,10 @@ CxPlatSendDataAllocBuffer( _In_ uint16_t MaxBufferLength ) { - if (SendData->DataPathType == DATAPATH_TYPE_USER) { - return DataPathUserFuncs.CxPlatSendDataAllocBuffer( - (CXPLAT_SEND_DATA_INTERNAL*)SendData, - MaxBufferLength); - } else if (SendData->DataPathType == DATAPATH_TYPE_XDP) { - // use XDP + if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { + + } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { + } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -384,12 +591,10 @@ CxPlatSendDataFreeBuffer( _In_ QUIC_BUFFER* Buffer ) { - if (SendData->DataPathType == DATAPATH_TYPE_USER) { - DataPathUserFuncs.CxPlatSendDataFreeBuffer( - (CXPLAT_SEND_DATA_INTERNAL*)SendData, - Buffer); - } else if (SendData->DataPathType == DATAPATH_TYPE_XDP) { - // use XDP + if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { + + } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { + } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -401,11 +606,10 @@ CxPlatSendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ) { - if (SendData->DataPathType == DATAPATH_TYPE_USER) { - return DataPathUserFuncs.CxPlatSendDataIsFull( - (CXPLAT_SEND_DATA_INTERNAL*)SendData); - } else if (SendData->DataPathType == DATAPATH_TYPE_XDP) { - // use XDP + if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { + + } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { + } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -420,13 +624,10 @@ CxPlatSocketSend( _In_ CXPLAT_SEND_DATA* SendData ) { - if (Socket->DataPathType == DATAPATH_TYPE_USER) { - return DataPathUserFuncs.CxPlatSocketSend( - (CXPLAT_SOCKET_INTERNAL*)Socket, - Route, - (CXPLAT_SEND_DATA_INTERNAL*)SendData); - } else if (Socket->DataPathType == DATAPATH_TYPE_XDP) { - // use XDP + if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { + + } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { + } else { CXPLAT_DBG_ASSERT(FALSE); } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index d723f08597..985eb15164 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -106,9 +106,6 @@ CXPLAT_STATIC_ASSERT( ErrorCode == WSAECONNRESET \ ) -typedef struct CXPLAT_DATAPATH_PROC CXPLAT_DATAPATH_PROC; // Per-processor datapath state. -typedef struct CXPLAT_SOCKET_PROC CXPLAT_SOCKET_PROC; // Per-processor socket state. - typedef enum CXPLAT_SOCKET_TYPE { CXPLAT_SOCKET_UDP = 0, CXPLAT_SOCKET_TCP_LISTENER = 1, @@ -116,31 +113,6 @@ typedef enum CXPLAT_SOCKET_TYPE { CXPLAT_SOCKET_TCP_SERVER = 3 } CXPLAT_SOCKET_TYPE; -// -// Type of IO. -// -typedef enum DATAPATH_IO_TYPE { - DATAPATH_IO_SIGNATURE = 'WINU', - DATAPATH_IO_RECV = DATAPATH_IO_SIGNATURE + 1, - DATAPATH_IO_SEND = DATAPATH_IO_SIGNATURE + 2, - DATAPATH_IO_QUEUE_SEND = DATAPATH_IO_SIGNATURE + 3, - DATAPATH_IO_ACCEPTEX = DATAPATH_IO_SIGNATURE + 4, - DATAPATH_IO_CONNECTEX = DATAPATH_IO_SIGNATURE + 5, - DATAPATH_IO_RIO_NOTIFY = DATAPATH_IO_SIGNATURE + 6, - DATAPATH_IO_RIO_RECV = DATAPATH_IO_SIGNATURE + 7, - DATAPATH_IO_RIO_SEND = DATAPATH_IO_SIGNATURE + 8, - DATAPATH_IO_RECV_FAILURE = DATAPATH_IO_SIGNATURE + 9, - DATAPATH_IO_MAX -} DATAPATH_IO_TYPE; - -// -// IO header for SQE->CQE based completions. -// -typedef struct DATAPATH_IO_SQE { - DATAPATH_IO_TYPE IoType; - DATAPATH_SQE DatapathSqe; -} DATAPATH_IO_SQE; - // // Internal receive context. // @@ -222,7 +194,7 @@ typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) CXPLAT_RIO_SEND_BUFFE // // This send buffer's datapath. // - CXPLAT_DATAPATH_INTERNAL* Datapath; + CXPLAT_DATAPATH* Datapath; // // This send buffer's send data. @@ -326,341 +298,11 @@ typedef struct CXPLAT_SEND_DATA_INTERNAL { QUIC_ADDR MappedRemoteAddress; } CXPLAT_SEND_DATA_INTERNAL; -// -// Per-processor socket state. -// -typedef struct QUIC_CACHEALIGN CXPLAT_SOCKET_PROC { - // - // Used to synchronize clean up. - // - CXPLAT_REF_COUNT RefCount; - - // - // Submission queue event for IO completion - // - DATAPATH_IO_SQE IoSqe; - - // - // Submission queue event for RIO IO completion - // - DATAPATH_IO_SQE RioSqe; - - // - // The datapath per-processor context. - // - CXPLAT_DATAPATH_PROC* DatapathProc; - - // - // Parent CXPLAT_SOCKET. - // - CXPLAT_SOCKET_INTERNAL* Parent; - - // - // Socket handle to the networking stack. - // - SOCKET Socket; - - // - // Rundown for synchronizing upcalls to the app and downcalls on the Socket. - // - CXPLAT_RUNDOWN_REF RundownRef; - - // - // Flag indicates the socket started processing IO. - // - BOOLEAN IoStarted : 1; - - // - // Flag indicates a persistent out-of-memory failure for the receive path. - // - BOOLEAN RecvFailure : 1; - - // - // Debug Flags - // - uint8_t Uninitialized : 1; - uint8_t Freed : 1; - - // - // The set of parameters/state passed to WsaRecvMsg for the IP stack to - // populate to indicate the result of the receive. - // - - union { - // - // Normal TCP/UDP socket data - // - struct { - RIO_CQ RioCq; - RIO_RQ RioRq; - ULONG RioRecvCount; - ULONG RioSendCount; - CXPLAT_LIST_ENTRY RioSendOverflow; - BOOLEAN RioNotifyArmed; - }; - // - // TCP Listener socket data - // - struct { - CXPLAT_SOCKET_INTERNAL* AcceptSocket; - char AcceptAddrSpace[ - sizeof(SOCKADDR_INET) + 16 + - sizeof(SOCKADDR_INET) + 16 - ]; - }; - }; -} CXPLAT_SOCKET_PROC; - -// -// Per-port state. Multiple sockets are created on each port. -// -typedef struct CXPLAT_SOCKET_INTERNAL { - CXPLAT_SOCKET; - - // - // Parent datapath. - // - CXPLAT_DATAPATH_INTERNAL* Datapath; - - // - // Client context pointer. - // - void *ClientContext; - - // - // The local address and port. - // - SOCKADDR_INET LocalAddress; - - // - // The remote address and port. - // - SOCKADDR_INET RemoteAddress; - - // - // Synchronization mechanism for cleanup. - // - CXPLAT_REF_COUNT RefCount; - - // - // The local interface's MTU. - // - uint16_t Mtu; - - // - // The size of a receive buffer's payload. - // - uint32_t RecvBufLen; - - // - // Socket type. - // - uint8_t Type : 2; // CXPLAT_SOCKET_TYPE - - // - // Flag indicates the socket has a default remote destination. - // - uint8_t HasFixedRemoteAddress : 1; - - // - // Flag indicates the socket indicated a disconnect event. - // - uint8_t DisconnectIndicated : 1; - - // - // Flag indicates the binding is being used for PCP. - // - uint8_t PcpBinding : 1; - - // - // Flag indicates the socket is using RIO instead of traditional Winsock. - // - uint8_t UseRio : 1; - - // - // Debug flags. - // - uint8_t Uninitialized : 1; - uint8_t Freed : 1; - - // - // Per-processor socket contexts. - // - CXPLAT_SOCKET_PROC Processors[0]; - -} CXPLAT_SOCKET_INTERNAL; - -// -// Represents a single IO completion port and thread for processing work that is -// completed on a single processor. -// -typedef struct QUIC_CACHEALIGN CXPLAT_DATAPATH_PROC { - - // - // Parent datapath. - // - CXPLAT_DATAPATH_INTERNAL* Datapath; - - // - // Event queue used for processing work. - // - CXPLAT_EVENTQ* EventQ; - - // - // Used to synchronize clean up. - // - CXPLAT_REF_COUNT RefCount; - - // - // The index of ideal processor for this datapath. - // - uint16_t IdealProcessor; - - // - // Debug flags - // - uint8_t Uninitialized : 1; - - // - // Pool of send contexts to be shared by all sockets on this core. - // - CXPLAT_POOL SendDataPool; - - // - // Pool of send contexts to be shared by all RIO sockets on this core. - // - CXPLAT_POOL RioSendDataPool; - - // - // Pool of send buffers to be shared by all sockets on this core. - // - CXPLAT_POOL SendBufferPool; - - // - // Pool of large segmented send buffers to be shared by all sockets on this - // core. - // - CXPLAT_POOL LargeSendBufferPool; - - // - // Pool of send buffers to be shared by all RIO sockets on this core. - // - CXPLAT_POOL RioSendBufferPool; - - // - // Pool of large segmented send buffers to be shared by all RIO sockets on - // this core. - // - CXPLAT_POOL RioLargeSendBufferPool; - - // - // Pool of receive datagram contexts and buffers to be shared by all sockets - // on this core. - // - CXPLAT_POOL RecvDatagramPool; - - // - // Pool of RIO receive datagram contexts and buffers to be shared by all - // RIO sockets on this core. - // - CXPLAT_POOL RioRecvPool; - -} CXPLAT_DATAPATH_PROC; - -// -// Main structure for tracking all UDP abstractions. -// -typedef struct CXPLAT_DATAPATH_INTERNAL { - - // - // The UDP callback function pointers. - // - CXPLAT_UDP_DATAPATH_CALLBACKS UdpHandlers; - - // - // The TCP callback function pointers. - // - CXPLAT_TCP_DATAPATH_CALLBACKS TcpHandlers; - - // - // Function pointer to AcceptEx. - // - LPFN_ACCEPTEX AcceptEx; - - // - // Function pointer to ConnectEx. - // - LPFN_CONNECTEX ConnectEx; - - // - // Function pointer to WSASendMsg. - // - LPFN_WSASENDMSG WSASendMsg; - - // - // Function pointer to WSARecvMsg. - // - LPFN_WSARECVMSG WSARecvMsg; - - // - // Function pointer table for RIO. - // - RIO_EXTENSION_FUNCTION_TABLE RioDispatch; - - // - // Used to synchronize clean up. - // - CXPLAT_REF_COUNT RefCount; - - // - // Set of supported features. - // - uint32_t Features; - - // - // The size of each receive datagram array element, including client context, - // internal context, and padding. - // - uint32_t DatagramStride; - - // - // The offset of the receive payload buffer from the start of the receive - // context. - // - uint32_t RecvPayloadOffset; - - // - // The number of processors. - // - uint16_t ProcCount; - - // - // Maximum batch sizes supported for send. - // - uint8_t MaxSendBatchSize; - - // - // Uses RIO interface instead of normal asyc IO. - // - uint8_t UseRio : 1; - - // - // Debug flags - // - uint8_t Uninitialized : 1; - uint8_t Freed : 1; - - // - // Per-processor completion contexts. - // - CXPLAT_DATAPATH_PROC Processors[0]; - -} CXPLAT_DATAPATH_INTERNAL; _IRQL_requires_max_(PASSIVE_LEVEL) void MANGLE(CxPlatSocketDelete)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket + _In_ CXPLAT_SOCKET* Socket ); #ifdef DEBUG @@ -821,7 +463,7 @@ CxPlatDataPathDatagramToInternalDatagramContext( CXPLAT_DATAPATH_PROC* CxPlatDataPathGetProc( - _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _In_ uint16_t Processor ) { @@ -925,7 +567,7 @@ CxPlatFreeRecvContext( void CxPlatDataPathQueryRssScalabilityInfo( - _Inout_ CXPLAT_DATAPATH_INTERNAL* Datapath + _Inout_ CXPLAT_DATAPATH* Datapath ) { int Result; @@ -975,7 +617,7 @@ CxPlatDataPathQueryRssScalabilityInfo( QUIC_STATUS CxPlatDataPathQuerySockoptSupport( - _Inout_ CXPLAT_DATAPATH_INTERNAL* Datapath + _Inout_ CXPLAT_DATAPATH* Datapath ) { int Result; @@ -1169,46 +811,23 @@ _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatDataPathInitialize)( _In_ uint32_t ClientRecvContextLength, - _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, - _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, _In_opt_ QUIC_EXECUTION_CONFIG* Config, - _Out_ CXPLAT_DATAPATH_INTERNAL** NewDataPath + _Out_ CXPLAT_DATAPATH* Datapath ) { int WsaError; QUIC_STATUS Status; WSADATA WsaData; const uint16_t* ProcessorList; - uint32_t ProcessorCount; - uint32_t DatapathLength; - CXPLAT_DATAPATH_INTERNAL* Datapath = NULL; + BOOLEAN WsaInitialized = FALSE; - if (NewDataPath == NULL) { + if (Datapath == NULL) { Status = QUIC_STATUS_INVALID_PARAMETER; goto Exit; } - if (UdpCallbacks != NULL) { - if (UdpCallbacks->Receive == NULL || UdpCallbacks->Unreachable == NULL) { - Status = QUIC_STATUS_INVALID_PARAMETER; - goto Exit; - } - } - if (TcpCallbacks != NULL) { - if (TcpCallbacks->Accept == NULL || - TcpCallbacks->Connect == NULL || - TcpCallbacks->Receive == NULL || - TcpCallbacks->SendComplete == NULL) { - Status = QUIC_STATUS_INVALID_PARAMETER; - goto Exit; - } - } - - if (!CxPlatWorkersLazyStart(Config)) { - Status = QUIC_STATUS_OUT_OF_MEMORY; - goto Exit; - } + // TODO: moveup if ((WsaError = WSAStartup(MAKEWORD(2, 2), &WsaData)) != 0) { QuicTraceEvent( LibraryErrorStatus, @@ -1221,36 +840,11 @@ MANGLE(CxPlatDataPathInitialize)( WsaInitialized = TRUE; if (Config && Config->ProcessorCount) { - ProcessorCount = Config->ProcessorCount; ProcessorList = Config->ProcessorList; } else { - ProcessorCount = CxPlatProcMaxCount(); ProcessorList = NULL; } - DatapathLength = - sizeof(CXPLAT_DATAPATH_INTERNAL) + - ProcessorCount * sizeof(CXPLAT_DATAPATH_PROC); - - Datapath = (CXPLAT_DATAPATH_INTERNAL*)CXPLAT_ALLOC_PAGED(DatapathLength, QUIC_POOL_DATAPATH); - if (Datapath == NULL) { - QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_DATAPATH", - DatapathLength); - Status = QUIC_STATUS_OUT_OF_MEMORY; - goto Error; - } - - RtlZeroMemory(Datapath, DatapathLength); - if (UdpCallbacks) { - Datapath->UdpHandlers = *UdpCallbacks; - } - if (TcpCallbacks) { - Datapath->TcpHandlers = *TcpCallbacks; - } - Datapath->ProcCount = (uint16_t)ProcessorCount; CxPlatRefInitializeEx(&Datapath->RefCount, Datapath->ProcCount); Datapath->UseRio = Config && !!(Config->Flags & QUIC_EXECUTION_CONFIG_FLAG_RIO); @@ -1386,11 +980,10 @@ MANGLE(CxPlatDataPathInitialize)( } CXPLAT_FRE_ASSERT(CxPlatRundownAcquire(&CxPlatWorkerRundown)); - *NewDataPath = Datapath; Status = QUIC_STATUS_SUCCESS; Error: - + // TODO: move up? if (QUIC_FAILED(Status)) { if (Datapath != NULL) { CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); @@ -1408,7 +1001,7 @@ MANGLE(CxPlatDataPathInitialize)( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDataPathRelease( - _In_ CXPLAT_DATAPATH_INTERNAL* Datapath + _In_ CXPLAT_DATAPATH* Datapath ) { if (CxPlatRefDecrement(&Datapath->RefCount)) { @@ -1445,7 +1038,7 @@ CxPlatProcessorContextRelease( _IRQL_requires_max_(PASSIVE_LEVEL) void MANGLE(CxPlatDataPathUninitialize)( - _In_ CXPLAT_DATAPATH_INTERNAL* Datapath + _In_ CXPLAT_DATAPATH* Datapath ) { if (Datapath != NULL) { @@ -1461,7 +1054,7 @@ MANGLE(CxPlatDataPathUninitialize)( _IRQL_requires_max_(PASSIVE_LEVEL) void MANGLE(CxPlatDataPathUpdateConfig)( - _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ) { @@ -1472,7 +1065,7 @@ MANGLE(CxPlatDataPathUpdateConfig)( _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t MANGLE(CxPlatDataPathGetSupportedFeatures)( - _In_ CXPLAT_DATAPATH_INTERNAL* Datapath + _In_ CXPLAT_DATAPATH* Datapath ) { return Datapath->Features; @@ -1481,7 +1074,7 @@ MANGLE(CxPlatDataPathGetSupportedFeatures)( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN MANGLE(CxPlatDataPathIsPaddingPreferred)( - _In_ CXPLAT_DATAPATH_INTERNAL* Datapath + _In_ CXPLAT_DATAPATH* Datapath ) { return !!(Datapath->Features & CXPLAT_DATAPATH_FEATURE_SEND_SEGMENTATION); @@ -1491,7 +1084,7 @@ _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS MANGLE(CxPlatDataPathGetLocalAddresses)( - _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) CXPLAT_ADAPTER_ADDRESS** Addresses, _Out_ uint32_t* AddressesCount @@ -1605,7 +1198,7 @@ _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS MANGLE(CxPlatDataPathGetGatewayAddresses)( - _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) QUIC_ADDR** GatewayAddresses, _Out_ uint32_t* GatewayAddressesCount @@ -1708,109 +1301,19 @@ MANGLE(CxPlatDataPathGetGatewayAddresses)( return Status; } -void -CxPlatDataPathPopulateTargetAddress( - _In_ ADDRESS_FAMILY Family, - _In_ ADDRINFOW *Ai, - _Out_ SOCKADDR_INET* Address - ) -{ - if (Ai->ai_addr->sa_family == QUIC_ADDRESS_FAMILY_INET6) { - // - // Is this a mapped ipv4 one? - // - PSOCKADDR_IN6 SockAddr6 = (PSOCKADDR_IN6)Ai->ai_addr; - - if (Family == QUIC_ADDRESS_FAMILY_UNSPEC && IN6ADDR_ISV4MAPPED(SockAddr6)) - { - PSOCKADDR_IN SockAddr4 = &Address->Ipv4; - // - // Get the ipv4 address from the mapped address. - // - SockAddr4->sin_family = QUIC_ADDRESS_FAMILY_INET; - SockAddr4->sin_addr = - *(IN_ADDR UNALIGNED *) - IN6_GET_ADDR_V4MAPPED(&SockAddr6->sin6_addr); - SockAddr4->sin_port = SockAddr6->sin6_port; - return; - } - } - - CxPlatCopyMemory(Address, Ai->ai_addr, Ai->ai_addrlen); -} - +// TODO: DELETE _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatDataPathResolveAddress)( - _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _In_z_ const char* HostName, _Inout_ QUIC_ADDR* Address ) { - QUIC_STATUS Status; - PWSTR HostNameW = NULL; - ADDRINFOW Hints = { 0 }; - ADDRINFOW *Ai; - - Status = - CxPlatUtf8ToWideChar( - HostName, - QUIC_POOL_PLATFORM_TMP_ALLOC, - &HostNameW); - if (QUIC_FAILED(Status)) { - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - Status, - "Convert HostName to unicode"); - goto Exit; - } - - // - // Prepopulate hint with input family. It might be unspecified. - // - Hints.ai_family = Address->si_family; - - // - // Try numeric name first. - // - Hints.ai_flags = AI_NUMERICHOST; - if (GetAddrInfoW(HostNameW, NULL, &Hints, &Ai) == 0) { - CxPlatDataPathPopulateTargetAddress((ADDRESS_FAMILY)Hints.ai_family, Ai, Address); - FreeAddrInfoW(Ai); - Status = QUIC_STATUS_SUCCESS; - goto Exit; - } - - // - // Try canonical host name. - // - Hints.ai_flags = AI_CANONNAME; - if (GetAddrInfoW(HostNameW, NULL, &Hints, &Ai) == 0) { - CxPlatDataPathPopulateTargetAddress((ADDRESS_FAMILY)Hints.ai_family, Ai, Address); - FreeAddrInfoW(Ai); - Status = QUIC_STATUS_SUCCESS; - goto Exit; - } - - QuicTraceEvent( - LibraryError, - "[ lib] ERROR, %s.", - "Resolving hostname to IP"); - QuicTraceLogError( - DatapathResolveHostNameFailed, - "[%p] Couldn't resolve hostname '%s' to an IP address", - Datapath, - HostName); - Status = HRESULT_FROM_WIN32(WSAHOST_NOT_FOUND); - -Exit: - - if (HostNameW != NULL) { - CXPLAT_FREE(HostNameW, QUIC_POOL_PLATFORM_TMP_ALLOC); - } - - return Status; + UNREFERENCED_PARAMETER(Datapath); + UNREFERENCED_PARAMETER(HostName); + UNREFERENCED_PARAMETER(Address); + return QUIC_STATUS_NOT_SUPPORTED; } void @@ -1857,9 +1360,9 @@ CxPlatSocketEnqueueSqe( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketCreateUdp)( - _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _In_ const CXPLAT_UDP_CONFIG* Config, - _Out_ CXPLAT_SOCKET** NewSocket + _Out_ CXPLAT_SOCKET* Socket ) { QUIC_STATUS Status; @@ -1871,27 +1374,6 @@ MANGLE(CxPlatSocketCreateUdp)( CXPLAT_DBG_ASSERT(Datapath->UdpHandlers.Receive != NULL || Config->Flags & CXPLAT_SOCKET_FLAG_PCP); - uint32_t SocketLength = - sizeof(CXPLAT_SOCKET_INTERNAL) + SocketCount * sizeof(CXPLAT_SOCKET_PROC); - CXPLAT_SOCKET_INTERNAL* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); - if (Socket == NULL) { - QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_SOCKET", - SocketLength); - Status = QUIC_STATUS_OUT_OF_MEMORY; - goto Error; - } - - QuicTraceEvent( - DatapathCreated, - "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", - Socket, - CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress), - CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress)); - - ZeroMemory(Socket, SocketLength); Socket->Datapath = Datapath; Socket->ClientContext = Config->CallbackContext; Socket->HasFixedRemoteAddress = (Config->RemoteAddress != NULL); @@ -2445,7 +1927,6 @@ MANGLE(CxPlatSocketCreateUdp)( // Must set output pointer before starting receive path, as the receive path // will try to use the output. // - *NewSocket = (CXPLAT_SOCKET*)Socket; for (uint16_t i = 0; i < SocketCount; i++) { CxPlatDataPathStartReceiveAsync(&Socket->Processors[i]); @@ -2453,21 +1934,16 @@ MANGLE(CxPlatSocketCreateUdp)( } Status = QUIC_STATUS_SUCCESS; - Socket = NULL; Error: - if (Socket != NULL) { - MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET_INTERNAL*)Socket); - } - return Status; } _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatSocketCreateTcpInternal( - _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _In_ CXPLAT_SOCKET_TYPE Type, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ const QUIC_ADDR* RemoteAddress, @@ -2484,8 +1960,8 @@ CxPlatSocketCreateTcpInternal( CXPLAT_DBG_ASSERT(Datapath->TcpHandlers.Receive != NULL); CXPLAT_SOCKET_PROC* SocketProc = NULL; - uint32_t SocketLength = sizeof(CXPLAT_SOCKET_INTERNAL) + sizeof(CXPLAT_SOCKET_PROC); - CXPLAT_SOCKET_INTERNAL* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); + uint32_t SocketLength = sizeof(CXPLAT_SOCKET) + sizeof(CXPLAT_SOCKET_PROC); + CXPLAT_SOCKET* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); if (Socket == NULL) { QuicTraceEvent( AllocFailure, @@ -2714,7 +2190,7 @@ CxPlatSocketCreateTcpInternal( Error: if (Socket != NULL) { - MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET_INTERNAL*)Socket); + MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET*)Socket); } return Status; @@ -2723,7 +2199,7 @@ CxPlatSocketCreateTcpInternal( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketCreateTcp)( - _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, _In_opt_ void* CallbackContext, @@ -2743,7 +2219,7 @@ MANGLE(CxPlatSocketCreateTcp)( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketCreateTcpListener)( - _In_ CXPLAT_DATAPATH_INTERNAL* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ void* RecvCallbackContext, _Out_ CXPLAT_SOCKET** NewSocket @@ -2756,8 +2232,8 @@ MANGLE(CxPlatSocketCreateTcpListener)( CXPLAT_DBG_ASSERT(Datapath->TcpHandlers.Receive != NULL); CXPLAT_SOCKET_PROC* SocketProc = NULL; - uint32_t SocketLength = sizeof(CXPLAT_SOCKET_INTERNAL) + sizeof(CXPLAT_SOCKET_PROC); - CXPLAT_SOCKET_INTERNAL* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); + uint32_t SocketLength = sizeof(CXPLAT_SOCKET) + sizeof(CXPLAT_SOCKET_PROC); + CXPLAT_SOCKET* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); if (Socket == NULL) { QuicTraceEvent( AllocFailure, @@ -2954,7 +2430,7 @@ MANGLE(CxPlatSocketCreateTcpListener)( Error: if (Socket != NULL) { - MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET_INTERNAL*)Socket); + MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET*)Socket); } return Status; @@ -2963,7 +2439,7 @@ MANGLE(CxPlatSocketCreateTcpListener)( _IRQL_requires_max_(PASSIVE_LEVEL) void MANGLE(CxPlatSocketDelete)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket + _In_ CXPLAT_SOCKET* Socket ) { CXPLAT_DBG_ASSERT(Socket != NULL); @@ -2987,7 +2463,7 @@ MANGLE(CxPlatSocketDelete)( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatSocketRelease( - _In_ CXPLAT_SOCKET_INTERNAL* Socket + _In_ CXPLAT_SOCKET* Socket ) { if (CxPlatRefDecrement(&Socket->RefCount)) { @@ -3029,7 +2505,7 @@ CxPlatSocketContextRelease( } } else { if (SocketProc->AcceptSocket != NULL) { - MANGLE(CxPlatSocketDelete)(SocketProc->AcceptSocket); + MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET*)SocketProc->AcceptSocket); SocketProc->AcceptSocket = NULL; } } @@ -3046,7 +2522,7 @@ CxPlatSocketContextRelease( } SocketProc->Freed = TRUE; - CxPlatSocketRelease(SocketProc->Parent); + CxPlatSocketRelease((CXPLAT_SOCKET*)SocketProc->Parent); } } @@ -3130,7 +2606,7 @@ CxPlatSocketContextUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketUpdateQeo)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, _In_ uint32_t OffloadCount @@ -3145,17 +2621,18 @@ MANGLE(CxPlatSocketUpdateQeo)( _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 MANGLE(CxPlatSocketGetLocalMtu)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket + _In_ CXPLAT_SOCKET* Socket ) { CXPLAT_DBG_ASSERT(Socket != NULL); return Socket->Mtu; } +// TODO: remove _IRQL_requires_max_(DISPATCH_LEVEL) void MANGLE(CxPlatSocketGetLocalAddress)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET* Socket, _Out_ QUIC_ADDR* Address ) { @@ -3163,10 +2640,11 @@ MANGLE(CxPlatSocketGetLocalAddress)( *Address = Socket->LocalAddress; } +// TODO: remove _IRQL_requires_max_(DISPATCH_LEVEL) void MANGLE(CxPlatSocketGetRemoteAddress)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET* Socket, _Out_ QUIC_ADDR* Address ) { @@ -3183,13 +2661,12 @@ RioRecvBufferAllocate( { CXPLAT_DATAPATH_PROC* DatapathProc = CXPLAT_CONTAINING_RECORD(Pool, CXPLAT_DATAPATH_PROC, RioRecvPool); - CXPLAT_DATAPATH_INTERNAL* Datapath = DatapathProc->Datapath; CXPLAT_DATAPATH_INTERNAL_RECV_CONTEXT* RecvContext = CxPlatLargeAlloc(Size, Tag); if (RecvContext != NULL) { RecvContext->RioBufferId = - Datapath->RioDispatch.RIORegisterBuffer((char*)RecvContext, Size); + DatapathProc->Datapath->RioDispatch.RIORegisterBuffer((char*)RecvContext, Size); if (RecvContext->RioBufferId == RIO_INVALID_BUFFERID) { CxPlatLargeFree(RecvContext, Tag); @@ -3210,10 +2687,9 @@ RioRecvBufferFree( CXPLAT_DATAPATH_INTERNAL_RECV_CONTEXT* RecvContext = Entry; CXPLAT_DATAPATH_PROC* DatapathProc = CXPLAT_CONTAINING_RECORD(Pool, CXPLAT_DATAPATH_PROC, RioRecvPool); - CXPLAT_DATAPATH_INTERNAL* Datapath = DatapathProc->Datapath; CXPLAT_DBG_ASSERT(RecvContext->RioBufferId != RIO_INVALID_BUFFERID); - Datapath->RioDispatch.RIODeregisterBuffer(RecvContext->RioBufferId); + DatapathProc->Datapath->RioDispatch.RIODeregisterBuffer(RecvContext->RioBufferId); CxPlatLargeFree(RecvContext, Tag); } @@ -3260,7 +2736,7 @@ CxPlatSocketStartAccept( ) { QUIC_STATUS Status; - CXPLAT_DATAPATH_INTERNAL* Datapath = ListenerSocketProc->Parent->Datapath; + CXPLAT_DATAPATH* Datapath = ListenerSocketProc->Parent->Datapath; DWORD BytesRecv = 0; int Result; @@ -3270,7 +2746,7 @@ CxPlatSocketStartAccept( if (ListenerSocketProc->AcceptSocket == NULL) { Status = CxPlatSocketCreateTcpInternal( - Datapath, + ListenerSocketProc->Parent->Datapath, CXPLAT_SOCKET_TCP_SERVER, NULL, NULL, @@ -3434,7 +2910,7 @@ CxPlatDataPathSocketProcessAcceptCompletion( Error: if (ListenerSocketProc->AcceptSocket != NULL) { - MANGLE(CxPlatSocketDelete)(ListenerSocketProc->AcceptSocket); + MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET*)ListenerSocketProc->AcceptSocket); ListenerSocketProc->AcceptSocket = NULL; } @@ -3510,7 +2986,7 @@ CxPlatSocketStartRioReceives( { QUIC_STATUS Status; BOOLEAN NeedCommit = FALSE; - CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->Parent->Datapath; + CXPLAT_DATAPATH* Datapath = SocketProc->Parent->Datapath; while (SocketProc->RioRecvCount < RIO_RECV_QUEUE_DEPTH) { RIO_BUF Data = {0}; @@ -3611,7 +3087,7 @@ CxPlatSocketStartWinsockReceive( _Out_opt_ CXPLAT_DATAPATH_INTERNAL_RECV_CONTEXT** SyncRecvContext ) { - const CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->Parent->Datapath; + const CXPLAT_DATAPATH* Datapath = SocketProc->Parent->Datapath; CXPLAT_DBG_ASSERT((SyncIoResult != NULL) == (SyncBytesReceived != NULL)); CXPLAT_DBG_ASSERT((SyncIoResult != NULL) == (SyncRecvContext != NULL)); @@ -3820,9 +3296,8 @@ CxPlatDataPathUdpRecvComplete( CXPLAT_RECV_DATA* RecvDataChain = NULL; CXPLAT_RECV_DATA** DatagramChainTail = &RecvDataChain; - CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->Parent->Datapath; CXPLAT_RECV_DATA* Datagram; - PUCHAR RecvPayload = ((PUCHAR)RecvContext) + Datapath->RecvPayloadOffset; + PUCHAR RecvPayload = ((PUCHAR)RecvContext) + SocketProc->Parent->Datapath->RecvPayloadOffset; BOOLEAN FoundLocalAddr = FALSE; UINT16 MessageLength = NumberOfBytesTransferred; @@ -4034,7 +3509,6 @@ CxPlatDataPathSocketProcessRioCompletion( { UNREFERENCED_PARAMETER(Cqe); CXPLAT_SOCKET_PROC* SocketProc = CONTAINING_RECORD(Sqe, CXPLAT_SOCKET_PROC, RioSqe); - CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->DatapathProc->Datapath; ULONG ResultCount; BOOLEAN UpcallAcquired; ULONG TotalResultCount = 0; @@ -4048,7 +3522,7 @@ CxPlatDataPathSocketProcessRioCompletion( RIORESULT Results[32]; ResultCount = - Datapath->RioDispatch.RIODequeueCompletion( + SocketProc->DatapathProc->Datapath->RioDispatch.RIODequeueCompletion( SocketProc->RioCq, Results, RTL_NUMBER_OF(Results)); CXPLAT_FRE_ASSERT(ResultCount != RIO_CORRUPT_CQ); @@ -4168,7 +3642,6 @@ CxPlatDataPathTcpRecvComplete( CXPLAT_DBG_ASSERT(NumberOfBytesTransferred <= SocketProc->Parent->RecvBufLen); - CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->Parent->Datapath; CXPLAT_RECV_DATA* Data = (CXPLAT_RECV_DATA*)(RecvContext + 1); CXPLAT_DATAPATH_INTERNAL_RECV_BUFFER_CONTEXT* InternalDatagramContext = @@ -4176,12 +3649,13 @@ CxPlatDataPathTcpRecvComplete( InternalDatagramContext->RecvContext = RecvContext; Data->Next = NULL; - Data->Buffer = ((PUCHAR)RecvContext) + Datapath->RecvPayloadOffset; + Data->Buffer = ((PUCHAR)RecvContext) + SocketProc->Parent->Datapath->RecvPayloadOffset; Data->BufferLength = NumberOfBytesTransferred; Data->Route = &RecvContext->Route; Data->PartitionIndex = SocketProc->DatapathProc->IdealProcessor; Data->TypeOfService = 0; Data->Allocated = TRUE; + Data->BufferFrom = CXPLAT_BUFFER_FROM_USER; Data->QueuedOnConnection = FALSE; RecvContext->ReferenceCount++; RecvContext = NULL; @@ -4344,14 +3818,13 @@ RioSendDataAllocate( { CXPLAT_DATAPATH_PROC* DatapathProc = CXPLAT_CONTAINING_RECORD(Pool, CXPLAT_DATAPATH_PROC, RioSendDataPool); - CXPLAT_DATAPATH_INTERNAL* Datapath = DatapathProc->Datapath; CXPLAT_SEND_DATA_INTERNAL* SendData; SendData = CxPlatLargeAlloc(Size, Tag); if (SendData != NULL) { SendData->RioBufferId = - Datapath->RioDispatch.RIORegisterBuffer((char*)SendData, Size); + DatapathProc->Datapath->RioDispatch.RIORegisterBuffer((char*)SendData, Size); if (SendData->RioBufferId == RIO_INVALID_BUFFERID) { CxPlatLargeFree(SendData, Tag); SendData = NULL; @@ -4369,12 +3842,11 @@ RioSendDataFree( ) { CXPLAT_SEND_DATA_INTERNAL* SendData = Entry; - CXPLAT_DATAPATH_INTERNAL* Datapath = SendData->Owner->Datapath; UNREFERENCED_PARAMETER(Pool); CXPLAT_DBG_ASSERT(SendData->RioBufferId != RIO_INVALID_BUFFERID); - Datapath->RioDispatch.RIODeregisterBuffer(SendData->RioBufferId); + SendData->Owner->Datapath->RioDispatch.RIODeregisterBuffer(SendData->RioBufferId); CxPlatLargeFree(SendData, Tag); } @@ -4382,7 +3854,7 @@ _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* MANGLE(CxPlatSendDataAlloc)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) { @@ -4455,7 +3927,7 @@ RioSendBufferHeaderFromBuffer( void* RioSendBufferAllocateInternal( - CXPLAT_DATAPATH_INTERNAL* Datapath, + CXPLAT_DATAPATH* Datapath, _In_ uint32_t Size, _In_ uint32_t Tag ) @@ -4490,9 +3962,8 @@ RioSendBufferAllocate( { CXPLAT_DATAPATH_PROC* DatapathProc = CXPLAT_CONTAINING_RECORD(Pool, CXPLAT_DATAPATH_PROC, RioSendBufferPool); - CXPLAT_DATAPATH_INTERNAL* Datapath = DatapathProc->Datapath; - return RioSendBufferAllocateInternal(Datapath, Size, Tag); + return RioSendBufferAllocateInternal(DatapathProc->Datapath, Size, Tag); } void* @@ -4504,9 +3975,8 @@ RioSendLargeBufferAllocate( { CXPLAT_DATAPATH_PROC* DatapathProc = CXPLAT_CONTAINING_RECORD(Pool, CXPLAT_DATAPATH_PROC, RioLargeSendBufferPool); - CXPLAT_DATAPATH_INTERNAL* Datapath = DatapathProc->Datapath; - return RioSendBufferAllocateInternal(Datapath, Size, Tag); + return RioSendBufferAllocateInternal(DatapathProc->Datapath, Size, Tag); } void @@ -4517,12 +3987,11 @@ RioSendBufferFree( ) { CXPLAT_RIO_SEND_BUFFER_HEADER* RioHeader = RioSendBufferHeaderFromBuffer(Entry); - CXPLAT_DATAPATH_INTERNAL* Datapath = RioHeader->Datapath; UNREFERENCED_PARAMETER(Pool); CXPLAT_DBG_ASSERT(RioHeader->RioBufferId != RIO_INVALID_BUFFERID); - Datapath->RioDispatch.RIODeregisterBuffer(RioHeader->RioBufferId); + RioHeader->Datapath->RioDispatch.RIODeregisterBuffer(RioHeader->RioBufferId); CxPlatLargeFree(RioHeader, Tag); } @@ -4771,7 +4240,7 @@ CxPlatSocketSendWithRio( ) { CXPLAT_SOCKET_PROC* SocketProc = SendData->SocketProc; - CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->Parent->Datapath; + CXPLAT_DATAPATH* Datapath = SocketProc->Parent->Datapath; RIO_BUF RemoteAddr = {0}; RIO_BUF Control = {0}; @@ -4843,8 +4312,8 @@ CxPlatSocketSendInline( QUIC_STATUS Status; int Result; DWORD BytesSent; - CXPLAT_DATAPATH_INTERNAL* Datapath = SocketProc->Parent->Datapath; - CXPLAT_SOCKET_INTERNAL* Socket = SocketProc->Parent; + CXPLAT_DATAPATH* Datapath = SocketProc->Parent->Datapath; + CXPLAT_SOCKET* Socket = SocketProc->Parent; QuicTraceEvent( DatapathSend, @@ -4994,7 +4463,7 @@ CxPlatSocketSendEnqueue( _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS MANGLE(CxPlatSocketSend)( - _In_ CXPLAT_SOCKET_INTERNAL* Socket, + _In_ CXPLAT_SOCKET* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ) diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 42680a7e49..408e66b9fe 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -252,6 +252,367 @@ typedef struct DATAPATH_SQE { #endif } DATAPATH_SQE; +typedef struct CXPLAT_DATAPATH_PROC CXPLAT_DATAPATH_PROC; + +// +// Type of IO. +// +typedef enum DATAPATH_IO_TYPE { + DATAPATH_IO_SIGNATURE = 'WINU', + DATAPATH_IO_RECV = DATAPATH_IO_SIGNATURE + 1, + DATAPATH_IO_SEND = DATAPATH_IO_SIGNATURE + 2, + DATAPATH_IO_QUEUE_SEND = DATAPATH_IO_SIGNATURE + 3, + DATAPATH_IO_ACCEPTEX = DATAPATH_IO_SIGNATURE + 4, + DATAPATH_IO_CONNECTEX = DATAPATH_IO_SIGNATURE + 5, + DATAPATH_IO_RIO_NOTIFY = DATAPATH_IO_SIGNATURE + 6, + DATAPATH_IO_RIO_RECV = DATAPATH_IO_SIGNATURE + 7, + DATAPATH_IO_RIO_SEND = DATAPATH_IO_SIGNATURE + 8, + DATAPATH_IO_RECV_FAILURE = DATAPATH_IO_SIGNATURE + 9, + DATAPATH_IO_MAX +} DATAPATH_IO_TYPE; + +// +// IO header for SQE->CQE based completions. +// +typedef struct DATAPATH_IO_SQE { + DATAPATH_IO_TYPE IoType; + DATAPATH_SQE DatapathSqe; +} DATAPATH_IO_SQE; + +// +// Per-processor socket state. +// +typedef struct QUIC_CACHEALIGN CXPLAT_SOCKET_PROC { + // + // Used to synchronize clean up. + // + CXPLAT_REF_COUNT RefCount; + + // + // Submission queue event for IO completion + // + DATAPATH_IO_SQE IoSqe; + + // + // Submission queue event for RIO IO completion + // + DATAPATH_IO_SQE RioSqe; + + // + // The datapath per-processor context. + // + CXPLAT_DATAPATH_PROC* DatapathProc; + + // + // Parent CXPLAT_SOCKET. + // + CXPLAT_SOCKET* Parent; + + // + // Socket handle to the networking stack. + // + SOCKET Socket; + + // + // Rundown for synchronizing upcalls to the app and downcalls on the Socket. + // + CXPLAT_RUNDOWN_REF RundownRef; + + // + // Flag indicates the socket started processing IO. + // + BOOLEAN IoStarted : 1; + + // + // Flag indicates a persistent out-of-memory failure for the receive path. + // + BOOLEAN RecvFailure : 1; + + // + // Debug Flags + // + uint8_t Uninitialized : 1; + uint8_t Freed : 1; + + // + // The set of parameters/state passed to WsaRecvMsg for the IP stack to + // populate to indicate the result of the receive. + // + + union { + // + // Normal TCP/UDP socket data + // + struct { + RIO_CQ RioCq; + RIO_RQ RioRq; + ULONG RioRecvCount; + ULONG RioSendCount; + CXPLAT_LIST_ENTRY RioSendOverflow; + BOOLEAN RioNotifyArmed; + }; + // + // TCP Listener socket data + // + struct { + CXPLAT_SOCKET* AcceptSocket; + char AcceptAddrSpace[ + sizeof(SOCKADDR_INET) + 16 + + sizeof(SOCKADDR_INET) + 16 + ]; + }; + }; +} CXPLAT_SOCKET_PROC; + +// +// Represents a single IO completion port and thread for processing work that is +// completed on a single processor. +// +typedef struct QUIC_CACHEALIGN CXPLAT_DATAPATH_PROC { + + // + // Parent datapath. + // + CXPLAT_DATAPATH* Datapath; + + // + // Event queue used for processing work. + // + CXPLAT_EVENTQ* EventQ; + + // + // Used to synchronize clean up. + // + CXPLAT_REF_COUNT RefCount; + + // + // The index of ideal processor for this datapath. + // + uint16_t IdealProcessor; + + // + // Debug flags + // + uint8_t Uninitialized : 1; + + // + // Pool of send contexts to be shared by all sockets on this core. + // + CXPLAT_POOL SendDataPool; + + // + // Pool of send contexts to be shared by all RIO sockets on this core. + // + CXPLAT_POOL RioSendDataPool; + + // + // Pool of send buffers to be shared by all sockets on this core. + // + CXPLAT_POOL SendBufferPool; + + // + // Pool of large segmented send buffers to be shared by all sockets on this + // core. + // + CXPLAT_POOL LargeSendBufferPool; + + // + // Pool of send buffers to be shared by all RIO sockets on this core. + // + CXPLAT_POOL RioSendBufferPool; + + // + // Pool of large segmented send buffers to be shared by all RIO sockets on + // this core. + // + CXPLAT_POOL RioLargeSendBufferPool; + + // + // Pool of receive datagram contexts and buffers to be shared by all sockets + // on this core. + // + CXPLAT_POOL RecvDatagramPool; + + // + // Pool of RIO receive datagram contexts and buffers to be shared by all + // RIO sockets on this core. + // + CXPLAT_POOL RioRecvPool; + +} CXPLAT_DATAPATH_PROC; + +// +// Main structure for tracking all UDP abstractions. +// +typedef struct CXPLAT_DATAPATH { + CXPLAT_DATAPATH_BASE; + + // + // Function pointer to AcceptEx. + // + LPFN_ACCEPTEX AcceptEx; + + // + // Function pointer to ConnectEx. + // + LPFN_CONNECTEX ConnectEx; + + // + // Function pointer to WSASendMsg. + // + LPFN_WSASENDMSG WSASendMsg; + + // + // Function pointer to WSARecvMsg. + // + LPFN_WSARECVMSG WSARecvMsg; + + // + // Function pointer table for RIO. + // + RIO_EXTENSION_FUNCTION_TABLE RioDispatch; + + // + // Used to synchronize clean up. + // + CXPLAT_REF_COUNT RefCount; + + // + // Set of supported features. + // + uint32_t Features; + + // + // The size of each receive datagram array element, including client context, + // internal context, and padding. + // + uint32_t DatagramStride; + + // + // The offset of the receive payload buffer from the start of the receive + // context. + // + uint32_t RecvPayloadOffset; + + // + // The number of processors. + // + uint16_t ProcCount; + + // + // Maximum batch sizes supported for send. + // + uint8_t MaxSendBatchSize; + + // + // Uses RIO interface instead of normal asyc IO. + // + uint8_t UseRio : 1; + + // + // Debug flags + // + uint8_t Uninitialized : 1; + uint8_t Freed : 1; + + CXPLAT_DATAPATH_RAW* RawDataPath; + + // + // Per-processor completion contexts. + // + CXPLAT_DATAPATH_PROC Processors[0]; + +} CXPLAT_DATAPATH; + +// +// Per-port state. Multiple sockets are created on each port. +// +typedef struct CXPLAT_SOCKET { + CXPLAT_SOCKET_BASE; + + // + // Parent datapath. + // + // CXPLAT_DATAPATH_BASE* Datapath; + CXPLAT_DATAPATH* Datapath; + + // + // Client context pointer. + // + void *ClientContext; + + // + // Synchronization mechanism for cleanup. + // + CXPLAT_REF_COUNT RefCount; + + // + // The local interface's MTU. + // + uint16_t Mtu; + + // + // The size of a receive buffer's payload. + // + uint32_t RecvBufLen; + + // + // Socket type. + // + uint8_t Type : 2; // CXPLAT_SOCKET_TYPE + + // + // Flag indicates the socket has a default remote destination. + // + uint8_t HasFixedRemoteAddress : 1; + + // + // Flag indicates the socket indicated a disconnect event. + // + uint8_t DisconnectIndicated : 1; + + // + // Flag indicates the binding is being used for PCP. + // + uint8_t PcpBinding : 1; + + // + // Flag indicates the socket is using RIO instead of traditional Winsock. + // + uint8_t UseRio : 1; + + // + // Debug flags. + // TODO: Move to base + uint8_t Uninitialized : 1; + uint8_t Freed : 1; + + // + // Per-processor socket contexts. + // + CXPLAT_SOCKET_PROC Processors[0]; + +} CXPLAT_SOCKET; + +uint32_t +CxPlatGetRawSocketSize (); + +CXPLAT_SOCKET* +CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket); + +CXPLAT_SOCKET_RAW* +CxPlatSocketToRaw(CXPLAT_SOCKET* Socket); + +// +// Queries the raw datapath stack for the total size needed to allocate the +// datapath structure. +// +_IRQL_requires_max_(PASSIVE_LEVEL) +size_t +CxPlatDpRawGetDatapathSize( + _In_opt_ const QUIC_EXECUTION_CONFIG* Config + ); + #define CXPLAT_CQE_TYPE_WORKER_WAKE CXPLAT_CQE_TYPE_QUIC_BASE + 1 #define CXPLAT_CQE_TYPE_WORKER_UPDATE_POLL CXPLAT_CQE_TYPE_QUIC_BASE + 2 #define CXPLAT_CQE_TYPE_SOCKET_SHUTDOWN CXPLAT_CQE_TYPE_QUIC_BASE + 3 From 6b61e7c2f1cdecc018ef95860a466a8f604a73bc Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Fri, 28 Jul 2023 15:58:43 -0700 Subject: [PATCH 19/87] tmp --- src/core/send.c | 79 +++++++++++++++----------- src/inc/quic_datapath.h | 54 +++++++++++++++++- src/platform/datapath_raw.h | 7 +-- src/platform/datapath_raw_socket.c | 4 +- src/platform/datapath_raw_win.c | 2 +- src/platform/datapath_win.c | 73 ++++++++++++++---------- src/platform/datapath_winuser.c | 14 +++-- src/platform/unittest/DataPathTest.cpp | 2 +- src/test/lib/DataTest.cpp | 1 + 9 files changed, 155 insertions(+), 81 deletions(-) diff --git a/src/core/send.c b/src/core/send.c index 236d890c2b..7d322557cf 100644 --- a/src/core/send.c +++ b/src/core/send.c @@ -1049,28 +1049,35 @@ QuicSendPathChallenges( // We need to set the path challenge flag back on so that when route is resolved, // we know we need to continue to send the challenge. // - CXPLAT_DBG_ASSERT(Path->Route.State != RouteSuspected); - if (Path->Route.State == RouteUnresolved) { - QuicConnAddRef(Connection, QUIC_CONN_REF_ROUTE); - QUIC_STATUS Status = - CxPlatResolveRoute( - Path->Binding->Socket, &Path->Route, Path->ID, (void*)Connection, QuicConnQueueRouteCompletion); - if (Status == QUIC_STATUS_SUCCESS) { - QuicConnRelease(Connection, QUIC_CONN_REF_ROUTE); - } else { + // if raw datapath is available and the route is not loopback + if (CxPlatRawSocketAvailable(Path->Binding->Socket) && + !((Path->Route.RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET && + Path->Route.RemoteAddress.Ipv4.sin_addr.S_un.S_addr == htonl(INADDR_LOOPBACK)) || + (Path->Route.RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET6 && + IN6_IS_ADDR_LOOPBACK(&Path->Route.RemoteAddress.Ipv6.sin6_addr)))) { + CXPLAT_DBG_ASSERT(Path->Route.State != RouteSuspected); + if (Path->Route.State == RouteUnresolved) { + QuicConnAddRef(Connection, QUIC_CONN_REF_ROUTE); + QUIC_STATUS Status = + CxPlatResolveRoute( + Path->Binding->Socket, &Path->Route, Path->ID, (void*)Connection, QuicConnQueueRouteCompletion); + if (Status == QUIC_STATUS_SUCCESS) { + QuicConnRelease(Connection, QUIC_CONN_REF_ROUTE); + } else { + // + // Route resolution failed or pended. We need to pause sending. + // + CXPLAT_DBG_ASSERT(Status == QUIC_STATUS_PENDING || QUIC_FAILED(Status)); + Send->SendFlags |= QUIC_CONN_SEND_FLAG_PATH_CHALLENGE; + continue; + } + } else if (Path->Route.State == RouteResolving) { // - // Route resolution failed or pended. We need to pause sending. + // Can't send now. Once route resolution completes, we will resume sending. // - CXPLAT_DBG_ASSERT(Status == QUIC_STATUS_PENDING || QUIC_FAILED(Status)); Send->SendFlags |= QUIC_CONN_SEND_FLAG_PATH_CHALLENGE; continue; } - } else if (Path->Route.State == RouteResolving) { - // - // Can't send now. Once route resolution completes, we will resume sending. - // - Send->SendFlags |= QUIC_CONN_SEND_FLAG_PATH_CHALLENGE; - continue; } #endif @@ -1163,26 +1170,32 @@ QuicSendFlush( // // Make sure the route is resolved before sending packets. // - CXPLAT_DBG_ASSERT(Path->IsActive); - if (Path->Route.State == RouteUnresolved || Path->Route.State == RouteSuspected) { - QuicConnAddRef(Connection, QUIC_CONN_REF_ROUTE); - QUIC_STATUS Status = - CxPlatResolveRoute( - Path->Binding->Socket, &Path->Route, Path->ID, (void*)Connection, QuicConnQueueRouteCompletion); - if (Status == QUIC_STATUS_SUCCESS) { - QuicConnRelease(Connection, QUIC_CONN_REF_ROUTE); - } else { + if (CxPlatRawSocketAvailable(Path->Binding->Socket) && + !((Path->Route.RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET && + Path->Route.RemoteAddress.Ipv4.sin_addr.S_un.S_addr == htonl(INADDR_LOOPBACK)) || + (Path->Route.RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET6 && + IN6_IS_ADDR_LOOPBACK(&Path->Route.RemoteAddress.Ipv6.sin6_addr)))) { + CXPLAT_DBG_ASSERT(Path->IsActive); + if (Path->Route.State == RouteUnresolved || Path->Route.State == RouteSuspected) { + QuicConnAddRef(Connection, QUIC_CONN_REF_ROUTE); + QUIC_STATUS Status = + CxPlatResolveRoute( + Path->Binding->Socket, &Path->Route, Path->ID, (void*)Connection, QuicConnQueueRouteCompletion); + if (Status == QUIC_STATUS_SUCCESS) { + QuicConnRelease(Connection, QUIC_CONN_REF_ROUTE); + } else { + // + // Route resolution failed or pended. We need to pause sending. + // + CXPLAT_DBG_ASSERT(Status == QUIC_STATUS_PENDING || QUIC_FAILED(Status)); + return TRUE; + } + } else if (Path->Route.State == RouteResolving) { // - // Route resolution failed or pended. We need to pause sending. + // Can't send now. Once route resolution completes, we will resume sending. // - CXPLAT_DBG_ASSERT(Status == QUIC_STATUS_PENDING || QUIC_FAILED(Status)); return TRUE; } - } else if (Path->Route.State == RouteResolving) { - // - // Can't send now. Once route resolution completes, we will resume sending. - // - return TRUE; } #endif diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 4c413b009a..5d808a2e10 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -131,7 +131,6 @@ typedef struct CXPLAT_DATAPATH_BASE CXPLAT_DATAPATH_BASE; typedef struct CXPLAT_DATAPATH_RAW CXPLAT_DATAPATH_RAW; typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; - // // Represents a UDP or TCP abstraction. // @@ -174,6 +173,11 @@ typedef struct CXPLAT_SEND_DATA_INTERNAL CXPLAT_SEND_DATA_INTERNAL; typedef struct CXPLAT_SEND_DATA { CXPLAT_BUFFER_FROM BufferFrom : 2; + + // + // The type of ECN markings needed for send. + // + uint8_t ECN; // CXPLAT_ECN_TYPE } CXPLAT_SEND_DATA; // @@ -640,6 +644,19 @@ CxPlatSocketDelete( _In_ CXPLAT_SOCKET* Socket ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +BOOLEAN +CxPlatRawDataPathAvailable( + _In_ CXPLAT_DATAPATH* Datapath + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +BOOLEAN +CxPlatRawSocketAvailable( + _In_ CXPLAT_SOCKET* Socket + ); + // // Plumbs new or removes existing QUIC encryption offload information. // @@ -965,6 +982,41 @@ XDP_CxPlatSendDataAlloc( _Inout_ CXPLAT_SEND_CONFIG* Config ); +_IRQL_requires_max_(DISPATCH_LEVEL) +void +XDP_CxPlatSendDataFree( + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +QUIC_BUFFER* +XDP_CxPlatSendDataAllocBuffer( + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ uint16_t MaxBufferLength + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +XDP_CxPlatSendDataFreeBuffer( + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ QUIC_BUFFER* Buffer + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +BOOLEAN +XDP_CxPlatSendDataIsFull( + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +QUIC_STATUS +XDP_CxPlatSocketSend( + _In_ CXPLAT_SOCKET_RAW* Socket, + _In_ const CXPLAT_ROUTE* Route, + _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + ); + #if defined(__cplusplus) } #endif diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index 61366cc18e..405528abee 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -85,11 +85,6 @@ typedef struct CXPLAT_INTERFACE { typedef struct CXPLAT_SEND_DATA_INTERNAL { CXPLAT_SEND_DATA; - // - // The type of ECN markings needed for send. - // - CXPLAT_ECN_TYPE ECN; - QUIC_BUFFER Buffer; } CXPLAT_SEND_DATA_INTERNAL; @@ -272,7 +267,7 @@ typedef struct CXPLAT_SOCKET_RAW { CXPLAT_SEND_DATA_INTERNAL* PausedTcpSend; // Paused TCP send data *before* framing CXPLAT_SEND_DATA_INTERNAL* CachedRstSend; // Cached TCP RST send data *after* framing - CXPLAT_SOCKET; // _INTRENAL? + CXPLAT_SOCKET; } CXPLAT_SOCKET_RAW; BOOLEAN diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index fbb4082830..c0a7899af1 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -26,12 +26,12 @@ CxPlatGetRawSocketSize () { // TODO: rename CXPLAT_SOCKET* CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket) { - return (CXPLAT_SOCKET*)(Socket + 1); + return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); } // TODO: rename CXPLAT_SOCKET_RAW* CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { - return (CXPLAT_SOCKET_RAW*)Socket - 1; + return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); } CXPLAT_SOCKET_RAW* diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index 7766366821..90eeb8083b 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -397,7 +397,7 @@ CxPlatRawSocketDelete( CxPlatDpRawTxEnqueue(Socket->CachedRstSend); } - CXPLAT_FREE(Socket, QUIC_POOL_SOCKET); + // CXPLAT_FREE(Socket, QUIC_POOL_SOCKET); } _IRQL_requires_max_(PASSIVE_LEVEL) diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 2414535a44..855f260595 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -42,6 +42,24 @@ CxPlatDataPathRecvDataToRecvPacket( // use global variable to store the offset? set at init phase } +_IRQL_requires_max_(PASSIVE_LEVEL) +BOOLEAN +CxPlatRawDataPathAvailable( + _In_ CXPLAT_DATAPATH* Datapath + ) +{ + return Datapath->RawDataPath != NULL; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +BOOLEAN +CxPlatRawSocketAvailable( + _In_ CXPLAT_SOCKET* Socket + ) +{ + return Socket->Datapath && CxPlatRawDataPathAvailable(Socket->Datapath); +} + _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatDataPathInitialize( @@ -52,12 +70,6 @@ CxPlatDataPathInitialize( _Out_ CXPLAT_DATAPATH** NewDataPath ) { - // allocate for CXPLAT_DATAPATH as Datapath - // call CxPlatDataPathInitialize with Datapath - // allocate for CXPLAT_DATAPATH_RAW as RawDatapath - // RawDatapath->ParentDatapath = Datapath - // call CxPlatInitRawDataPath with RawDatapath - // Init all Datapath // QUIC_STATUS Status = QUIC_STATUS_SUCCESS; @@ -133,7 +145,10 @@ CxPlatDataPathInitialize( DataPath, RawDataPath); if (QUIC_FAILED(Status)) { - goto Error; + Status = QUIC_STATUS_SUCCESS; + CXPLAT_FREE(RawDataPath, QUIC_POOL_DATAPATH); + RawDataPath = NULL; + // TODO: log } DataPath->RawDataPath = RawDataPath; @@ -151,15 +166,10 @@ CxPlatDataPathUninitialize( _In_ CXPLAT_DATAPATH* Datapath ) { - DataPathUserFuncs.CxPlatDataPathUninitialize(Datapath); if (Datapath->RawDataPath) { XDP_CxPlatDataPathUninitialize(Datapath->RawDataPath); } - - // if (Datapath->Xdp) { - // // DataPathXdpFuncs.CxPlatDataPathUninitialize(Datapath->User); - // } - // CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); + DataPathUserFuncs.CxPlatDataPathUninitialize(Datapath); } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -173,9 +183,6 @@ CxPlatDataPathUpdateConfig( if (Datapath->RawDataPath) { XDP_CxPlatDataPathUpdateConfig(Datapath->RawDataPath, Config); } - // if (Datapath->Xdp) { - // // DataPathXdpFuncs.CxPlatDataPathUpdateConfig(Datapath->Xdp, Config); - // } } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -357,6 +364,7 @@ CxPlatSocketCreateUdp( // Call CxPlatInitRawSocket with NewSocket as Raw BOOLEAN IsServerSocket = Config->RemoteAddress == NULL; uint16_t SocketCount = IsServerSocket ? Datapath->ProcCount : 1; + // TODO: check Datapath->RawDataPath and shrink allocation size uint32_t RawSocketLength = CxPlatGetRawSocketSize() + SocketCount * sizeof(CXPLAT_SOCKET_PROC); CXPLAT_SOCKET_RAW* RawSocket = CXPLAT_ALLOC_PAGED(RawSocketLength, QUIC_POOL_SOCKET); if (RawSocket == NULL) { @@ -388,14 +396,14 @@ CxPlatSocketCreateUdp( goto Error; } - if (Datapath) { + if (Datapath->RawDataPath) { Status = CxPlatInitRawSocket( Datapath->RawDataPath, Config, RawSocket); if (QUIC_FAILED(Status)) { - // just ignore with logging? - goto Error; + Status = QUIC_STATUS_SUCCESS; + // TODO: logging } } @@ -444,9 +452,12 @@ CxPlatSocketDelete( ) { // TODO: bubble up common logic - CxPlatRawSocketDelete(CxPlatSocketToRaw(Socket)); + if (Socket->Datapath && Socket->Datapath->RawDataPath) { + CxPlatRawSocketDelete(CxPlatSocketToRaw(Socket)); + } + // TODO: want to free socket here DataPathUserFuncs.CxPlatSocketDelete(Socket); - // free(Socket); + CXPLAT_FREE(CxPlatSocketToRaw(Socket), QUIC_POOL_SOCKET); } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -558,9 +569,9 @@ CxPlatSendDataFree( ) { if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { - + DataPathUserFuncs.CxPlatSendDataFree((CXPLAT_SEND_DATA_INTERNAL*)SendData); } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { - + XDP_CxPlatSendDataFree((CXPLAT_SEND_DATA_INTERNAL*)SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -575,9 +586,9 @@ CxPlatSendDataAllocBuffer( ) { if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { - + return DataPathUserFuncs.CxPlatSendDataAllocBuffer((CXPLAT_SEND_DATA_INTERNAL*)SendData, MaxBufferLength); } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { - + return XDP_CxPlatSendDataAllocBuffer((CXPLAT_SEND_DATA_INTERNAL*)SendData, MaxBufferLength); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -592,9 +603,9 @@ CxPlatSendDataFreeBuffer( ) { if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { - + DataPathUserFuncs.CxPlatSendDataFreeBuffer((CXPLAT_SEND_DATA_INTERNAL*)SendData, Buffer); } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { - + XDP_CxPlatSendDataFreeBuffer((CXPLAT_SEND_DATA_INTERNAL*)SendData, Buffer); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -607,9 +618,9 @@ CxPlatSendDataIsFull( ) { if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { - + return DataPathUserFuncs.CxPlatSendDataIsFull((CXPLAT_SEND_DATA_INTERNAL*)SendData); } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { - + return XDP_CxPlatSendDataIsFull((CXPLAT_SEND_DATA_INTERNAL*)SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -625,9 +636,9 @@ CxPlatSocketSend( ) { if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { - + return DataPathUserFuncs.CxPlatSocketSend(Socket, Route, (CXPLAT_SEND_DATA_INTERNAL*)SendData); } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { - + return XDP_CxPlatSocketSend(CxPlatSocketToRaw(Socket), Route, (CXPLAT_SEND_DATA_INTERNAL*)SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 985eb15164..494696c6b5 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -242,10 +242,10 @@ typedef struct CXPLAT_SEND_DATA_INTERNAL { // uint16_t SegmentSize; - // - // The type of ECN markings needed for send. - // - uint8_t ECN; // CXPLAT_ECN_TYPE + // // + // // The type of ECN markings needed for send. + // // + // uint8_t ECN; // CXPLAT_ECN_TYPE // // Set of flags set to configure the send behavior. @@ -2474,7 +2474,7 @@ CxPlatSocketRelease( CXPLAT_DBG_ASSERT(!Socket->Freed); CXPLAT_DBG_ASSERT(Socket->Uninitialized); Socket->Freed = TRUE; - CXPLAT_FREE(Socket, QUIC_POOL_SOCKET); + // CXPLAT_FREE(Socket, QUIC_POOL_SOCKET); } } @@ -2522,7 +2522,7 @@ CxPlatSocketContextRelease( } SocketProc->Freed = TRUE; - CxPlatSocketRelease((CXPLAT_SOCKET*)SocketProc->Parent); + CxPlatSocketRelease((CXPLAT_SOCKET*)SocketProc->Parent); // } } @@ -2618,6 +2618,7 @@ MANGLE(CxPlatSocketUpdateQeo)( return QUIC_STATUS_NOT_SUPPORTED; } +// TODO: remove? _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 MANGLE(CxPlatSocketGetLocalMtu)( @@ -3397,6 +3398,7 @@ CxPlatDataPathUdpRecvComplete( Datagram->PartitionIndex = SocketProc->DatapathProc->IdealProcessor; Datagram->TypeOfService = (uint8_t)ECN; Datagram->Allocated = TRUE; + Datagram->BufferFrom = CXPLAT_BUFFER_FROM_USER; Datagram->QueuedOnConnection = FALSE; RecvPayload += MessageLength; diff --git a/src/platform/unittest/DataPathTest.cpp b/src/platform/unittest/DataPathTest.cpp index a144a4b4ac..d5cfa0875c 100644 --- a/src/platform/unittest/DataPathTest.cpp +++ b/src/platform/unittest/DataPathTest.cpp @@ -536,7 +536,7 @@ struct CxPlatSocket { CxPlatSocketGetLocalAddress(Socket, &Route.LocalAddress); CxPlatSocketGetRemoteAddress(Socket, &Route.RemoteAddress); #ifdef QUIC_USE_RAW_DATAPATH - if (!QuicAddrIsWildCard(&Route.RemoteAddress)) { + if (CxPlatRawDataPathAvailable(Datapath) && QuicAddrIsWildCard(&Route.RemoteAddress)) { // // This is a connected socket and its route must be resolved // to be able to send traffic. diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index e451177a82..fc6c4d1aec 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -514,6 +514,7 @@ QuicTestConnectAndPing( Connections.get()[i]->SetLocalAddr(LocalAddr); } #endif + CxPlatSleep(10000); TEST_QUIC_SUCCEEDED( Connections.get()[i]->Start( ClientConfiguration, From e3e866251244fa828ba470970c34889ef099f169 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 1 Aug 2023 15:41:46 -0700 Subject: [PATCH 20/87] add preview_feature flag back --- src/inc/msquic.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/inc/msquic.h b/src/inc/msquic.h index 573d17bbd8..ad9c5b4a14 100644 --- a/src/inc/msquic.h +++ b/src/inc/msquic.h @@ -824,7 +824,9 @@ void #define QUIC_PARAM_GLOBAL_VERSION_SETTINGS 0x01000007 // QUIC_VERSION_SETTINGS #endif #define QUIC_PARAM_GLOBAL_LIBRARY_GIT_HASH 0x01000008 // char[64] +#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES #define QUIC_PARAM_GLOBAL_EXECUTION_CONFIG 0x01000009 // QUIC_EXECUTION_CONFIG +#endif #define QUIC_PARAM_GLOBAL_TLS_PROVIDER 0x0100000A // QUIC_TLS_PROVIDER // From 7a3114dd20f66625eb5c1ba86c5bba8a2539f2a5 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 1 Aug 2023 15:45:40 -0700 Subject: [PATCH 21/87] refactoring CxPlatIsRouteReady --- src/core/send.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/send.c b/src/core/send.c index 074788d1cc..240d952e78 100644 --- a/src/core/send.c +++ b/src/core/send.c @@ -1043,6 +1043,7 @@ CxPlatIsRouteReady( Path->Binding->Socket, &Path->Route, Path->ID, (void*)Connection, QuicConnQueueRouteCompletion); if (Status == QUIC_STATUS_SUCCESS) { QuicConnRelease(Connection, QUIC_CONN_REF_ROUTE); + return TRUE; } else { // // Route resolution failed or pended. We need to pause sending. @@ -1050,13 +1051,12 @@ CxPlatIsRouteReady( CXPLAT_DBG_ASSERT(Status == QUIC_STATUS_PENDING || QUIC_FAILED(Status)); return FALSE; } - } else if (Path->Route.State == RouteResolving) { - // - // Can't send now. Once route resolution completes, we will resume sending. - // - return FALSE; } - return TRUE; + // + // Path->Route.State == RouteResolving + // Can't send now. Once route resolution completes, we will resume sending. + // + return FALSE; } // From 27d5ec46175ff2841886f34443ae60f90e8a2ded Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 1 Aug 2023 16:18:39 -0700 Subject: [PATCH 22/87] fix linux code check --- src/core/send.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/send.c b/src/core/send.c index 240d952e78..be356fce8c 100644 --- a/src/core/send.c +++ b/src/core/send.c @@ -1043,14 +1043,13 @@ CxPlatIsRouteReady( Path->Binding->Socket, &Path->Route, Path->ID, (void*)Connection, QuicConnQueueRouteCompletion); if (Status == QUIC_STATUS_SUCCESS) { QuicConnRelease(Connection, QUIC_CONN_REF_ROUTE); - return TRUE; } else { // // Route resolution failed or pended. We need to pause sending. // CXPLAT_DBG_ASSERT(Status == QUIC_STATUS_PENDING || QUIC_FAILED(Status)); - return FALSE; } + return Status == QUIC_STATUS_SUCCESS; } // // Path->Route.State == RouteResolving From 6472fc5035b6d7a8b9d78cea141406263b85fdec Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 1 Aug 2023 16:51:09 -0700 Subject: [PATCH 23/87] adjust func names --- src/platform/datapath_raw_socket.c | 2 +- src/platform/datapath_win.c | 15 +++++++++++---- src/platform/datapath_winuser.c | 8 ++++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index 6a17a2cb88..1880833c03 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -62,7 +62,7 @@ CxPlatGetSocket( _IRQL_requires_max_(PASSIVE_LEVEL) void -QuicCopyRouteInfo( +MANGLE(QuicCopyRouteInfo)( _Inout_ CXPLAT_ROUTE* DstRoute, _In_ CXPLAT_ROUTE* SrcRoute ) diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 855f260595..5bb6e7d815 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -654,10 +654,17 @@ CxPlatDataPathProcessCqe( DataPathUserFuncs.CxPlatDataPathProcessCqe(Cqe); } +_IRQL_requires_max_(PASSIVE_LEVEL) +void +QuicCopyRouteInfo( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ + // TODO: route to user/raw + *DstRoute = *SrcRoute; +} - -// TODO: remove ifdef -#ifdef QUIC_USE_RAW_DATAPATH void CxPlatResolveRouteComplete( _In_ void* Connection, @@ -693,4 +700,4 @@ CxPlatUpdateRoute( ) { } -#endif // QUIC_USE_RAW_DATAPATH + diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index ee01e8477a..b8e1867d9d 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -4624,7 +4624,7 @@ MANGLE(CxPlatDataPathProcessCqe)( _IRQL_requires_max_(PASSIVE_LEVEL) void -QuicCopyRouteInfo( +MANGLE(QuicCopyRouteInfo)( _Inout_ CXPLAT_ROUTE* DstRoute, _In_ CXPLAT_ROUTE* SrcRoute ) @@ -4633,7 +4633,7 @@ QuicCopyRouteInfo( } void -CxPlatResolveRouteComplete( +MANGLE(CxPlatResolveRouteComplete)( _In_ void* Context, _Inout_ CXPLAT_ROUTE* Route, _In_reads_bytes_(6) const uint8_t* PhysicalAddress, @@ -4648,7 +4648,7 @@ CxPlatResolveRouteComplete( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatResolveRoute( +MANGLE(CxPlatResolveRoute)( _In_ CXPLAT_SOCKET* Socket, _Inout_ CXPLAT_ROUTE* Route, _In_ uint8_t PathId, @@ -4666,7 +4666,7 @@ CxPlatResolveRoute( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatUpdateRoute( +MANGLE(CxPlatUpdateRoute)( _Inout_ CXPLAT_ROUTE* DstRoute, _In_ CXPLAT_ROUTE* SrcRoute ) From 72310bfd21b368d32930f16f0973d32aab895a15 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 1 Aug 2023 17:00:44 -0700 Subject: [PATCH 24/87] fix comments --- src/core/send.c | 11 +++++------ src/test/bin/quic_gtest.cpp | 10 ++++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/core/send.c b/src/core/send.c index be356fce8c..48799b801a 100644 --- a/src/core/send.c +++ b/src/core/send.c @@ -1043,13 +1043,12 @@ CxPlatIsRouteReady( Path->Binding->Socket, &Path->Route, Path->ID, (void*)Connection, QuicConnQueueRouteCompletion); if (Status == QUIC_STATUS_SUCCESS) { QuicConnRelease(Connection, QUIC_CONN_REF_ROUTE); - } else { - // - // Route resolution failed or pended. We need to pause sending. - // - CXPLAT_DBG_ASSERT(Status == QUIC_STATUS_PENDING || QUIC_FAILED(Status)); + return TRUE; } - return Status == QUIC_STATUS_SUCCESS; + // + // Route resolution failed or pended. We need to pause sending. + // + CXPLAT_DBG_ASSERT(Status == QUIC_STATUS_PENDING || QUIC_FAILED(Status)); } // // Path->Route.State == RouteResolving diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 87d50b3b3b..0e5214c774 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -1711,7 +1711,8 @@ TEST_P(WithSend0RttArgs1, Send0Rtt) { #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { // - // NAT rebind doesn't make sense for TCP and QTIP. + // QTIP doesn't work with 0-RTT. QTIP only pauses and caches 1 packet during + // TCP handshake. // return; } @@ -1759,7 +1760,8 @@ TEST_P(WithSend0RttArgs2, Reject0Rtt) { #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { // - // NAT rebind doesn't make sense for TCP and QTIP. + // QTIP doesn't work with 0-RTT. QTIP only pauses and caches 1 packet during + // TCP handshake. // return; } @@ -2383,8 +2385,8 @@ int main(int argc, char** argv) { #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) UseQTIP = true; #else - printf("QTIP is not supported in this build.\n"); - return -1; + printf("QTIP is not supported in this build.\n"); + return -1; #endif } else if (strstr(argv[i], "--osRunner")) { OsRunner = argv[i] + sizeof("--osRunner"); From 432a364f88720b34a4b87cedbea5dd0e2b4896e5 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 2 Aug 2023 10:38:39 -0700 Subject: [PATCH 25/87] fix IsRouteReady and clean ifdef for _KERNEL_MODE --- src/core/send.c | 8 ++++---- src/test/lib/ApiTest.cpp | 4 +++- src/test/lib/DataTest.cpp | 8 +++++--- src/test/lib/MtuTest.cpp | 8 +++++--- src/test/lib/TestHelpers.h | 20 ++++++++++++++++++++ 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/core/send.c b/src/core/send.c index 48799b801a..59c583e225 100644 --- a/src/core/send.c +++ b/src/core/send.c @@ -1020,10 +1020,10 @@ QuicSendGetNextStream( BOOLEAN CxPlatIsRouteReady( - _In_ QUIC_CONNECTION *Connection + _In_ QUIC_CONNECTION *Connection, + _In_ QUIC_PATH* Path ) { - QUIC_PATH* Path = &Connection->Paths[0]; // // Make sure the route is resolved before sending packets. // @@ -1081,7 +1081,7 @@ QuicSendPathChallenges( continue; } - if (!CxPlatIsRouteReady(Connection)) { + if (!CxPlatIsRouteReady(Connection, Path)) { Send->SendFlags |= QUIC_CONN_SEND_FLAG_PATH_CHALLENGE; continue; } @@ -1171,7 +1171,7 @@ QuicSendFlush( CXPLAT_DBG_ASSERT(!Connection->State.HandleClosed); - if (!CxPlatIsRouteReady(Connection)) { + if (!CxPlatIsRouteReady(Connection, Path)) { return TRUE; } diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index 0bf5f6ef25..0080434034 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -16,7 +16,9 @@ #pragma warning(disable:6387) // '_Param_(1)' could be '0': this does not adhere to the specification for the function -#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(_KERNEL_MODE) +bool UseQTIP = false; +#elif defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index f809a2c80d..4f7815c1ee 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -14,7 +14,9 @@ #include "DataTest.cpp.clog.h" #endif -#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(_KERNEL_MODE) +bool UseQTIP = false; +#elif defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif @@ -506,7 +508,7 @@ QuicTestConnectAndPing( TEST_QUIC_SUCCEEDED(Connections.get()[i]->SetRemoteAddr(RemoteAddr)); if (i != 0 -#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) && (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && !UseQTIP) #endif ) { @@ -521,7 +523,7 @@ QuicTestConnectAndPing( ServerLocalAddr.GetPort())); if (i == 0 -#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) && (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && !UseQTIP) #endif ) { diff --git a/src/test/lib/MtuTest.cpp b/src/test/lib/MtuTest.cpp index 98a605825b..c1db67a162 100644 --- a/src/test/lib/MtuTest.cpp +++ b/src/test/lib/MtuTest.cpp @@ -14,7 +14,9 @@ #include "MtuTest.cpp.clog.h" #endif -#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(_KERNEL_MODE) +bool UseQTIP = false; +#elif defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif @@ -49,7 +51,7 @@ struct ResetSettings { void QuicTestMtuSettings() { -#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) const uint16_t DefaultMaximumMtu = (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP)? 1488 : 1500; #else const uint16_t DefaultMaximumMtu = 1500; @@ -315,7 +317,7 @@ QuicTestMtuDiscovery( TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); const uint16_t MinimumMtu = RaiseMinimumMtu ? 1360 : 1248; -#if !defined(_KERNEL_MODE) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) const uint16_t MaximumMtu = (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP)? 1488 : 1500; #else const uint16_t MaximumMtu = 1500; diff --git a/src/test/lib/TestHelpers.h b/src/test/lib/TestHelpers.h index 675dda3bb9..5de1fa8793 100644 --- a/src/test/lib/TestHelpers.h +++ b/src/test/lib/TestHelpers.h @@ -46,6 +46,26 @@ QuicAddrSetToDuoNic( } } +#ifdef _KERNEL_MODE +uint32_t +QuitTestGetDatapathFeatureFlags() { + static uint32_t Length = sizeof(uint32_t); + uint32_t Features = 0; + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + &Features); + return Features; +} + +bool +QuitTestIsFeatureSupported(uint32_t Feature) { + return static_cast(QuitTestGetDatapathFeatureFlags() & Feature); +} + +#endif // _KERNEL_MODE + #include "msquic.hpp" #include "quic_toeplitz.h" From be4e91ada8b67e426d9ba080382bf0c018c02ff1 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 2 Aug 2023 11:06:27 -0700 Subject: [PATCH 26/87] kernel build error --- src/test/lib/ApiTest.cpp | 2 ++ src/test/lib/DataTest.cpp | 2 ++ src/test/lib/MtuTest.cpp | 2 ++ src/test/lib/TestHelpers.h | 20 -------------------- 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index 0080434034..26ca2ee2e2 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -18,6 +18,8 @@ #if defined(_KERNEL_MODE) bool UseQTIP = false; +// currently x is only CXPLAT_DATAPATH_FEATURE_RAW +#define QuitTestIsFeatureSupported(x) false #elif defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index 4f7815c1ee..ee5319ff6c 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -16,6 +16,8 @@ #if defined(_KERNEL_MODE) bool UseQTIP = false; +// currently x is only CXPLAT_DATAPATH_FEATURE_RAW +#define QuitTestIsFeatureSupported(x) false #elif defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif diff --git a/src/test/lib/MtuTest.cpp b/src/test/lib/MtuTest.cpp index c1db67a162..1afc7a6f15 100644 --- a/src/test/lib/MtuTest.cpp +++ b/src/test/lib/MtuTest.cpp @@ -16,6 +16,8 @@ #if defined(_KERNEL_MODE) bool UseQTIP = false; +// currently x is only CXPLAT_DATAPATH_FEATURE_RAW +#define QuitTestIsFeatureSupported(x) false #elif defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif diff --git a/src/test/lib/TestHelpers.h b/src/test/lib/TestHelpers.h index 5de1fa8793..675dda3bb9 100644 --- a/src/test/lib/TestHelpers.h +++ b/src/test/lib/TestHelpers.h @@ -46,26 +46,6 @@ QuicAddrSetToDuoNic( } } -#ifdef _KERNEL_MODE -uint32_t -QuitTestGetDatapathFeatureFlags() { - static uint32_t Length = sizeof(uint32_t); - uint32_t Features = 0; - MsQuic->GetParam( - nullptr, - QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, - &Length, - &Features); - return Features; -} - -bool -QuitTestIsFeatureSupported(uint32_t Feature) { - return static_cast(QuitTestGetDatapathFeatureFlags() & Feature); -} - -#endif // _KERNEL_MODE - #include "msquic.hpp" #include "quic_toeplitz.h" From 2cb2b535303a76261e4d87952c19d4a9a6ee95a1 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 2 Aug 2023 15:18:31 -0700 Subject: [PATCH 27/87] Set RouteResolved for Rx --- src/platform/datapath_epoll.c | 2 ++ src/platform/datapath_kqueue.c | 1 + src/platform/datapath_winkernel.c | 1 + src/platform/datapath_winuser.c | 1 + src/test/bin/quic_gtest.cpp | 6 ++++++ src/test/bin/quic_gtest.h | 12 +----------- src/test/lib/EventTest.cpp | 5 +++++ 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/platform/datapath_epoll.c b/src/platform/datapath_epoll.c index 5e0b331632..cec3d9f7be 100644 --- a/src/platform/datapath_epoll.c +++ b/src/platform/datapath_epoll.c @@ -1948,6 +1948,7 @@ CxPlatSocketReceiveCoalesced( } RecvBlock->OwningPool = &DatapathProc->RecvBlockPool; + RecvBlock->Route.State = RouteResolved; struct msghdr* MsgHdr = &RecvMsgHdr.msg_hdr; MsgHdr->msg_name = &RecvBlock->Route.RemoteAddress; @@ -2022,6 +2023,7 @@ CxPlatSocketReceiveMessages( RecvBlocks[i] = RecvBlock; RecvBlock->OwningPool = &DatapathProc->RecvBlockPool; + RecvBlock->Route.State = RouteResolved; struct msghdr* MsgHdr = &RecvMsgHdr[i].msg_hdr; MsgHdr->msg_name = &RecvBlock->Route.RemoteAddress; diff --git a/src/platform/datapath_kqueue.c b/src/platform/datapath_kqueue.c index bcb377e97b..9a345c7321 100644 --- a/src/platform/datapath_kqueue.c +++ b/src/platform/datapath_kqueue.c @@ -616,6 +616,7 @@ CxPlatDataPathAllocRecvBlock( 0); } else { CxPlatZeroMemory(RecvBlock, sizeof(*RecvBlock)); + RecvBlock->Route.State = RouteResolved; RecvBlock->OwningPool = &DatapathProc->RecvBlockPool; RecvBlock->RecvPacket.Buffer = RecvBlock->Buffer; RecvBlock->RecvPacket.Allocated = TRUE; diff --git a/src/platform/datapath_winkernel.c b/src/platform/datapath_winkernel.c index 6ee2888310..0f40959b2e 100644 --- a/src/platform/datapath_winkernel.c +++ b/src/platform/datapath_winkernel.c @@ -2119,6 +2119,7 @@ CxPlatSocketAllocRecvContext( CXPLAT_DATAPATH_INTERNAL_RECV_CONTEXT* InternalContext = CxPlatPoolAlloc(Pool); if (InternalContext != NULL) { + InternalContext->Route.State = RouteResolved; InternalContext->DatagramPoolIndex = IsUro; InternalContext->ProcContext = &Datapath->ProcContexts[ProcIndex]; InternalContext->DataBufferStart = NULL; diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 9141762fc1..2c5ab93ded 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -3227,6 +3227,7 @@ CxPlatSocketAllocRecvContext( RecvContext = CxPlatPoolAlloc(OwningPool); if (RecvContext != NULL) { + RecvContext->Route.State = RouteResolved; RecvContext->OwningPool = OwningPool; RecvContext->ReferenceCount = 0; RecvContext->SocketProc = SocketProc; diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 0e5214c774..27a0e05be8 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -1629,6 +1629,12 @@ TEST_P(WithSendArgs1, Send) { TEST_P(WithSendArgs2, SendLarge) { TestLoggerT Logger("QuicTestConnectAndPing", GetParam()); +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP && GetParam().UseZeroRtt) { + return; + } +#endif + if (TestingKernelMode) { QUIC_RUN_CONNECT_AND_PING_PARAMS Params = { GetParam().Family, diff --git a/src/test/bin/quic_gtest.h b/src/test/bin/quic_gtest.h index 86badfe0b2..7e95000110 100644 --- a/src/test/bin/quic_gtest.h +++ b/src/test/bin/quic_gtest.h @@ -364,11 +364,6 @@ struct SendArgs2 { for (bool UseZeroRtt : { false }) #endif { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP && UseZeroRtt) { - continue; - } -#endif list.push_back({ Family, UseSendBuffer, UseZeroRtt }); } return list; @@ -715,12 +710,7 @@ struct ValidateConnectionEventArgs { uint32_t Test; static ::std::vector Generate() { ::std::vector list; - uint32_t TestCount = 3; - -#if !defined(QUIC_DISABLE_0RTT_TESTS) // TODO: Fix openssl/XDP bug and enable this back - TestCount = QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) ? 3 : 2; -#endif - for (uint32_t Test = 0; Test < TestCount; ++Test) + for (uint32_t Test = 0; Test < 3; ++Test) list.push_back({ Test }); return list; } diff --git a/src/test/lib/EventTest.cpp b/src/test/lib/EventTest.cpp index 46548a74ce..68f3b7abd0 100644 --- a/src/test/lib/EventTest.cpp +++ b/src/test/lib/EventTest.cpp @@ -453,6 +453,11 @@ QuicTestValidateConnectionEvents3( _In_ QuicAddr& ServerLocalAddr ) { +#if !defined(QUIC_DISABLE_0RTT_TESTS) // TODO: Fix openssl/XDP bug and enable this back + if (!QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { + return; + } +#endif TestScopeLogger ScopeLogger(__FUNCTION__); MsQuicSettings Settings; From f791cc8c3742471086f88986d9f5c731486b3a30 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 2 Aug 2023 17:13:41 -0700 Subject: [PATCH 28/87] fix more tests --- src/test/lib/ApiTest.cpp | 28 ++++++++++++++++++++++++---- src/test/lib/EventTest.cpp | 14 +++++++++----- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index 26ca2ee2e2..19cfd50046 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -2135,6 +2135,28 @@ void QuicTestStatefulGlobalSetParam() sizeof(Mode), &Mode)); } + + { + TestScopeLogger LogScope1("Get QUIC_PARAM_GLOBAL_DATAPATH_FEATURES after Datapath is made (MsQuicLib.Datapath)"); + uint32_t Length = 0; + TEST_QUIC_STATUS( + QUIC_STATUS_BUFFER_TOO_SMALL, + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + nullptr)); + TEST_EQUAL(Length, sizeof(uint32_t)); + + uint32_t ActualFeatures = 0; + TEST_QUIC_SUCCEEDED( + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + &ActualFeatures)); + TEST_NOT_EQUAL(ActualFeatures, 0); + } } void QuicTestGlobalParam() @@ -2473,7 +2495,6 @@ void QuicTestGlobalParam() // { TestScopeLogger LogScope0("QUIC_PARAM_GLOBAL_DATAPATH_FEATURES"); - GlobalSettingScope ParamScope(QUIC_PARAM_GLOBAL_DATAPATH_FEATURES); { TestScopeLogger LogScope1("SetParam"); // @@ -2492,7 +2513,7 @@ void QuicTestGlobalParam() } { - TestScopeLogger LogScope2("GetParam"); + TestScopeLogger LogScope2("GetParam. Failed by missing MsQuicLib.Datapath"); uint32_t Length = 0; TEST_QUIC_STATUS( QUIC_STATUS_BUFFER_TOO_SMALL, @@ -2504,13 +2525,12 @@ void QuicTestGlobalParam() TEST_EQUAL(Length, sizeof(uint32_t)); uint32_t ActualFeatures = 0; - TEST_QUIC_SUCCEEDED( + TEST_QUIC_STATUS(QUIC_STATUS_INVALID_STATE, MsQuic->GetParam( nullptr, QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, &Length, &ActualFeatures)); - TEST_NOT_EQUAL(ActualFeatures, 0); } } diff --git a/src/test/lib/EventTest.cpp b/src/test/lib/EventTest.cpp index 68f3b7abd0..98bc1b4005 100644 --- a/src/test/lib/EventTest.cpp +++ b/src/test/lib/EventTest.cpp @@ -453,11 +453,6 @@ QuicTestValidateConnectionEvents3( _In_ QuicAddr& ServerLocalAddr ) { -#if !defined(QUIC_DISABLE_0RTT_TESTS) // TODO: Fix openssl/XDP bug and enable this back - if (!QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { - return; - } -#endif TestScopeLogger ScopeLogger(__FUNCTION__); MsQuicSettings Settings; @@ -532,6 +527,15 @@ void QuicTestValidateConnectionEvents(uint32_t Test) MsQuicAlpn Alpn("MsQuicTest"); +#if defined(QUIC_DISABLE_0RTT_TESTS) // TODO: Fix openssl/XDP bug and enable this back + if (Test == 2) { + return; + } +#endif + if (Test == 2 && QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { + return; + } + { // Listener Scope MsQuicListener Listener(Registration, CleanUpManual, ListenerEventValidatorCallback); From b78b06fdc56977828309fd96e06f66f70eb8d6e7 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Thu, 3 Aug 2023 09:32:42 -0700 Subject: [PATCH 29/87] move global definition in header file --- src/test/lib/ApiTest.cpp | 6 +----- src/test/lib/DataTest.cpp | 6 +----- src/test/lib/MtuTest.cpp | 6 +----- src/test/lib/precomp.h | 7 +++++++ 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index 19cfd50046..7b681ad12d 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -16,11 +16,7 @@ #pragma warning(disable:6387) // '_Param_(1)' could be '0': this does not adhere to the specification for the function -#if defined(_KERNEL_MODE) -bool UseQTIP = false; -// currently x is only CXPLAT_DATAPATH_FEATURE_RAW -#define QuitTestIsFeatureSupported(x) false -#elif defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index ee5319ff6c..33a06aed84 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -14,11 +14,7 @@ #include "DataTest.cpp.clog.h" #endif -#if defined(_KERNEL_MODE) -bool UseQTIP = false; -// currently x is only CXPLAT_DATAPATH_FEATURE_RAW -#define QuitTestIsFeatureSupported(x) false -#elif defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif diff --git a/src/test/lib/MtuTest.cpp b/src/test/lib/MtuTest.cpp index 1afc7a6f15..28c5909c8e 100644 --- a/src/test/lib/MtuTest.cpp +++ b/src/test/lib/MtuTest.cpp @@ -14,11 +14,7 @@ #include "MtuTest.cpp.clog.h" #endif -#if defined(_KERNEL_MODE) -bool UseQTIP = false; -// currently x is only CXPLAT_DATAPATH_FEATURE_RAW -#define QuitTestIsFeatureSupported(x) false -#elif defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif diff --git a/src/test/lib/precomp.h b/src/test/lib/precomp.h index d392cf663d..c85fefb4cc 100644 --- a/src/test/lib/precomp.h +++ b/src/test/lib/precomp.h @@ -35,6 +35,13 @@ #define WIN_ASSERT CXPLAT_FRE_ASSERT #endif #include "karray.h" + +#ifndef GLOBAL_FOR_KERNEL +#define GLOBAL_FOR_KERNEL +bool UseQTIP = false; +// currently x is only CXPLAT_DATAPATH_FEATURE_RAW +#define QuitTestIsFeatureSupported(x) false +#endif #endif #include "TestHelpers.h" From 0416ecd11999dee616641bff9edf8ea11ecc05f2 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Thu, 3 Aug 2023 13:57:09 -0700 Subject: [PATCH 30/87] kernel to avoid calling helper function --- src/test/bin/quic_gtest.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 27a0e05be8..f15a5eecd6 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -2090,10 +2090,6 @@ TEST(Drill, VarIntEncoder) { } TEST_P(WithDrillInitialPacketCidArgs, DrillInitialPacketCids) { - if (!QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { - GTEST_SKIP_("Raw datapath not enabled"); - } - TestLoggerT Logger("QuicDrillInitialPacketCids", GetParam()); if (TestingKernelMode) { QUIC_RUN_DRILL_INITIAL_PACKET_CID_PARAMS Params = { @@ -2105,6 +2101,9 @@ TEST_P(WithDrillInitialPacketCidArgs, DrillInitialPacketCids) { }; ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_DRILL_INITIAL_PACKET_CID, Params)); } else { + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { + GTEST_SKIP_("Raw datapath not enabled"); + } QuicDrillTestInitialCid( GetParam().Family, GetParam().SourceOrDest, @@ -2115,14 +2114,13 @@ TEST_P(WithDrillInitialPacketCidArgs, DrillInitialPacketCids) { } TEST_P(WithDrillInitialPacketTokenArgs, DrillInitialPacketToken) { - if (!QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { - GTEST_SKIP_("Raw datapath not enabled"); - } - TestLoggerT Logger("QuicDrillInitialPacketToken", GetParam()); if (TestingKernelMode) { ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_DRILL_INITIAL_PACKET_TOKEN, GetParam().Family)); } else { + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { + GTEST_SKIP_("Raw datapath not enabled"); + } QuicDrillTestInitialToken(GetParam().Family); } } From 71618da5f4fe5218ed195f03dffd76e446398ffb Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Thu, 3 Aug 2023 17:03:03 -0700 Subject: [PATCH 31/87] move QuitTestIsFeatureSupported after RegistrationOpen --- src/test/bin/quic_gtest.cpp | 76 ++-------------------------------- src/test/lib/DataTest.cpp | 16 +++++-- src/test/lib/HandshakeTest.cpp | 26 ++++++++++++ src/test/lib/MtuTest.cpp | 5 +-- src/test/lib/QuicDrill.cpp | 3 ++ src/test/lib/precomp.h | 1 + 6 files changed, 49 insertions(+), 78 deletions(-) diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index f15a5eecd6..7c4739cd7d 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -16,6 +16,7 @@ bool UseDuoNic = false; #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) bool UseQTIP = false; #endif +uint64_t LARGE_SEND_SIZE = 100000000llu; const MsQuicApi* MsQuic; const char* OsRunner = nullptr; uint32_t Timeout = UINT32_MAX; @@ -82,6 +83,7 @@ class QuicTestEnvironment : public ::testing::Environment { MsQuic = new(std::nothrow) MsQuicApi(); ASSERT_TRUE(QUIC_SUCCEEDED(MsQuic->GetInitStatus())); #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + // WARN: This should not work. datapath is not yet initialized if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { QUIC_EXECUTION_CONFIG Config = {QUIC_EXECUTION_CONFIG_FLAG_QTIP, 10000, 0, {0}}; ASSERT_TRUE(QUIC_SUCCEEDED( @@ -1400,9 +1402,6 @@ TEST_P(WithHandshakeArgs4, RandomLossResumeRejection) { #endif // QUIC_TEST_DATAPATH_HOOKS_ENABLED TEST_P(WithFamilyArgs, Unreachable) { - if (!QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { - GTEST_SKIP_("Raw datapath not enabled"); - } if (GetParam().Family == 4 && IsWindows2019()) GTEST_SKIP(); // IPv4 unreachable doesn't work on 2019 TestLoggerT Logger("QuicTestConnectUnreachable", GetParam()); if (TestingKernelMode) { @@ -1459,14 +1458,6 @@ TEST_P(WithFamilyArgs, ClientBlockedSourcePort) { #if QUIC_TEST_DATAPATH_HOOKS_ENABLED TEST_P(WithFamilyArgs, RebindPort) { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { - // - // NAT rebind doesn't make sense for TCP and QTIP. - // - return; - } -#endif TestLoggerT Logger("QuicTestNatPortRebind", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1480,14 +1471,6 @@ TEST_P(WithFamilyArgs, RebindPort) { } TEST_P(WithRebindPaddingArgs, RebindPortPadded) { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { - // - // NAT rebind doesn't make sense for TCP and QTIP. - // - return; - } -#endif TestLoggerT Logger("QuicTestNatPortRebind(pad)", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1501,14 +1484,6 @@ TEST_P(WithRebindPaddingArgs, RebindPortPadded) { } TEST_P(WithFamilyArgs, RebindAddr) { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { - // - // NAT rebind doesn't make sense for TCP and QTIP. - // - return; - } -#endif TestLoggerT Logger("QuicTestNatAddrRebind", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1522,14 +1497,6 @@ TEST_P(WithFamilyArgs, RebindAddr) { } TEST_P(WithRebindPaddingArgs, RebindAddrPadded) { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { - // - // NAT rebind doesn't make sense for TCP and QTIP. - // - return; - } -#endif TestLoggerT Logger("QuicTestNatAddrRebind(pad)", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1563,10 +1530,6 @@ TEST_P(WithFamilyArgs, ChangeMaxStreamIDs) { #if QUIC_TEST_DATAPATH_HOOKS_ENABLED TEST_P(WithFamilyArgs, LoadBalanced) { - if (!QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { - GTEST_SKIP_("Raw datapath not enabled"); - } - #ifdef QUIC_TEST_SCHANNEL_FLAGS if (IsWindows2022()) GTEST_SKIP(); // Not supported with Schannel on WS2022 #endif @@ -1629,16 +1592,10 @@ TEST_P(WithSendArgs1, Send) { TEST_P(WithSendArgs2, SendLarge) { TestLoggerT Logger("QuicTestConnectAndPing", GetParam()); -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP && GetParam().UseZeroRtt) { - return; - } -#endif - if (TestingKernelMode) { QUIC_RUN_CONNECT_AND_PING_PARAMS Params = { GetParam().Family, - 100000000llu, + LARGE_SEND_SIZE, 1, // ConnectionCount 1, // StreamCount 1, // StreamBurstCount @@ -1656,7 +1613,7 @@ TEST_P(WithSendArgs2, SendLarge) { } else { QuicTestConnectAndPing( GetParam().Family, - 100000000llu, + LARGE_SEND_SIZE, 1, // ConnectionCount 1, // StreamCount 1, // StreamBurstCount @@ -1714,16 +1671,6 @@ TEST_P(WithSendArgs3, SendIntermittently) { #ifndef QUIC_DISABLE_0RTT_TESTS TEST_P(WithSend0RttArgs1, Send0Rtt) { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { - // - // QTIP doesn't work with 0-RTT. QTIP only pauses and caches 1 packet during - // TCP handshake. - // - return; - } -#endif - TestLoggerT Logger("Send0Rtt", GetParam()); if (TestingKernelMode) { QUIC_RUN_CONNECT_AND_PING_PARAMS Params = { @@ -1763,15 +1710,6 @@ TEST_P(WithSend0RttArgs1, Send0Rtt) { } TEST_P(WithSend0RttArgs2, Reject0Rtt) { -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { - // - // QTIP doesn't work with 0-RTT. QTIP only pauses and caches 1 packet during - // TCP handshake. - // - return; - } -#endif TestLoggerT Logger("Reject0Rtt", GetParam()); if (TestingKernelMode) { QUIC_RUN_CONNECT_AND_PING_PARAMS Params = { @@ -2101,9 +2039,6 @@ TEST_P(WithDrillInitialPacketCidArgs, DrillInitialPacketCids) { }; ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_DRILL_INITIAL_PACKET_CID, Params)); } else { - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { - GTEST_SKIP_("Raw datapath not enabled"); - } QuicDrillTestInitialCid( GetParam().Family, GetParam().SourceOrDest, @@ -2118,9 +2053,6 @@ TEST_P(WithDrillInitialPacketTokenArgs, DrillInitialPacketToken) { if (TestingKernelMode) { ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_DRILL_INITIAL_PACKET_TOKEN, GetParam().Family)); } else { - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { - GTEST_SKIP_("Raw datapath not enabled"); - } QuicDrillTestInitialToken(GetParam().Family); } } diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index 33a06aed84..e21badd7b7 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -17,6 +17,7 @@ #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif +extern uint64_t LARGE_SEND_SIZE; /* Helper function to estimate a maximum timeout for a test with a @@ -369,6 +370,18 @@ QuicTestConnectAndPing( _In_ bool FifoScheduling ) { + MsQuicRegistration Registration(NULL, QUIC_EXECUTION_PROFILE_TYPE_MAX_THROUGHPUT, true); + TEST_TRUE(Registration.IsValid()); +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { + if (Length != LARGE_SEND_SIZE) { + return; + } else if (ClientZeroRtt) { + return; + } + } +#endif + const uint32_t TimeoutMs = EstimateTimeoutMs(Length) * StreamBurstCount; const uint16_t TotalStreamCount = (uint16_t)(StreamCount * StreamBurstCount); QUIC_ADDRESS_FAMILY QuicAddrFamily = (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; @@ -395,9 +408,6 @@ QuicTestConnectAndPing( ServerStats.TlsSecrets = ServerSecrets.get(); } - MsQuicRegistration Registration(NULL, QUIC_EXECUTION_PROFILE_TYPE_MAX_THROUGHPUT, true); - TEST_TRUE(Registration.IsValid()); - MsQuicAlpn Alpn("MsQuicTest"); MsQuicSettings Settings; diff --git a/src/test/lib/HandshakeTest.cpp b/src/test/lib/HandshakeTest.cpp index 995ca61171..224d79e8cd 100644 --- a/src/test/lib/HandshakeTest.cpp +++ b/src/test/lib/HandshakeTest.cpp @@ -14,6 +14,10 @@ #include "HandshakeTest.cpp.clog.h" #endif +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +extern bool UseQTIP; +#endif + QUIC_TEST_DATAPATH_HOOKS DatapathHooks::FuncTable = { DatapathHooks::CreateCallback, DatapathHooks::GetLocalAddressCallback, @@ -456,6 +460,14 @@ QuicTestNatPortRebind( RebindContext Context; MsQuicRegistration Registration(true); TEST_TRUE(Registration.IsValid()); +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { + // + // NAT rebind doesn't make sense for TCP and QTIP. + // + return; + } +#endif MsQuicConfiguration ServerConfiguration(Registration, "MsQuicTest", ServerSelfSignedCredConfig); TEST_TRUE(ServerConfiguration.IsValid()); @@ -505,6 +517,14 @@ QuicTestNatAddrRebind( RebindContext Context; MsQuicRegistration Registration(true); TEST_TRUE(Registration.IsValid()); +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { + // + // NAT rebind doesn't make sense for TCP and QTIP. + // + return; + } +#endif MsQuicConfiguration ServerConfiguration(Registration, "MsQuicTest", ServerSelfSignedCredConfig); TEST_TRUE(ServerConfiguration.IsValid()); @@ -1006,6 +1026,9 @@ QuicTestConnectUnreachable( { MsQuicRegistration Registration; TEST_TRUE(Registration.IsValid()); + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { + return; + } MsQuicAlpn Alpn("MsQuicTest"); @@ -2945,6 +2968,9 @@ QuicTestLoadBalancedHandshake( { MsQuicRegistration Registration(true); TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { + return; // TODO - QUIC_STATUS_NOT_SUPPORTED + } MsQuicConfiguration ClientConfiguration(Registration, "MsQuicTest", MsQuicCredentialConfig()); TEST_QUIC_SUCCEEDED(ClientConfiguration.GetInitStatus()); diff --git a/src/test/lib/MtuTest.cpp b/src/test/lib/MtuTest.cpp index 28c5909c8e..f17dd642ee 100644 --- a/src/test/lib/MtuTest.cpp +++ b/src/test/lib/MtuTest.cpp @@ -49,12 +49,13 @@ struct ResetSettings { void QuicTestMtuSettings() { + MsQuicRegistration Registration; + TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) const uint16_t DefaultMaximumMtu = (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP)? 1488 : 1500; #else const uint16_t DefaultMaximumMtu = 1500; #endif - { // // Test setting on library works @@ -78,8 +79,6 @@ QuicTestMtuSettings() TEST_EQUAL(NewSettings.MaximumMtu, UpdatedSettings.MaximumMtu); } - MsQuicRegistration Registration; - TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); MsQuicAlpn Alpn("MsQuicTest"); { { diff --git a/src/test/lib/QuicDrill.cpp b/src/test/lib/QuicDrill.cpp index 871b73935e..0bad5a120f 100644 --- a/src/test/lib/QuicDrill.cpp +++ b/src/test/lib/QuicDrill.cpp @@ -246,6 +246,9 @@ QuicDrillInitialPacketFailureTest( TEST_FAILURE("Registration not valid!"); return false; } + if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW)) { + return false; + } MsQuicAlpn Alpn("MsQuicTest"); diff --git a/src/test/lib/precomp.h b/src/test/lib/precomp.h index c85fefb4cc..da5b3537c7 100644 --- a/src/test/lib/precomp.h +++ b/src/test/lib/precomp.h @@ -39,6 +39,7 @@ #ifndef GLOBAL_FOR_KERNEL #define GLOBAL_FOR_KERNEL bool UseQTIP = false; +uint64_t LARGE_SEND_SIZE = 100000000llu; // currently x is only CXPLAT_DATAPATH_FEATURE_RAW #define QuitTestIsFeatureSupported(x) false #endif From 42e1871fd5b39bdd433bcd3101d97efbfd467a20 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Thu, 3 Aug 2023 17:23:23 -0700 Subject: [PATCH 32/87] supress warning --- src/test/lib/DataTest.cpp | 3 +++ src/test/lib/HandshakeTest.cpp | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index e21badd7b7..d0fcbe21c5 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -373,6 +373,8 @@ QuicTestConnectAndPing( MsQuicRegistration Registration(NULL, QUIC_EXECUTION_PROFILE_TYPE_MAX_THROUGHPUT, true); TEST_TRUE(Registration.IsValid()); #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#pragma warning(push) +#pragma warning(disable: 4127) if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { if (Length != LARGE_SEND_SIZE) { return; @@ -380,6 +382,7 @@ QuicTestConnectAndPing( return; } } +#pragma warning(pop) #endif const uint32_t TimeoutMs = EstimateTimeoutMs(Length) * StreamBurstCount; diff --git a/src/test/lib/HandshakeTest.cpp b/src/test/lib/HandshakeTest.cpp index 224d79e8cd..ced3a9ad06 100644 --- a/src/test/lib/HandshakeTest.cpp +++ b/src/test/lib/HandshakeTest.cpp @@ -461,12 +461,15 @@ QuicTestNatPortRebind( MsQuicRegistration Registration(true); TEST_TRUE(Registration.IsValid()); #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#pragma warning(push) +#pragma warning(disable: 4127) if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } +#pragma warning(pop) #endif MsQuicConfiguration ServerConfiguration(Registration, "MsQuicTest", ServerSelfSignedCredConfig); @@ -518,12 +521,15 @@ QuicTestNatAddrRebind( MsQuicRegistration Registration(true); TEST_TRUE(Registration.IsValid()); #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#pragma warning(push) +#pragma warning(disable: 4127) if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } +#pragma warning(pop) #endif MsQuicConfiguration ServerConfiguration(Registration, "MsQuicTest", ServerSelfSignedCredConfig); From 6e78d063d5f1733c87a903514f95f7cfc767d2b0 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Fri, 4 Aug 2023 14:11:03 -0700 Subject: [PATCH 33/87] remove QuitTestIsFeatureSupported from quic_gtest as it doesn't work as expected by dependency of MsQuicLib.Datapath --- src/test/MsQuicTests.h | 8 -------- src/test/bin/quic_gtest.cpp | 3 +-- src/test/bin/quic_gtest.h | 17 ----------------- src/test/lib/TestHelpers.h | 19 +++++++++++++++++++ src/test/lib/precomp.h | 2 -- 5 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/test/MsQuicTests.h b/src/test/MsQuicTests.h index fb7f5b75f5..d0bd406c98 100644 --- a/src/test/MsQuicTests.h +++ b/src/test/MsQuicTests.h @@ -231,14 +231,6 @@ QuicTestInterfaceBinding( _In_ int Family ); -uint32_t -QuitTestGetDatapathFeatureFlags(); - -bool -QuitTestIsFeatureSupported( - _In_ uint32_t Feature - ); - #ifdef QUIC_API_ENABLE_PREVIEW_FEATURES void QuicTestCibirExtension( diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 7c4739cd7d..24b03ceefb 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -83,8 +83,7 @@ class QuicTestEnvironment : public ::testing::Environment { MsQuic = new(std::nothrow) MsQuicApi(); ASSERT_TRUE(QUIC_SUCCEEDED(MsQuic->GetInitStatus())); #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - // WARN: This should not work. datapath is not yet initialized - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { + if (UseQTIP) { QUIC_EXECUTION_CONFIG Config = {QUIC_EXECUTION_CONFIG_FLAG_QTIP, 10000, 0, {0}}; ASSERT_TRUE(QUIC_SUCCEEDED( MsQuic->SetParam( diff --git a/src/test/bin/quic_gtest.h b/src/test/bin/quic_gtest.h index 7e95000110..f2bd730221 100644 --- a/src/test/bin/quic_gtest.h +++ b/src/test/bin/quic_gtest.h @@ -25,23 +25,6 @@ extern bool TestingKernelMode; extern bool UseQTIP; #endif -uint32_t -QuitTestGetDatapathFeatureFlags() { - static uint32_t Length = sizeof(uint32_t); - uint32_t Features = 0; - MsQuic->GetParam( - nullptr, - QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, - &Length, - &Features); - return Features; -} - -bool -QuitTestIsFeatureSupported(uint32_t Feature) { - return static_cast(QuitTestGetDatapathFeatureFlags() & Feature); -} - class WithBool : public testing::Test, public testing::WithParamInterface { }; diff --git a/src/test/lib/TestHelpers.h b/src/test/lib/TestHelpers.h index 675dda3bb9..887072a246 100644 --- a/src/test/lib/TestHelpers.h +++ b/src/test/lib/TestHelpers.h @@ -46,6 +46,25 @@ QuicAddrSetToDuoNic( } } +inline +uint32_t +QuitTestGetDatapathFeatureFlags() { + static uint32_t Length = sizeof(uint32_t); + uint32_t Features = 0; + MsQuic->GetParam( + nullptr, + QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, + &Length, + &Features); + return Features; +} + +inline +bool +QuitTestIsFeatureSupported(uint32_t Feature) { + return static_cast(QuitTestGetDatapathFeatureFlags() & Feature); +} + #include "msquic.hpp" #include "quic_toeplitz.h" diff --git a/src/test/lib/precomp.h b/src/test/lib/precomp.h index da5b3537c7..9c50a1fc89 100644 --- a/src/test/lib/precomp.h +++ b/src/test/lib/precomp.h @@ -40,8 +40,6 @@ #define GLOBAL_FOR_KERNEL bool UseQTIP = false; uint64_t LARGE_SEND_SIZE = 100000000llu; -// currently x is only CXPLAT_DATAPATH_FEATURE_RAW -#define QuitTestIsFeatureSupported(x) false #endif #endif From c87f06f88b4eeedccfc868f9c97ee702562365da Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Mon, 7 Aug 2023 14:50:04 -0700 Subject: [PATCH 34/87] remove raw feature check as much as possible --- src/test/bin/quic_gtest.cpp | 47 +++++++++++++++++++++++++++++++--- src/test/bin/quic_gtest.h | 5 ++++ src/test/lib/ApiTest.cpp | 6 ++--- src/test/lib/DataTest.cpp | 16 ++---------- src/test/lib/HandshakeTest.cpp | 22 ---------------- src/test/lib/MtuTest.cpp | 4 +-- 6 files changed, 55 insertions(+), 45 deletions(-) diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 24b03ceefb..0edc2fb088 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -16,7 +16,6 @@ bool UseDuoNic = false; #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) bool UseQTIP = false; #endif -uint64_t LARGE_SEND_SIZE = 100000000llu; const MsQuicApi* MsQuic; const char* OsRunner = nullptr; uint32_t Timeout = UINT32_MAX; @@ -1457,6 +1456,12 @@ TEST_P(WithFamilyArgs, ClientBlockedSourcePort) { #if QUIC_TEST_DATAPATH_HOOKS_ENABLED TEST_P(WithFamilyArgs, RebindPort) { + if (UseQTIP) { + // + // NAT rebind doesn't make sense for TCP and QTIP. + // + return; + } TestLoggerT Logger("QuicTestNatPortRebind", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1470,6 +1475,12 @@ TEST_P(WithFamilyArgs, RebindPort) { } TEST_P(WithRebindPaddingArgs, RebindPortPadded) { + if (UseQTIP) { + // + // NAT rebind doesn't make sense for TCP and QTIP. + // + return; + } TestLoggerT Logger("QuicTestNatPortRebind(pad)", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1483,6 +1494,12 @@ TEST_P(WithRebindPaddingArgs, RebindPortPadded) { } TEST_P(WithFamilyArgs, RebindAddr) { + if (UseQTIP) { + // + // NAT rebind doesn't make sense for TCP and QTIP. + // + return; + } TestLoggerT Logger("QuicTestNatAddrRebind", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1496,6 +1513,12 @@ TEST_P(WithFamilyArgs, RebindAddr) { } TEST_P(WithRebindPaddingArgs, RebindAddrPadded) { + if (UseQTIP) { + // + // NAT rebind doesn't make sense for TCP and QTIP. + // + return; + } TestLoggerT Logger("QuicTestNatAddrRebind(pad)", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1594,7 +1617,7 @@ TEST_P(WithSendArgs2, SendLarge) { if (TestingKernelMode) { QUIC_RUN_CONNECT_AND_PING_PARAMS Params = { GetParam().Family, - LARGE_SEND_SIZE, + 100000000llu, 1, // ConnectionCount 1, // StreamCount 1, // StreamBurstCount @@ -1612,7 +1635,7 @@ TEST_P(WithSendArgs2, SendLarge) { } else { QuicTestConnectAndPing( GetParam().Family, - LARGE_SEND_SIZE, + 100000000llu, 1, // ConnectionCount 1, // StreamCount 1, // StreamBurstCount @@ -1670,6 +1693,15 @@ TEST_P(WithSendArgs3, SendIntermittently) { #ifndef QUIC_DISABLE_0RTT_TESTS TEST_P(WithSend0RttArgs1, Send0Rtt) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (UseQTIP) { + // + // QTIP doesn't work with 0-RTT. QTIP only pauses and caches 1 packet during + // TCP handshake. + // + return; + } +#endif TestLoggerT Logger("Send0Rtt", GetParam()); if (TestingKernelMode) { QUIC_RUN_CONNECT_AND_PING_PARAMS Params = { @@ -1709,6 +1741,15 @@ TEST_P(WithSend0RttArgs1, Send0Rtt) { } TEST_P(WithSend0RttArgs2, Reject0Rtt) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (UseQTIP) { + // + // QTIP doesn't work with 0-RTT. QTIP only pauses and caches 1 packet during + // TCP handshake. + // + return; + } +#endif TestLoggerT Logger("Reject0Rtt", GetParam()); if (TestingKernelMode) { QUIC_RUN_CONNECT_AND_PING_PARAMS Params = { diff --git a/src/test/bin/quic_gtest.h b/src/test/bin/quic_gtest.h index f2bd730221..1d0523800e 100644 --- a/src/test/bin/quic_gtest.h +++ b/src/test/bin/quic_gtest.h @@ -347,6 +347,11 @@ struct SendArgs2 { for (bool UseZeroRtt : { false }) #endif { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (UseQTIP && UseZeroRtt) { + continue; + } +#endif list.push_back({ Family, UseSendBuffer, UseZeroRtt }); } return list; diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index 7b681ad12d..6ed36ec25a 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -2585,12 +2585,10 @@ void QuicTestGlobalParam() SimpleGetParamTest(nullptr, QUIC_PARAM_GLOBAL_EXECUTION_CONFIG, DataLength, Data); } - uint32_t Features = QuitTestGetDatapathFeatureFlags(); - if (!(Features & CXPLAT_DATAPATH_FEATURE_RAW) #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - || (!!(Features & CXPLAT_DATAPATH_FEATURE_RAW) && !UseQTIP) + if (!UseQTIP) #endif - ) { + { // // Good GetParam with length == 0 // diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index d0fcbe21c5..9a8afa0a2b 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -372,18 +372,6 @@ QuicTestConnectAndPing( { MsQuicRegistration Registration(NULL, QUIC_EXECUTION_PROFILE_TYPE_MAX_THROUGHPUT, true); TEST_TRUE(Registration.IsValid()); -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) -#pragma warning(push) -#pragma warning(disable: 4127) - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { - if (Length != LARGE_SEND_SIZE) { - return; - } else if (ClientZeroRtt) { - return; - } - } -#pragma warning(pop) -#endif const uint32_t TimeoutMs = EstimateTimeoutMs(Length) * StreamBurstCount; const uint16_t TotalStreamCount = (uint16_t)(StreamCount * StreamBurstCount); @@ -520,7 +508,7 @@ QuicTestConnectAndPing( if (i != 0 #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - && (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && !UseQTIP) + && !UseQTIP #endif ) { Connections.get()[i]->SetLocalAddr(LocalAddr); @@ -535,7 +523,7 @@ QuicTestConnectAndPing( if (i == 0 #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - && (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && !UseQTIP) + && !UseQTIP #endif ) { Connections.get()[i]->GetLocalAddr(LocalAddr); diff --git a/src/test/lib/HandshakeTest.cpp b/src/test/lib/HandshakeTest.cpp index ced3a9ad06..5224af3981 100644 --- a/src/test/lib/HandshakeTest.cpp +++ b/src/test/lib/HandshakeTest.cpp @@ -460,17 +460,6 @@ QuicTestNatPortRebind( RebindContext Context; MsQuicRegistration Registration(true); TEST_TRUE(Registration.IsValid()); -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) -#pragma warning(push) -#pragma warning(disable: 4127) - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { - // - // NAT rebind doesn't make sense for TCP and QTIP. - // - return; - } -#pragma warning(pop) -#endif MsQuicConfiguration ServerConfiguration(Registration, "MsQuicTest", ServerSelfSignedCredConfig); TEST_TRUE(ServerConfiguration.IsValid()); @@ -520,17 +509,6 @@ QuicTestNatAddrRebind( RebindContext Context; MsQuicRegistration Registration(true); TEST_TRUE(Registration.IsValid()); -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) -#pragma warning(push) -#pragma warning(disable: 4127) - if (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP) { - // - // NAT rebind doesn't make sense for TCP and QTIP. - // - return; - } -#pragma warning(pop) -#endif MsQuicConfiguration ServerConfiguration(Registration, "MsQuicTest", ServerSelfSignedCredConfig); TEST_TRUE(ServerConfiguration.IsValid()); diff --git a/src/test/lib/MtuTest.cpp b/src/test/lib/MtuTest.cpp index f17dd642ee..2856475f90 100644 --- a/src/test/lib/MtuTest.cpp +++ b/src/test/lib/MtuTest.cpp @@ -52,7 +52,7 @@ QuicTestMtuSettings() MsQuicRegistration Registration; TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - const uint16_t DefaultMaximumMtu = (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP)? 1488 : 1500; + const uint16_t DefaultMaximumMtu = UseQTIP ? 1488 : 1500; #else const uint16_t DefaultMaximumMtu = 1500; #endif @@ -315,7 +315,7 @@ QuicTestMtuDiscovery( const uint16_t MinimumMtu = RaiseMinimumMtu ? 1360 : 1248; #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - const uint16_t MaximumMtu = (QuitTestIsFeatureSupported(CXPLAT_DATAPATH_FEATURE_RAW) && UseQTIP)? 1488 : 1500; + const uint16_t MaximumMtu = UseQTIP ? 1488 : 1500; #else const uint16_t MaximumMtu = 1500; #endif From 3b791c23a97edf78dbc0e5f5a173bca3c416ea26 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Mon, 7 Aug 2023 14:58:48 -0700 Subject: [PATCH 35/87] ifdef for UseQTIP visibility --- src/test/bin/quic_gtest.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 0edc2fb088..1adcafeadf 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -1456,12 +1456,14 @@ TEST_P(WithFamilyArgs, ClientBlockedSourcePort) { #if QUIC_TEST_DATAPATH_HOOKS_ENABLED TEST_P(WithFamilyArgs, RebindPort) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } +#endif TestLoggerT Logger("QuicTestNatPortRebind", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1475,12 +1477,14 @@ TEST_P(WithFamilyArgs, RebindPort) { } TEST_P(WithRebindPaddingArgs, RebindPortPadded) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } +#endif TestLoggerT Logger("QuicTestNatPortRebind(pad)", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1494,12 +1498,14 @@ TEST_P(WithRebindPaddingArgs, RebindPortPadded) { } TEST_P(WithFamilyArgs, RebindAddr) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } +#endif TestLoggerT Logger("QuicTestNatAddrRebind", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { @@ -1513,12 +1519,14 @@ TEST_P(WithFamilyArgs, RebindAddr) { } TEST_P(WithRebindPaddingArgs, RebindAddrPadded) { +#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) if (UseQTIP) { // // NAT rebind doesn't make sense for TCP and QTIP. // return; } +#endif TestLoggerT Logger("QuicTestNatAddrRebind(pad)", GetParam()); if (TestingKernelMode) { QUIC_RUN_REBIND_PARAMS Params = { From 1e148e276601abe5aaccc45362ee00c1a8969594 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 8 Aug 2023 10:36:58 -0700 Subject: [PATCH 36/87] tmp --- src/inc/quic_datapath.h | 12 +- src/platform/datapath_raw_win.c | 35 +++-- src/platform/datapath_raw_xdp_win.c | 4 + src/platform/datapath_win.c | 209 ++++++++++++++++++++-------- src/platform/datapath_winuser.c | 36 ++++- src/platform/platform_worker.c | 6 +- src/test/bin/quic_gtest.cpp | 1 + src/test/lib/ApiTest.cpp | 184 ++++++++++++------------ 8 files changed, 318 insertions(+), 169 deletions(-) diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 8e0b17430a..15117c1688 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -864,7 +864,7 @@ typedef struct CXPLAT_DATAPATH_FUNCTIONS { _Inout_ QUIC_ADDR* Address); QUIC_STATUS (*CxPlatSocketCreateUdp)(_In_ CXPLAT_DATAPATH* Datapath, _In_ const CXPLAT_UDP_CONFIG* Config, - _Out_ CXPLAT_SOCKET* Socket); + _Out_ CXPLAT_SOCKET** Socket); QUIC_STATUS (*CxPlatSocketCreateTcp)(_In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, @@ -897,6 +897,8 @@ typedef struct CXPLAT_DATAPATH_FUNCTIONS { _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA_INTERNAL* SendData); void (*CxPlatDataPathProcessCqe)(_In_ CXPLAT_CQE* Cqe); + void (*QuicCopyRouteInfo)(_Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoutee); } CXPLAT_DATAPATH_FUNCTIONS; extern const struct CXPLAT_DATAPATH_FUNCTIONS DataPathUserFuncs; @@ -1022,6 +1024,14 @@ XDP_CxPlatSocketSend( _In_ CXPLAT_SEND_DATA_INTERNAL* SendData ); +void +XDP_CxPlatResolveRouteComplete( + _In_ void* Context, + _Inout_ CXPLAT_ROUTE* Route, + _In_reads_bytes_(6) const uint8_t* PhysicalAddress, + _In_ uint8_t PathId + ); + #if defined(__cplusplus) } #endif diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index a247ca33d3..b9584ddae8 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -148,20 +148,20 @@ CxPlatInitRawDataPath( Error: -// if (DataPath != NULL) { -// #if DEBUG -// DataPath->Uninitialized = TRUE; -// #endif -// if (DpRawInitialized) { -// CxPlatDpRawUninitialize(DataPath); -// } else { -// if (SockPoolInitialized) { -// CxPlatSockPoolUninitialize(&DataPath->SocketPool); -// } -// CXPLAT_FREE(DataPath, QUIC_POOL_DATAPATH); -// CxPlatRundownRelease(&CxPlatWorkerRundown); -// } -// } + if (DataPath != NULL) { +#if DEBUG + DataPath->Uninitialized = TRUE; +#endif + if (DpRawInitialized) { + CxPlatDpRawUninitialize(DataPath); + } else { + if (SockPoolInitialized) { + CxPlatSockPoolUninitialize(&DataPath->SocketPool); + } + // CXPLAT_FREE(DataPath, QUIC_POOL_DATAPATH); + CxPlatRundownRelease(&CxPlatWorkerRundown); + } + } return Status; } @@ -297,7 +297,6 @@ CxPlatInitRawSocket( #pragma warning(push) #pragma warning(disable:6001) // Using uninitialized memory CXPLAT_DBG_ASSERT(Socket != NULL); - #pragma warning(pop) QUIC_STATUS Status = QUIC_STATUS_SUCCESS; // CxPlatZeroMemory(Socket, sizeof(CXPLAT_SOCKET_RAW)); @@ -348,9 +347,11 @@ CxPlatInitRawSocket( if (Socket != NULL) { CxPlatRundownUninitialize(&Socket->Rundown); CXPLAT_FREE(Socket, QUIC_POOL_SOCKET); + Socket->RawDatapath = NULL; Socket = NULL; } } + #pragma warning(pop) return Status; } @@ -386,6 +387,10 @@ CxPlatRawSocketDelete( _In_ CXPLAT_SOCKET_RAW* Socket ) { + if (!Socket->RawDatapath) { + // Raw socket was not initialized. + return; + } CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); CxPlatRemoveSocket(&Socket->RawDatapath->SocketPool, Socket); CxPlatRundownReleaseAndWait(&Socket->Rundown); diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 7b233e5363..bf11b34f6f 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -1310,6 +1310,10 @@ MANGLE(CxPlatSocketUpdateQeo)( _In_ uint32_t OffloadCount ) { + if (!Socket->RawDatapath) { + // Raw socket was not created. + return QUIC_STATUS_INVALID_STATE; + } XDP_DATAPATH* Xdp = (XDP_DATAPATH*)Socket->Datapath; XDP_QUIC_CONNECTION Connections[2]; diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 5bb6e7d815..87b2ec662c 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -70,9 +70,15 @@ CxPlatDataPathInitialize( _Out_ CXPLAT_DATAPATH** NewDataPath ) { + QUIC_STATUS Status = QUIC_STATUS_SUCCESS; + if (NewDataPath == NULL) { + Status = QUIC_STATUS_INVALID_PARAMETER; + goto Error; + } + + // // Init all Datapath // - QUIC_STATUS Status = QUIC_STATUS_SUCCESS; if (UdpCallbacks != NULL) { if (UdpCallbacks->Receive == NULL || UdpCallbacks->Unreachable == NULL) { Status = QUIC_STATUS_INVALID_PARAMETER; @@ -124,6 +130,9 @@ CxPlatDataPathInitialize( Config, DataPath); if (QUIC_FAILED(Status)) { + QuicTraceLogVerbose( + DatapathInitFail, + "[ dp] Failed to initialize datapath, status:%d", Status); goto Error; } @@ -139,21 +148,24 @@ CxPlatDataPathInitialize( } CxPlatZeroMemory(RawDataPath, RawDatapathSize); - Status = CxPlatInitRawDataPath( - ClientRecvContextLength, - Config, - DataPath, - RawDataPath); + Status = QUIC_STATUS_INVALID_PARAMETER; + // Status = CxPlatInitRawDataPath( + // ClientRecvContextLength, + // Config, + // DataPath, + // RawDataPath); if (QUIC_FAILED(Status)) { + QuicTraceLogVerbose( + RawDatapathInitFail, + "[ raw] Failed to initialize raw datapath, status:%d", Status); Status = QUIC_STATUS_SUCCESS; CXPLAT_FREE(RawDataPath, QUIC_POOL_DATAPATH); RawDataPath = NULL; - // TODO: log } DataPath->RawDataPath = RawDataPath; *NewDataPath = DataPath; - + fprintf(stderr, "DataPath initialize\n"); Error: // TODO: error handling @@ -166,7 +178,7 @@ CxPlatDataPathUninitialize( _In_ CXPLAT_DATAPATH* Datapath ) { - if (Datapath->RawDataPath) { + if (Datapath->RawDataPath) { XDP_CxPlatDataPathUninitialize(Datapath->RawDataPath); } DataPathUserFuncs.CxPlatDataPathUninitialize(Datapath); @@ -191,9 +203,11 @@ CxPlatDataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH* Datapath ) { - // FIXME: Which feature should be taken? - // return DataPathUserFuncs.CxPlatDataPathGetSupportedFeatures(Datapath); - return 0; + if (Datapath->RawDataPath) { + return DataPathUserFuncs.CxPlatDataPathGetSupportedFeatures(Datapath) | + XDP_CxPlatDataPathGetSupportedFeatures(Datapath->RawDataPath); + } + return DataPathUserFuncs.CxPlatDataPathGetSupportedFeatures(Datapath); } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -217,12 +231,11 @@ CxPlatDataPathGetLocalAddresses( _Out_ uint32_t* AddressesCount ) { - // which datapath should be used? + // TODO: XDP doesn't support, could be inlined here return DataPathUserFuncs.CxPlatDataPathGetLocalAddresses( Datapath, Addresses, AddressesCount); - // TODO: xdp } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -235,7 +248,7 @@ CxPlatDataPathGetGatewayAddresses( _Out_ uint32_t* GatewayAddressesCount ) { - // XDP doesn't support, but later which should be called? + // TODO: XDP doesn't support, Could be inlined here. return DataPathUserFuncs.CxPlatDataPathGetGatewayAddresses( Datapath, GatewayAddresses, @@ -356,67 +369,81 @@ CxPlatSocketCreateUdp( _Out_ CXPLAT_SOCKET** NewSocket ) { + fprintf(stderr, "CxPlatSocketCreateUdp\n"); + // return CxPlatSocketCreateUdp_OLD(Datapath, Config, NewSocket); + +#pragma warning(push) +#pragma warning(suppress:4701) QUIC_STATUS Status = QUIC_STATUS_SUCCESS; // Raw (Sock (Base (addrs))) // alloc memory by sizeof RAW // call CxPlatSocketCreateUdp with NewSocket as Sock // Call CxPlatInitRawSocket with NewSocket as Raw - BOOLEAN IsServerSocket = Config->RemoteAddress == NULL; - uint16_t SocketCount = IsServerSocket ? Datapath->ProcCount : 1; - // TODO: check Datapath->RawDataPath and shrink allocation size - uint32_t RawSocketLength = CxPlatGetRawSocketSize() + SocketCount * sizeof(CXPLAT_SOCKET_PROC); - CXPLAT_SOCKET_RAW* RawSocket = CXPLAT_ALLOC_PAGED(RawSocketLength, QUIC_POOL_SOCKET); - if (RawSocket == NULL) { - QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_SOCKET", - RawSocketLength); - Status = QUIC_STATUS_OUT_OF_MEMORY; - goto Error; - } - ZeroMemory(RawSocket, RawSocketLength); - CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); + // BOOLEAN IsServerSocket = Config->RemoteAddress == NULL; + // uint16_t SocketCount = IsServerSocket ? Datapath->ProcCount : 1; + // // TODO: check Datapath->RawDataPath and shrink allocation size + // uint32_t RawSocketLength = CxPlatGetRawSocketSize() + SocketCount * sizeof(CXPLAT_SOCKET_PROC); + // CXPLAT_SOCKET_RAW* RawSocket = CXPLAT_ALLOC_PAGED(RawSocketLength, QUIC_POOL_SOCKET); + // CXPLAT_SOCKET* Socket = NULL; + // if (RawSocket == NULL) { + // QuicTraceEvent( + // AllocFailure, + // "Allocation of '%s' failed. (%llu bytes)", + // "CXPLAT_SOCKET", + // RawSocketLength); + // Status = QUIC_STATUS_OUT_OF_MEMORY; + // goto Error; + // } + // fprintf(stderr, "RawSocket allocated %p\n", RawSocket); - QuicTraceEvent( - DatapathCreated, - "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", - Socket, - CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress), - CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress)); + // ZeroMemory(RawSocket, RawSocketLength); + // Socket = CxPlatRawToSocket(RawSocket); + + // QuicTraceEvent( + // DatapathCreated, + // "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", + // Socket, + // CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress), + // CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress)); Status = DataPathUserFuncs.CxPlatSocketCreateUdp( Datapath, Config, - Socket); + NewSocket); if (QUIC_FAILED(Status)) { + QuicTraceLogVerbose( + SockCreateFail, + "[sock] Failed to create socket, status:%d", Status); goto Error; } - if (Datapath->RawDataPath) { + if (*NewSocket) { Status = CxPlatInitRawSocket( Datapath->RawDataPath, Config, - RawSocket); + CxPlatSocketToRaw(*NewSocket)); if (QUIC_FAILED(Status)) { + QuicTraceLogVerbose( + RawSockCreateFail, + "[sock] Failed to create raw socket, status:%d", Status); Status = QUIC_STATUS_SUCCESS; - // TODO: logging } } - *NewSocket = (CXPLAT_SOCKET*)Socket; - RawSocket = NULL; + // *NewSocket = Socket; + // RawSocket = NULL; + // Socket = NULL; Error: - // TODO: error handling - // if (RawSocket) { - // // invalid type - // CxPlatSocketDelete((CXPLAT_SOCKET*)RawSocket); - // } + // if (Socket) { + // // TODO: break by MultiBindListener test by the RawSocket doesn't have ->Datapath + // CxPlatSocketDelete(Socket); + // } +#pragma warning(pop) return Status; } @@ -427,10 +454,65 @@ CxPlatSocketCreateTcp( _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, _In_opt_ void* CallbackContext, - _Out_ CXPLAT_SOCKET** Socket + _Out_ CXPLAT_SOCKET** NewSocket ) { - return QUIC_STATUS_NOT_SUPPORTED; + + return DataPathUserFuncs.CxPlatSocketCreateTcp( + Datapath, + LocalAddress, + RemoteAddress, + CallbackContext, + NewSocket); + +// QUIC_STATUS Status = QUIC_STATUS_SUCCESS; +// CXPLAT_SOCKET* Socket = NULL; +// uint32_t RawSocketLength = CxPlatGetRawSocketSize() + sizeof(CXPLAT_SOCKET_PROC); +// CXPLAT_SOCKET_RAW* RawSocket = CXPLAT_ALLOC_PAGED(RawSocketLength, QUIC_POOL_SOCKET); +// if (RawSocket == NULL) { +// QuicTraceEvent( +// AllocFailure, +// "Allocation of '%s' failed. (%llu bytes)", +// "CXPLAT_SOCKET", +// RawSocketLength); +// Status = QUIC_STATUS_OUT_OF_MEMORY; +// goto Error; +// } +// fprintf(stderr, "Tcp Socket allocated :%p\n", Socket); + +// QuicTraceEvent( +// DatapathCreated, +// "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", +// Socket, +// CASTED_CLOG_BYTEARRAY(LocalAddress ? sizeof(*LocalAddress) : 0, LocalAddress), +// CASTED_CLOG_BYTEARRAY(RemoteAddress ? sizeof(*RemoteAddress) : 0, RemoteAddress)); + +// ZeroMemory(RawSocket, RawSocketLength); +// CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); + +// QUIC_STATUS Status = DataPathUserFuncs.CxPlatSocketCreateTcp( +// Datapath, +// LocalAddress, +// RemoteAddress, +// CallbackContext, +// Socket); +// if (QUIC_FAILED(Status)) { +// QuicTraceLogVerbose( +// SockCreateFail, +// "[sock] Failed to create Tcp socket, status:%d", Status); +// goto Error; +// } + +// *NewSocket = Socket; +// RawSocket = NULL; +// Socket = NULL; + +// Error: +// if (RawSocket) { +// CxPlatSocketDelete(Socket); +// } + +// return Status; } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -442,22 +524,29 @@ CxPlatSocketCreateTcpListener( _Out_ CXPLAT_SOCKET** NewSocket ) { - return QUIC_STATUS_NOT_SUPPORTED; + return DataPathUserFuncs.CxPlatSocketCreateTcpListener( + Datapath, + LocalAddress, + RecvCallbackContext, + NewSocket); } _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatSocketDelete( - _In_ CXPLAT_SOCKET* Socket + _In_ CXPLAT_SOCKET* Socket // TODO: consider which type to be used ) { // TODO: bubble up common logic if (Socket->Datapath && Socket->Datapath->RawDataPath) { CxPlatRawSocketDelete(CxPlatSocketToRaw(Socket)); } - // TODO: want to free socket here + + fprintf(stderr, "RawSocket deleting %p\n", CxPlatSocketToRaw(Socket)); DataPathUserFuncs.CxPlatSocketDelete(Socket); - CXPLAT_FREE(CxPlatSocketToRaw(Socket), QUIC_POOL_SOCKET); + + // TODO: TCP socket cannot be free as RawSocket + // CXPLAT_FREE(CxPlatSocketToRaw(Socket), QUIC_POOL_SOCKET); } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -523,8 +612,6 @@ CxPlatRecvDataReturn( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ) { - // TODO: CXPLAT_RECV_DATA doesn't have RawDatapath existence. - // need flag to check which datapath is used. CXPLAT_DBG_ASSERT(RecvDataChain != NULL); if (RecvDataChain->BufferFrom == CXPLAT_BUFFER_FROM_USER) { DataPathUserFuncs.CxPlatRecvDataReturn(RecvDataChain); @@ -673,7 +760,10 @@ CxPlatResolveRouteComplete( _In_ uint8_t PathId ) { - + // not 100% sure + if (Route->State == RouteUnresolved) { + XDP_CxPlatResolveRouteComplete(Connection, Route, PhysicalAddress, PathId); + } } // @@ -689,7 +779,9 @@ CxPlatResolveRoute( _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback ) { - return QUIC_STATUS_NOT_SUPPORTED; + // TODO: which? + Route->State = RouteResolved; + return QUIC_STATUS_SUCCESS; } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -699,5 +791,6 @@ CxPlatUpdateRoute( _In_ CXPLAT_ROUTE* SrcRoute ) { + // TODO: which? } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 229b991ffe..64bca223e7 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1013,6 +1013,7 @@ CxPlatDataPathRelease( CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); WSACleanup(); CxPlatRundownRelease(&CxPlatWorkerRundown); + fprintf(stderr, "DataPath released\n"); } } @@ -1364,7 +1365,7 @@ QUIC_STATUS MANGLE(CxPlatSocketCreateUdp)( _In_ CXPLAT_DATAPATH* Datapath, _In_ const CXPLAT_UDP_CONFIG* Config, - _Out_ CXPLAT_SOCKET* Socket + _Out_ CXPLAT_SOCKET** NewSocket ) { QUIC_STATUS Status; @@ -1376,6 +1377,28 @@ MANGLE(CxPlatSocketCreateUdp)( CXPLAT_DBG_ASSERT(Datapath->UdpHandlers.Receive != NULL || Config->Flags & CXPLAT_SOCKET_FLAG_PCP); + uint32_t RawSocketLength = CxPlatGetRawSocketSize() + SocketCount * sizeof(CXPLAT_SOCKET_PROC); + CXPLAT_SOCKET_RAW* RawSocket = CXPLAT_ALLOC_PAGED(RawSocketLength, QUIC_POOL_SOCKET); + if (RawSocket == NULL) { + QuicTraceEvent( + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "CXPLAT_SOCKET", + RawSocketLength); + Status = QUIC_STATUS_OUT_OF_MEMORY; + goto Error; + } + CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); + fprintf(stderr, "RawSocket allocated %p\n", RawSocket); + + QuicTraceEvent( + DatapathCreated, + "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", + Socket, + CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress), + CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress)); + + ZeroMemory(RawSocket, RawSocketLength); Socket->Datapath = Datapath; Socket->ClientContext = Config->CallbackContext; Socket->HasFixedRemoteAddress = (Config->RemoteAddress != NULL); @@ -1935,6 +1958,9 @@ MANGLE(CxPlatSocketCreateUdp)( Socket->Processors[i].IoStarted = TRUE; } + *NewSocket = Socket; + Socket = NULL; + RawSocket = NULL; Status = QUIC_STATUS_SUCCESS; Error: @@ -1973,6 +1999,7 @@ CxPlatSocketCreateTcpInternal( Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; } + fprintf(stderr, "Tcp Socket allocated :%p\n", Socket); QuicTraceEvent( DatapathCreated, @@ -2476,7 +2503,8 @@ CxPlatSocketRelease( CXPLAT_DBG_ASSERT(!Socket->Freed); CXPLAT_DBG_ASSERT(Socket->Uninitialized); Socket->Freed = TRUE; - // CXPLAT_FREE(Socket, QUIC_POOL_SOCKET); + fprintf(stderr, "RawSocket deleting internal %p\n", CxPlatSocketToRaw(Socket)); + CXPLAT_FREE(CxPlatSocketToRaw(Socket), QUIC_POOL_SOCKET); } } @@ -2524,6 +2552,7 @@ CxPlatSocketContextRelease( } SocketProc->Freed = TRUE; + fprintf(stderr, "SocketProc freed :%p\n", SocketProc); CxPlatSocketRelease((CXPLAT_SOCKET*)SocketProc->Parent); // } } @@ -4702,5 +4731,6 @@ const struct CXPLAT_DATAPATH_FUNCTIONS DataPathUserFuncs = { MANGLE(CxPlatSendDataFreeBuffer), MANGLE(CxPlatSendDataIsFull), MANGLE(CxPlatSocketSend), - MANGLE(CxPlatDataPathProcessCqe) + MANGLE(CxPlatDataPathProcessCqe), + MANGLE(QuicCopyRouteInfo) }; diff --git a/src/platform/platform_worker.c b/src/platform/platform_worker.c index 64c1abc29c..5fd9cdf0a5 100644 --- a/src/platform/platform_worker.c +++ b/src/platform/platform_worker.c @@ -213,7 +213,7 @@ CxPlatWorkersLazyStart( CxPlatRundownInitialize(&CxPlatWorkerRundown); CxPlatLockRelease(&CxPlatWorkerLock); - + fprintf(stderr, "WorkersLazyStart\n"); return TRUE; Error: @@ -267,6 +267,7 @@ CxPlatWorkersUninit( void ) { + fprintf(stderr, "CxPlatWorkersUninit %p\n", CxPlatWorkers); if (CxPlatWorkers != NULL) { CxPlatRundownReleaseAndWait(&CxPlatWorkerRundown); @@ -299,6 +300,7 @@ CxPlatWorkersUninit( } CxPlatLockUninitialize(&CxPlatWorkerLock); + fprintf(stderr, "CxPlatWorkersUninit done\n"); } CXPLAT_EVENTQ* @@ -481,7 +483,7 @@ CXPLAT_THREAD_CALLBACK(CxPlatWorkerThread, Context) { CXPLAT_WORKER* Worker = (CXPLAT_WORKER*)Context; CXPLAT_DBG_ASSERT(Worker != NULL); - + fprintf(stderr, "Worker start\n"); QuicTraceLogInfo( PlatformWorkerThreadStart, "[ lib][%p] Worker start", diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 1adcafeadf..331252c76f 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -252,6 +252,7 @@ TEST(ParameterValidation, ValidateConnectionParam) { ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_VALIDATE_CONNECTION_PARAM)); } else { QuicTestConnectionParam(); + fprintf(stderr, "exited Registration scope\n"); } } diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index 6ed36ec25a..a4a045abf3 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -2104,33 +2104,33 @@ void QuicTestStatefulGlobalSetParam() // // Set QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE after connection start (MsQuicLib.InUse) // - { - TestScopeLogger LogScope1("Set QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE after connection start (MsQuicLib.InUse)"); - GlobalSettingScope ParamScope(QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE); - MsQuicAlpn Alpn("MsQuicTest"); - MsQuicCredentialConfig ClientCredConfig; - MsQuicConfiguration ClientConfiguration(Registration, Alpn, ClientCertCredConfig); - TEST_TRUE(ClientConfiguration.IsValid()); - MsQuicConnection Connection(Registration); - TEST_QUIC_SUCCEEDED(Connection.GetInitStatus()); - TEST_QUIC_SUCCEEDED( - MsQuic->ConnectionStart( - Connection.Handle, - ClientConfiguration, - QUIC_ADDRESS_FAMILY_INET, - "localhost", - 4433)); - TEST_TRUE(WaitForMsQuicInUse()); // Waiting for to set MsQuicLib.InUse = TRUE - - uint16_t Mode = QUIC_LOAD_BALANCING_SERVER_ID_IP; - TEST_QUIC_STATUS( - QUIC_STATUS_INVALID_STATE, - MsQuic->SetParam( - nullptr, - QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE, - sizeof(Mode), - &Mode)); - } + // { + // TestScopeLogger LogScope1("Set QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE after connection start (MsQuicLib.InUse)"); + // GlobalSettingScope ParamScope(QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE); + // MsQuicAlpn Alpn("MsQuicTest"); + // MsQuicCredentialConfig ClientCredConfig; + // MsQuicConfiguration ClientConfiguration(Registration, Alpn, ClientCertCredConfig); + // TEST_TRUE(ClientConfiguration.IsValid()); + // MsQuicConnection Connection(Registration); + // TEST_QUIC_SUCCEEDED(Connection.GetInitStatus()); + // TEST_QUIC_SUCCEEDED( + // MsQuic->ConnectionStart( + // Connection.Handle, + // ClientConfiguration, + // QUIC_ADDRESS_FAMILY_INET, + // "localhost", + // 4433)); + // TEST_TRUE(WaitForMsQuicInUse()); // Waiting for to set MsQuicLib.InUse = TRUE + + // uint16_t Mode = QUIC_LOAD_BALANCING_SERVER_ID_IP; + // TEST_QUIC_STATUS( + // QUIC_STATUS_INVALID_STATE, + // MsQuic->SetParam( + // nullptr, + // QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE, + // sizeof(Mode), + // &Mode)); + // } { TestScopeLogger LogScope1("Get QUIC_PARAM_GLOBAL_DATAPATH_FEATURES after Datapath is made (MsQuicLib.Datapath)"); @@ -3214,44 +3214,45 @@ void QuicTest_QUIC_PARAM_CONN_QUIC_VERSION(MsQuicRegistration& Registration, MsQ TestScopeLogger LogScope0("QUIC_PARAM_CONN_QUIC_VERSION"); MsQuicConnection Connection(Registration); TEST_QUIC_SUCCEEDED(Connection.GetInitStatus()); + UNREFERENCED_PARAMETER(ClientConfiguration); // // SetParam // - { - TestScopeLogger LogScope1("SetParam is not allowed"); - uint32_t Dummy = 0; - TEST_QUIC_STATUS( - QUIC_STATUS_INVALID_PARAMETER, - Connection.SetParam( - QUIC_PARAM_CONN_QUIC_VERSION, - sizeof(Dummy), - &Dummy)); - } + // { + // TestScopeLogger LogScope1("SetParam is not allowed"); + // uint32_t Dummy = 0; + // TEST_QUIC_STATUS( + // QUIC_STATUS_INVALID_PARAMETER, + // Connection.SetParam( + // QUIC_PARAM_CONN_QUIC_VERSION, + // sizeof(Dummy), + // &Dummy)); + // } - // - // GetParam - // + // // + // // GetParam + // // { - TestScopeLogger LogScope1("GetParam"); - uint32_t Length = 0; - TEST_QUIC_STATUS( - QUIC_STATUS_BUFFER_TOO_SMALL, - Connection.GetParam( - QUIC_PARAM_CONN_QUIC_VERSION, - &Length, - nullptr)); - TEST_EQUAL(Length, sizeof(uint32_t)); - - uint32_t Version = 65535; - { - TestScopeLogger LogScope2("Version == 0 before start"); - TEST_QUIC_SUCCEEDED( - Connection.GetParam( - QUIC_PARAM_CONN_QUIC_VERSION, - &Length, - &Version)); - TEST_EQUAL(Version, 0); - } + // TestScopeLogger LogScope1("GetParam"); + // uint32_t Length = 0; + // TEST_QUIC_STATUS( + // QUIC_STATUS_BUFFER_TOO_SMALL, + // Connection.GetParam( + // QUIC_PARAM_CONN_QUIC_VERSION, + // &Length, + // nullptr)); + // TEST_EQUAL(Length, sizeof(uint32_t)); + + // uint32_t Version = 65535; + // { + // TestScopeLogger LogScope2("Version == 0 before start"); + // TEST_QUIC_SUCCEEDED( + // Connection.GetParam( + // QUIC_PARAM_CONN_QUIC_VERSION, + // &Length, + // &Version)); + // TEST_EQUAL(Version, 0); + // } { TestScopeLogger LogScope2("Version == 1 after start"); @@ -3262,12 +3263,14 @@ void QuicTest_QUIC_PARAM_CONN_QUIC_VERSION(MsQuicRegistration& Registration, MsQ QUIC_ADDRESS_FAMILY_INET, "localhost", 4433)); - TEST_QUIC_SUCCEEDED( - Connection.GetParam( - QUIC_PARAM_CONN_QUIC_VERSION, - &Length, - &Version)); - TEST_EQUAL(Version, 1); + CxPlatSleep(1000); + fprintf(stderr, "took 1 sec\n"); + // TEST_QUIC_SUCCEEDED( + // Connection.GetParam( + // QUIC_PARAM_CONN_QUIC_VERSION, + // &Length, + // &Version)); + // TEST_EQUAL(Version, 1); } } } @@ -4429,30 +4432,31 @@ void QuicTestConnectionParam() MsQuicConfiguration ClientConfiguration(Registration, Alpn, ClientCertCredConfig); QuicTest_QUIC_PARAM_CONN_QUIC_VERSION(Registration, ClientConfiguration); - QuicTest_QUIC_PARAM_CONN_LOCAL_ADDRESS(Registration, ClientConfiguration); - QuicTest_QUIC_PARAM_CONN_REMOTE_ADDRESS(Registration, ClientConfiguration); - QuicTest_QUIC_PARAM_CONN_IDEAL_PROCESSOR(Registration); - QuicTest_QUIC_PARAM_CONN_SETTINGS(Registration, ClientConfiguration); - QuicTest_QUIC_PARAM_CONN_STATISTICS(Registration); - QuicTest_QUIC_PARAM_CONN_STATISTICS_PLAT(Registration); - QuicTest_QUIC_PARAM_CONN_SHARE_UDP_BINDING(Registration, ClientConfiguration); - QuicTest_QUIC_PARAM_CONN_LOCAL_BIDI_STREAM_COUNT(Registration); - QuicTest_QUIC_PARAM_CONN_LOCAL_UNIDI_STREAM_COUNT(Registration); - QuicTest_QUIC_PARAM_CONN_MAX_STREAM_IDS(Registration); - QuicTest_QUIC_PARAM_CONN_CLOSE_REASON_PHRASE(Registration); - QuicTest_QUIC_PARAM_CONN_STREAM_SCHEDULING_SCHEME(Registration); - QuicTest_QUIC_PARAM_CONN_DATAGRAM_RECEIVE_ENABLED(Registration, ClientConfiguration); - QuicTest_QUIC_PARAM_CONN_DATAGRAM_SEND_ENABLED(Registration); - QuicTest_QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION(Registration, ClientConfiguration); - // QUIC_PARAM_CONN_RESUMPTION_TICKET is covered by TestConnection.cpp and EventTest.cpp - QuicTest_QUIC_PARAM_CONN_PEER_CERTIFICATE_VALID(Registration); - QuicTest_QUIC_PARAM_CONN_LOCAL_INTERFACE(Registration, ClientConfiguration); - QuicTest_QUIC_PARAM_CONN_TLS_SECRETS(Registration, ClientConfiguration); - // QUIC_PARAM_CONN_VERSION_SETTINGS is covered by QuicTestVersionSettings - QuicTest_QUIC_PARAM_CONN_CIBIR_ID(Registration, ClientConfiguration); - QuicTest_QUIC_PARAM_CONN_STATISTICS_V2(Registration); - QuicTest_QUIC_PARAM_CONN_STATISTICS_V2_PLAT(Registration); - QuicTest_QUIC_PARAM_CONN_ORIG_DEST_CID(Registration, ClientConfiguration); + fprintf(stderr, "exited Connection scope\n"); + // QuicTest_QUIC_PARAM_CONN_LOCAL_ADDRESS(Registration, ClientConfiguration); + // QuicTest_QUIC_PARAM_CONN_REMOTE_ADDRESS(Registration, ClientConfiguration); + // QuicTest_QUIC_PARAM_CONN_IDEAL_PROCESSOR(Registration); + // QuicTest_QUIC_PARAM_CONN_SETTINGS(Registration, ClientConfiguration); + // QuicTest_QUIC_PARAM_CONN_STATISTICS(Registration); + // QuicTest_QUIC_PARAM_CONN_STATISTICS_PLAT(Registration); + // QuicTest_QUIC_PARAM_CONN_SHARE_UDP_BINDING(Registration, ClientConfiguration); + // QuicTest_QUIC_PARAM_CONN_LOCAL_BIDI_STREAM_COUNT(Registration); + // QuicTest_QUIC_PARAM_CONN_LOCAL_UNIDI_STREAM_COUNT(Registration); + // QuicTest_QUIC_PARAM_CONN_MAX_STREAM_IDS(Registration); + // QuicTest_QUIC_PARAM_CONN_CLOSE_REASON_PHRASE(Registration); + // QuicTest_QUIC_PARAM_CONN_STREAM_SCHEDULING_SCHEME(Registration); + // QuicTest_QUIC_PARAM_CONN_DATAGRAM_RECEIVE_ENABLED(Registration, ClientConfiguration); + // QuicTest_QUIC_PARAM_CONN_DATAGRAM_SEND_ENABLED(Registration); + // QuicTest_QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION(Registration, ClientConfiguration); + // // QUIC_PARAM_CONN_RESUMPTION_TICKET is covered by TestConnection.cpp and EventTest.cpp + // QuicTest_QUIC_PARAM_CONN_PEER_CERTIFICATE_VALID(Registration); + // QuicTest_QUIC_PARAM_CONN_LOCAL_INTERFACE(Registration, ClientConfiguration); + // QuicTest_QUIC_PARAM_CONN_TLS_SECRETS(Registration, ClientConfiguration); + // // QUIC_PARAM_CONN_VERSION_SETTINGS is covered by QuicTestVersionSettings + // QuicTest_QUIC_PARAM_CONN_CIBIR_ID(Registration, ClientConfiguration); + // QuicTest_QUIC_PARAM_CONN_STATISTICS_V2(Registration); + // QuicTest_QUIC_PARAM_CONN_STATISTICS_V2_PLAT(Registration); + // QuicTest_QUIC_PARAM_CONN_ORIG_DEST_CID(Registration, ClientConfiguration); } // From d7d58b5f2520892e169bd2a8ccebc31478138072 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 8 Aug 2023 14:09:49 -0700 Subject: [PATCH 37/87] tmp --- src/platform/datapath_win.c | 2 +- src/platform/datapath_winuser.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 87b2ec662c..a6e8b4b1fd 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -420,7 +420,7 @@ CxPlatSocketCreateUdp( goto Error; } - if (*NewSocket) { + if (Datapath->RawDataPath) { Status = CxPlatInitRawSocket( Datapath->RawDataPath, Config, diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 64bca223e7..fdeccdfed4 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -792,7 +792,7 @@ CxPlatDataPathQuerySockoptSupport( } } - Datapath->Features |= CXPLAT_DATAPATH_FEATURE_TCP; + // Datapath->Features |= CXPLAT_DATAPATH_FEATURE_TCP; Error: From f8ab0fad330706babac148f906dcfdf92fc87c61 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 8 Aug 2023 15:45:00 -0700 Subject: [PATCH 38/87] fix merge side effects --- src/core/path.h | 7 ------- src/platform/datapath_raw_socket.c | 10 ---------- src/test/lib/MtuTest.cpp | 2 ++ 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/src/core/path.h b/src/core/path.h index d6dc6f07df..158f6fc14f 100644 --- a/src/core/path.h +++ b/src/core/path.h @@ -302,13 +302,6 @@ QuicConnGetPathForDatagram( _In_ const CXPLAT_RECV_DATA* Datagram ); -_IRQL_requires_max_(PASSIVE_LEVEL) -void -QuicCopyRouteInfo( - _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute - ); - // // Plumbs new or removes existing QUIC encryption offload information. // diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index af3ee155cc..1880833c03 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -61,16 +61,6 @@ CxPlatGetSocket( } _IRQL_requires_max_(PASSIVE_LEVEL) -void -QuicCopyRouteInfo( - _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute - ) -{ - CxPlatCopyMemory(DstRoute, SrcRoute, (uint8_t*)&SrcRoute->State - (uint8_t*)SrcRoute); - CxPlatUpdateRoute(DstRoute, SrcRoute); -} - void MANGLE(QuicCopyRouteInfo)( _Inout_ CXPLAT_ROUTE* DstRoute, diff --git a/src/test/lib/MtuTest.cpp b/src/test/lib/MtuTest.cpp index a57a05013a..f87024e875 100644 --- a/src/test/lib/MtuTest.cpp +++ b/src/test/lib/MtuTest.cpp @@ -49,6 +49,8 @@ struct ResetSettings { void QuicTestMtuSettings() { + MsQuicRegistration Registration(true); + TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) const uint16_t DefaultMaximumMtu = UseQTIP ? 1488 : 1500; // reserve 12B for TCP header #else From 88f4f7cba913926e153dc792ea4c3e2f7d1b97d8 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Thu, 17 Aug 2023 16:11:49 -0700 Subject: [PATCH 39/87] all tests passed --- src/core/binding.c | 2 + src/core/binding.h | 6 + src/core/connection.c | 4 +- src/core/packet.c | 6 +- src/inc/quic_datapath.h | 39 +++++- src/platform/datapath_epoll.c | 4 +- src/platform/datapath_kqueue.c | 4 +- src/platform/datapath_raw_dpdk.c | 4 +- src/platform/datapath_raw_socket.c | 2 + src/platform/datapath_raw_socket_win.c | 2 +- src/platform/datapath_raw_win.c | 4 +- src/platform/datapath_raw_xdp_win.c | 4 +- src/platform/datapath_win.c | 106 ++++++++------ src/platform/datapath_winuser.c | 10 +- src/platform/platform_worker.c | 4 - src/test/bin/quic_gtest.cpp | 11 +- src/test/lib/ApiTest.cpp | 185 ++++++++++++------------- src/test/lib/HandshakeTest.cpp | 4 +- 18 files changed, 238 insertions(+), 163 deletions(-) diff --git a/src/core/binding.c b/src/core/binding.c index c6e5669ce6..c5da045722 100644 --- a/src/core/binding.c +++ b/src/core/binding.c @@ -1151,6 +1151,7 @@ QuicBindingPreprocessDatagram( CxPlatZeroMemory(&Packet->PacketNumber, sizeof(CXPLAT_RECV_PACKET) - sizeof(uint64_t)); Packet->Buffer = Datagram->Buffer; Packet->BufferLength = Datagram->BufferLength; + Packet->BufferFrom = Datagram->BufferFrom; *ReleaseDatagram = TRUE; @@ -1661,6 +1662,7 @@ QuicBindingReceive( ProcShifted | InterlockedIncrement64((int64_t*)&MsQuicLib.PerProc[Proc].ReceivePacketId); Packet->Buffer = Datagram->Buffer; Packet->BufferLength = Datagram->BufferLength; + Packet->BufferFrom = Datagram->BufferFrom; CXPLAT_DBG_ASSERT(Packet->PacketId != 0); QuicTraceEvent( diff --git a/src/core/binding.h b/src/core/binding.h index d120aebf5e..a86bf3ec89 100644 --- a/src/core/binding.h +++ b/src/core/binding.h @@ -152,6 +152,12 @@ typedef struct CXPLAT_RECV_PACKET { // BOOLEAN HasNonProbingFrame : 1; + // + // + // + uint16_t BufferFrom : 2; + + uint16_t Reserved : 2; } CXPLAT_RECV_PACKET; typedef enum QUIC_BINDING_LOOKUP_TYPE { diff --git a/src/core/connection.c b/src/core/connection.c index f8942ccdcd..7839776c23 100644 --- a/src/core/connection.c +++ b/src/core/connection.c @@ -3728,7 +3728,7 @@ QuicConnGetKeyOrDeferDatagram( while (*Tail != NULL) { Tail = &((*Tail)->Next); } - *Tail = CxPlatDataPathRecvPacketToRecvData(Packet); + *Tail = CxPlatDataPathRecvPacketToRecvData(Packet, Packet->BufferFrom); (*Tail)->Next = NULL; } } @@ -4588,7 +4588,7 @@ QuicConnRecvFrames( if (QuicBindingQueueStatelessOperation( Connection->Paths[0].Binding, QUIC_OPER_TYPE_VERSION_NEGOTIATION, - CxPlatDataPathRecvPacketToRecvData(Packet))) { + CxPlatDataPathRecvPacketToRecvData(Packet, Packet->BufferFrom))) { Packet->ReleaseDeferred = TRUE; } QuicConnCloseLocally( diff --git a/src/core/packet.c b/src/core/packet.c index 0d18945362..c8b4319b77 100644 --- a/src/core/packet.c +++ b/src/core/packet.c @@ -551,7 +551,7 @@ QuicPacketValidateInitialToken( } const CXPLAT_RECV_DATA* Datagram = - CxPlatDataPathRecvPacketToRecvData(Packet); + CxPlatDataPathRecvPacketToRecvData(Packet, Packet->BufferFrom); if (!QuicAddrCompare(&Token.Encrypted.RemoteAddress, &Datagram->Route->RemoteAddress)) { QuicPacketLogDrop(Owner, Packet, "Retry Token Addr Mismatch"); *DropPacket = TRUE; @@ -812,7 +812,7 @@ QuicPacketLogDrop( ) { const CXPLAT_RECV_DATA* Datagram = // cppcheck-suppress unreadVariable; NOLINT - CxPlatDataPathRecvPacketToRecvData(Packet); + CxPlatDataPathRecvPacketToRecvData(Packet, Packet->BufferFrom); if (Packet->AssignedToConnection) { InterlockedIncrement64((int64_t*)&((QUIC_CONNECTION*)Owner)->Stats.Recv.DroppedPackets); @@ -846,7 +846,7 @@ QuicPacketLogDropWithValue( ) { const CXPLAT_RECV_DATA* Datagram = // cppcheck-suppress unreadVariable; NOLINT - CxPlatDataPathRecvPacketToRecvData(Packet); + CxPlatDataPathRecvPacketToRecvData(Packet, Packet->BufferFrom); if (Packet->AssignedToConnection) { InterlockedIncrement64((int64_t*)&((QUIC_CONNECTION*)Owner)->Stats.Recv.DroppedPackets); diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 15117c1688..551cfedec8 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -172,7 +172,7 @@ typedef enum CXPLAT_BUFFER_FROM { typedef struct CXPLAT_SEND_DATA_INTERNAL CXPLAT_SEND_DATA_INTERNAL; typedef struct CXPLAT_SEND_DATA { - CXPLAT_BUFFER_FROM BufferFrom : 2; + uint16_t BufferFrom : 2; // // The type of ECN markings needed for send. @@ -268,7 +268,7 @@ typedef struct CXPLAT_RECV_DATA { // uint16_t Allocated : 1; // Used for debugging. Set to FALSE on free. uint16_t QueuedOnConnection : 1; // Used for debugging. - CXPLAT_BUFFER_FROM BufferFrom : 2; + uint16_t BufferFrom : 2; uint16_t Reserved : 4; uint16_t ReservedEx : 8; @@ -321,7 +321,8 @@ typedef struct CXPLAT_QEO_CONNECTION { // CXPLAT_RECV_DATA* CxPlatDataPathRecvPacketToRecvData( - _In_ const CXPLAT_RECV_PACKET* const RecvPacket + _In_ const CXPLAT_RECV_PACKET* const RecvPacket, + _In_ uint16_t BufferFrom ); // @@ -1032,6 +1033,38 @@ XDP_CxPlatResolveRouteComplete( _In_ uint8_t PathId ); +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +XDP_CxPlatResolveRoute( + _In_ CXPLAT_SOCKET* Sock, + _Inout_ CXPLAT_ROUTE* Route, + _In_ uint8_t PathId, + _In_ void* Context, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback + ); + +void +XDP_CxPlatDataPathProcessCqe( + _In_ CXPLAT_CQE* Cqe + ); + +CXPLAT_RECV_PACKET* +XDP_CxPlatDataPathRecvDataToRecvPacket( + _In_ const CXPLAT_RECV_DATA* const Datagram + ); + +CXPLAT_RECV_DATA* +XDP_CxPlatDataPathRecvPacketToRecvData( + _In_ const CXPLAT_RECV_PACKET* const Context + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +XDP_CxPlatUpdateRoute( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ); + #if defined(__cplusplus) } #endif diff --git a/src/platform/datapath_epoll.c b/src/platform/datapath_epoll.c index cec3d9f7be..99e45e982c 100644 --- a/src/platform/datapath_epoll.c +++ b/src/platform/datapath_epoll.c @@ -1718,9 +1718,11 @@ CxPlatSocketGetRemoteAddress( CXPLAT_RECV_DATA* CxPlatDataPathRecvPacketToRecvData( - _In_ const CXPLAT_RECV_PACKET* const Packet + _In_ const CXPLAT_RECV_PACKET* const Packet, + _In_ uint16_t BufferFrom ) { + UNREFERENCED_PARAMETER(BufferFrom); return (CXPLAT_RECV_DATA*)((char *)Packet - sizeof(CXPLAT_RECV_DATA)); } diff --git a/src/platform/datapath_kqueue.c b/src/platform/datapath_kqueue.c index 9a345c7321..00830e0802 100644 --- a/src/platform/datapath_kqueue.c +++ b/src/platform/datapath_kqueue.c @@ -1746,9 +1746,11 @@ CxPlatSocketGetRemoteAddress( CXPLAT_RECV_DATA* CxPlatDataPathRecvPacketToRecvData( - _In_ const CXPLAT_RECV_PACKET* const Packet + _In_ const CXPLAT_RECV_PACKET* const Packet, + _In_ uint16_t BufferFrom ) { + UNREFERENCED_PARAMETER(BufferFrom); CXPLAT_DATAPATH_RECV_BLOCK* RecvBlock = (CXPLAT_DATAPATH_RECV_BLOCK*) ((char *)Packet - sizeof(CXPLAT_DATAPATH_RECV_BLOCK)); diff --git a/src/platform/datapath_raw_dpdk.c b/src/platform/datapath_raw_dpdk.c index a10e0656ab..99d414402e 100644 --- a/src/platform/datapath_raw_dpdk.c +++ b/src/platform/datapath_raw_dpdk.c @@ -85,9 +85,11 @@ static int CxPlatDpdkWorkerThread(_In_ void* Context); CXPLAT_RECV_DATA* CxPlatDataPathRecvPacketToRecvData( - _In_ const CXPLAT_RECV_PACKET* const Context + _In_ const CXPLAT_RECV_PACKET* const Context, + _In_ uint16_t BufferFrom ) { + UNREFERENCED_PARAMETER(BufferFrom) return (CXPLAT_RECV_DATA*)(((uint8_t*)Context) - sizeof(DPDK_RX_PACKET)); } diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index 1880833c03..0b79a58556 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -163,6 +163,7 @@ CxPlatDpRawParseUdp( Packet->Buffer = (uint8_t*)Udp->Data; Packet->BufferLength = QuicNetByteSwapShort(Udp->Length) - sizeof(UDP_HEADER); + Packet->BufferFrom = CXPLAT_BUFFER_FROM_XDP; } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -232,6 +233,7 @@ CxPlatDpRawParseTcp( Packet->Buffer = (uint8_t*)(Tcp) + HeaderLength; Packet->BufferLength = Length; + Packet->BufferFrom = CXPLAT_BUFFER_FROM_XDP; Packet->ReservedEx = HeaderLength; } diff --git a/src/platform/datapath_raw_socket_win.c b/src/platform/datapath_raw_socket_win.c index 64ddd14bbe..fd1b6c26d3 100644 --- a/src/platform/datapath_raw_socket_win.c +++ b/src/platform/datapath_raw_socket_win.c @@ -85,7 +85,7 @@ MANGLE(CxPlatResolveRoute)( _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback ) { - CXPLAT_SOCKET_RAW* Socket = (CXPLAT_SOCKET_RAW*)Sock; + CXPLAT_SOCKET_RAW* Socket = CxPlatSocketToRaw(Sock); NETIO_STATUS Status = ERROR_SUCCESS; MIB_IPFORWARD_ROW2 IpforwardRow = {0}; CXPLAT_ROUTE_STATE State = Route->State; diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index b9584ddae8..cec6c73d7b 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -148,7 +148,7 @@ CxPlatInitRawDataPath( Error: - if (DataPath != NULL) { + if (QUIC_FAILED(Status)) { #if DEBUG DataPath->Uninitialized = TRUE; #endif @@ -486,7 +486,7 @@ CxPlatDpRawRxEthernet( CXPLAT_DBG_ASSERT(Packets[i+1]->Next == NULL); i++; } - Datapath->ParentDataPath->UdpHandlers.Receive((CXPLAT_SOCKET*)Socket, Socket->CallbackContext, (CXPLAT_RECV_DATA*)PacketChain); + Datapath->ParentDataPath->UdpHandlers.Receive(CxPlatRawToSocket(Socket), Socket->CallbackContext, (CXPLAT_RECV_DATA*)PacketChain); } else if (PacketChain->Reserved == L4_TYPE_TCP_SYN || PacketChain->Reserved == L4_TYPE_TCP_SYNACK) { CxPlatDpRawSocketAckSyn(Socket, PacketChain); CxPlatDpRawRxFree(PacketChain); diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index bf11b34f6f..0ef2b31cdc 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -1932,7 +1932,7 @@ MANGLE(CxPlatDataPathProcessCqe)( CONTAINING_RECORD(CxPlatCqeUserData(Cqe), DATAPATH_XDP_IO_SQE, DatapathSqe); XDP_QUEUE* Queue; - if (Sqe->IoType == DATAPATH_IO_RECV) { + if (Sqe->IoType == DATAPATH_XDP_IO_RECV) { Queue = CONTAINING_RECORD(Sqe, XDP_QUEUE, RxIoSqe); QuicTraceLogVerbose( XdpQueueAsyncIoRxComplete, @@ -1940,7 +1940,7 @@ MANGLE(CxPlatDataPathProcessCqe)( Queue); Queue->RxQueued = FALSE; } else { - CXPLAT_DBG_ASSERT(Sqe->IoType == DATAPATH_IO_SEND); + CXPLAT_DBG_ASSERT(Sqe->IoType == DATAPATH_XDP_IO_SEND); Queue = CONTAINING_RECORD(Sqe, XDP_QUEUE, TxIoSqe); QuicTraceLogVerbose( XdpQueueAsyncIoTxComplete, diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index a6e8b4b1fd..9fc2751a69 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -20,15 +20,25 @@ #pragma warning(disable:4100) // unreferenced #pragma warning(disable:6101) // uninitialized +#define IS_LOOPBACK(RemoteAddress) ((RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET && \ + RemoteAddress.Ipv4.sin_addr.S_un.S_addr == htonl(INADDR_LOOPBACK)) || \ + (RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET6 && \ + IN6_IS_ADDR_LOOPBACK(&RemoteAddress.Ipv6.sin6_addr))) + CXPLAT_RECV_DATA* CxPlatDataPathRecvPacketToRecvData( - _In_ const CXPLAT_RECV_PACKET* const Context + _In_ const CXPLAT_RECV_PACKET* const Context, + _In_ uint16_t BufferFrom ) { - return DataPathUserFuncs.CxPlatDataPathRecvPacketToRecvData(Context); - // TODO: xdp - // Or inline the function - // use global variable to store the offset? set at init phase + if (BufferFrom == CXPLAT_BUFFER_FROM_USER) { + return DataPathUserFuncs.CxPlatDataPathRecvPacketToRecvData(Context); + } else if (BufferFrom == CXPLAT_BUFFER_FROM_XDP) { + return XDP_CxPlatDataPathRecvPacketToRecvData(Context); + } else { + CXPLAT_DBG_ASSERT(FALSE); + } + return NULL; } CXPLAT_RECV_PACKET* @@ -36,10 +46,14 @@ CxPlatDataPathRecvDataToRecvPacket( _In_ const CXPLAT_RECV_DATA* const Datagram ) { - return DataPathUserFuncs.CxPlatDataPathRecvDataToRecvPacket(Datagram); - // TODO: xdp - // Or inline the function - // use global variable to store the offset? set at init phase + if (Datagram->BufferFrom == CXPLAT_BUFFER_FROM_USER) { + return DataPathUserFuncs.CxPlatDataPathRecvDataToRecvPacket(Datagram); + } else if (Datagram->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { + return XDP_CxPlatDataPathRecvDataToRecvPacket(Datagram); + } else { + CXPLAT_DBG_ASSERT(FALSE); + } + return NULL; } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -148,12 +162,12 @@ CxPlatDataPathInitialize( } CxPlatZeroMemory(RawDataPath, RawDatapathSize); - Status = QUIC_STATUS_INVALID_PARAMETER; - // Status = CxPlatInitRawDataPath( - // ClientRecvContextLength, - // Config, - // DataPath, - // RawDataPath); + // Status = QUIC_STATUS_INVALID_PARAMETER; + Status = CxPlatInitRawDataPath( + ClientRecvContextLength, + Config, + DataPath, + RawDataPath); if (QUIC_FAILED(Status)) { QuicTraceLogVerbose( RawDatapathInitFail, @@ -165,7 +179,6 @@ CxPlatDataPathInitialize( DataPath->RawDataPath = RawDataPath; *NewDataPath = DataPath; - fprintf(stderr, "DataPath initialize\n"); Error: // TODO: error handling @@ -369,9 +382,6 @@ CxPlatSocketCreateUdp( _Out_ CXPLAT_SOCKET** NewSocket ) { - fprintf(stderr, "CxPlatSocketCreateUdp\n"); - // return CxPlatSocketCreateUdp_OLD(Datapath, Config, NewSocket); - #pragma warning(push) #pragma warning(suppress:4701) QUIC_STATUS Status = QUIC_STATUS_SUCCESS; @@ -416,7 +426,7 @@ CxPlatSocketCreateUdp( if (QUIC_FAILED(Status)) { QuicTraceLogVerbose( SockCreateFail, - "[sock] Failed to create socket, status:%d", Status); + "[sock] Failed to create socket, status:%d", Status); goto Error; } @@ -441,7 +451,6 @@ CxPlatSocketCreateUdp( // if (Socket) { // // TODO: break by MultiBindListener test by the RawSocket doesn't have ->Datapath // CxPlatSocketDelete(Socket); - // } #pragma warning(pop) return Status; @@ -542,7 +551,6 @@ CxPlatSocketDelete( CxPlatRawSocketDelete(CxPlatSocketToRaw(Socket)); } - fprintf(stderr, "RawSocket deleting %p\n", CxPlatSocketToRaw(Socket)); DataPathUserFuncs.CxPlatSocketDelete(Socket); // TODO: TCP socket cannot be free as RawSocket @@ -571,14 +579,9 @@ CxPlatSocketGetLocalMtu( _In_ CXPLAT_SOCKET* Socket ) { - // if RemoteAddress is "lo", use Socket mtu - // else if RawSocket is availabe, use RawSocket Mtu - // else use Socket Mtu + CXPLAT_DBG_ASSERT(Socket != NULL); if (Socket->Datapath && Socket->Datapath->RawDataPath && - !((Socket->RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET && - Socket->RemoteAddress.Ipv4.sin_addr.S_un.S_addr == htonl(INADDR_LOOPBACK)) || - (Socket->RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET6 && - IN6_IS_ADDR_LOOPBACK(&Socket->RemoteAddress.Ipv6.sin6_addr)))) { + !IS_LOOPBACK(Socket->RemoteAddress)) { XDP_CxPlatSocketGetLocalMtu(CxPlatSocketToRaw(Socket)); } return Socket->Mtu; @@ -632,10 +635,7 @@ CxPlatSendDataAlloc( { CXPLAT_SEND_DATA* SendData = NULL; if (Socket->Datapath && Socket->Datapath->RawDataPath && - !((Socket->RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET && - Socket->RemoteAddress.Ipv4.sin_addr.S_un.S_addr == htonl(INADDR_LOOPBACK)) || - (Socket->RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET6 && - IN6_IS_ADDR_LOOPBACK(&Socket->RemoteAddress.Ipv6.sin6_addr)))) { + !IS_LOOPBACK(Config->Route->RemoteAddress)) { SendData = XDP_CxPlatSendDataAlloc(CxPlatSocketToRaw(Socket), Config); if (SendData) { SendData->BufferFrom = CXPLAT_BUFFER_FROM_XDP; @@ -737,8 +737,24 @@ CxPlatDataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ) { - // what is the difference between datapath? - DataPathUserFuncs.CxPlatDataPathProcessCqe(Cqe); + // TODO: refactoring + switch (CxPlatCqeType(Cqe)) { + case CXPLAT_CQE_TYPE_SOCKET_IO: { + DATAPATH_IO_SQE* Sqe = + CONTAINING_RECORD(CxPlatCqeUserData(Cqe), DATAPATH_IO_SQE, DatapathSqe); + if (Sqe->IoType == 1480872005 || Sqe->IoType == 1480872006) { + XDP_CxPlatDataPathProcessCqe(Cqe); + } else { + DataPathUserFuncs.CxPlatDataPathProcessCqe(Cqe); + } + break; + } + case CXPLAT_CQE_TYPE_SOCKET_SHUTDOWN: { + XDP_CxPlatDataPathProcessCqe(Cqe); + break; + } + default: CXPLAT_DBG_ASSERT(FALSE); break; + } } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -748,8 +764,12 @@ QuicCopyRouteInfo( _In_ CXPLAT_ROUTE* SrcRoute ) { - // TODO: route to user/raw - *DstRoute = *SrcRoute; + if (!IS_LOOPBACK(SrcRoute->RemoteAddress)) { + CxPlatCopyMemory(DstRoute, SrcRoute, (uint8_t*)&SrcRoute->State - (uint8_t*)SrcRoute); + CxPlatUpdateRoute(DstRoute, SrcRoute); + } else { + *DstRoute = *SrcRoute; + } } void @@ -760,8 +780,7 @@ CxPlatResolveRouteComplete( _In_ uint8_t PathId ) { - // not 100% sure - if (Route->State == RouteUnresolved) { + if (Route->State != RouteResolved) { XDP_CxPlatResolveRouteComplete(Connection, Route, PhysicalAddress, PathId); } } @@ -779,7 +798,10 @@ CxPlatResolveRoute( _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback ) { - // TODO: which? + if (Socket->Datapath && Socket->Datapath->RawDataPath && + !IS_LOOPBACK(Route->RemoteAddress)) { + return XDP_CxPlatResolveRoute(Socket, Route, PathId, Context, Callback); + } Route->State = RouteResolved; return QUIC_STATUS_SUCCESS; } @@ -791,6 +813,8 @@ CxPlatUpdateRoute( _In_ CXPLAT_ROUTE* SrcRoute ) { - // TODO: which? + if (!IS_LOOPBACK(SrcRoute->RemoteAddress)) { + XDP_CxPlatUpdateRoute(DstRoute, SrcRoute); + } } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index fdeccdfed4..682afd0964 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1013,7 +1013,7 @@ CxPlatDataPathRelease( CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); WSACleanup(); CxPlatRundownRelease(&CxPlatWorkerRundown); - fprintf(stderr, "DataPath released\n"); + } } @@ -1389,7 +1389,6 @@ MANGLE(CxPlatSocketCreateUdp)( goto Error; } CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); - fprintf(stderr, "RawSocket allocated %p\n", RawSocket); QuicTraceEvent( DatapathCreated, @@ -1965,6 +1964,10 @@ MANGLE(CxPlatSocketCreateUdp)( Error: + if (RawSocket != NULL) { + MANGLE(CxPlatSocketDelete)(CxPlatRawToSocket(RawSocket)); + } + return Status; } @@ -1999,7 +2002,6 @@ CxPlatSocketCreateTcpInternal( Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; } - fprintf(stderr, "Tcp Socket allocated :%p\n", Socket); QuicTraceEvent( DatapathCreated, @@ -2503,7 +2505,6 @@ CxPlatSocketRelease( CXPLAT_DBG_ASSERT(!Socket->Freed); CXPLAT_DBG_ASSERT(Socket->Uninitialized); Socket->Freed = TRUE; - fprintf(stderr, "RawSocket deleting internal %p\n", CxPlatSocketToRaw(Socket)); CXPLAT_FREE(CxPlatSocketToRaw(Socket), QUIC_POOL_SOCKET); } } @@ -2552,7 +2553,6 @@ CxPlatSocketContextRelease( } SocketProc->Freed = TRUE; - fprintf(stderr, "SocketProc freed :%p\n", SocketProc); CxPlatSocketRelease((CXPLAT_SOCKET*)SocketProc->Parent); // } } diff --git a/src/platform/platform_worker.c b/src/platform/platform_worker.c index 5fd9cdf0a5..b4e2c96d43 100644 --- a/src/platform/platform_worker.c +++ b/src/platform/platform_worker.c @@ -213,7 +213,6 @@ CxPlatWorkersLazyStart( CxPlatRundownInitialize(&CxPlatWorkerRundown); CxPlatLockRelease(&CxPlatWorkerLock); - fprintf(stderr, "WorkersLazyStart\n"); return TRUE; Error: @@ -267,7 +266,6 @@ CxPlatWorkersUninit( void ) { - fprintf(stderr, "CxPlatWorkersUninit %p\n", CxPlatWorkers); if (CxPlatWorkers != NULL) { CxPlatRundownReleaseAndWait(&CxPlatWorkerRundown); @@ -300,7 +298,6 @@ CxPlatWorkersUninit( } CxPlatLockUninitialize(&CxPlatWorkerLock); - fprintf(stderr, "CxPlatWorkersUninit done\n"); } CXPLAT_EVENTQ* @@ -483,7 +480,6 @@ CXPLAT_THREAD_CALLBACK(CxPlatWorkerThread, Context) { CXPLAT_WORKER* Worker = (CXPLAT_WORKER*)Context; CXPLAT_DBG_ASSERT(Worker != NULL); - fprintf(stderr, "Worker start\n"); QuicTraceLogInfo( PlatformWorkerThreadStart, "[ lib][%p] Worker start", diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 331252c76f..bd50c1d7f6 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -252,7 +252,6 @@ TEST(ParameterValidation, ValidateConnectionParam) { ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_VALIDATE_CONNECTION_PARAM)); } else { QuicTestConnectionParam(); - fprintf(stderr, "exited Registration scope\n"); } } @@ -1515,6 +1514,11 @@ TEST_P(WithFamilyArgs, RebindAddr) { }; ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_NAT_ADDR_REBIND, Params)); } else { +#ifdef _WIN32 + if (!UseDuoNic) { + GTEST_SKIP_("Raw socket with 127.0.0.2/::2 is not supported"); + } +#endif QuicTestNatAddrRebind(GetParam().Family, 0); } } @@ -1536,6 +1540,11 @@ TEST_P(WithRebindPaddingArgs, RebindAddrPadded) { }; ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_NAT_PORT_REBIND, Params)); } else { +#ifdef _WIN32 + if (!UseDuoNic) { + GTEST_SKIP_("Raw socket with 127.0.0.2/::2 is not supported"); + } +#endif QuicTestNatAddrRebind(GetParam().Family, GetParam().Padding); } } diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index 307cdfa438..141eba8dc0 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -2104,33 +2104,33 @@ void QuicTestStatefulGlobalSetParam() // // Set QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE after connection start (MsQuicLib.InUse) // - // { - // TestScopeLogger LogScope1("Set QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE after connection start (MsQuicLib.InUse)"); - // GlobalSettingScope ParamScope(QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE); - // MsQuicAlpn Alpn("MsQuicTest"); - // MsQuicCredentialConfig ClientCredConfig; - // MsQuicConfiguration ClientConfiguration(Registration, Alpn, ClientCertCredConfig); - // TEST_TRUE(ClientConfiguration.IsValid()); - // MsQuicConnection Connection(Registration); - // TEST_QUIC_SUCCEEDED(Connection.GetInitStatus()); - // TEST_QUIC_SUCCEEDED( - // MsQuic->ConnectionStart( - // Connection.Handle, - // ClientConfiguration, - // QUIC_ADDRESS_FAMILY_INET, - // "localhost", - // 4433)); - // TEST_TRUE(WaitForMsQuicInUse()); // Waiting for to set MsQuicLib.InUse = TRUE - - // uint16_t Mode = QUIC_LOAD_BALANCING_SERVER_ID_IP; - // TEST_QUIC_STATUS( - // QUIC_STATUS_INVALID_STATE, - // MsQuic->SetParam( - // nullptr, - // QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE, - // sizeof(Mode), - // &Mode)); - // } + { + TestScopeLogger LogScope1("Set QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE after connection start (MsQuicLib.InUse)"); + GlobalSettingScope ParamScope(QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE); + MsQuicAlpn Alpn("MsQuicTest"); + MsQuicCredentialConfig ClientCredConfig; + MsQuicConfiguration ClientConfiguration(Registration, Alpn, ClientCertCredConfig); + TEST_TRUE(ClientConfiguration.IsValid()); + MsQuicConnection Connection(Registration); + TEST_QUIC_SUCCEEDED(Connection.GetInitStatus()); + TEST_QUIC_SUCCEEDED( + MsQuic->ConnectionStart( + Connection.Handle, + ClientConfiguration, + QUIC_ADDRESS_FAMILY_INET, + "localhost", + 4433)); + TEST_TRUE(WaitForMsQuicInUse()); // Waiting for to set MsQuicLib.InUse = TRUE + + uint16_t Mode = QUIC_LOAD_BALANCING_SERVER_ID_IP; + TEST_QUIC_STATUS( + QUIC_STATUS_INVALID_STATE, + MsQuic->SetParam( + nullptr, + QUIC_PARAM_GLOBAL_LOAD_BALACING_MODE, + sizeof(Mode), + &Mode)); + } { TestScopeLogger LogScope1("Get QUIC_PARAM_GLOBAL_DATAPATH_FEATURES after Datapath is made (MsQuicLib.Datapath)"); @@ -3236,45 +3236,45 @@ void QuicTest_QUIC_PARAM_CONN_QUIC_VERSION(MsQuicRegistration& Registration, MsQ TestScopeLogger LogScope0("QUIC_PARAM_CONN_QUIC_VERSION"); MsQuicConnection Connection(Registration); TEST_QUIC_SUCCEEDED(Connection.GetInitStatus()); - UNREFERENCED_PARAMETER(ClientConfiguration); + // // SetParam // - // { - // TestScopeLogger LogScope1("SetParam is not allowed"); - // uint32_t Dummy = 0; - // TEST_QUIC_STATUS( - // QUIC_STATUS_INVALID_PARAMETER, - // Connection.SetParam( - // QUIC_PARAM_CONN_QUIC_VERSION, - // sizeof(Dummy), - // &Dummy)); - // } + { + TestScopeLogger LogScope1("SetParam is not allowed"); + uint32_t Dummy = 0; + TEST_QUIC_STATUS( + QUIC_STATUS_INVALID_PARAMETER, + Connection.SetParam( + QUIC_PARAM_CONN_QUIC_VERSION, + sizeof(Dummy), + &Dummy)); + } - // // - // // GetParam - // // + // + // GetParam + // { - // TestScopeLogger LogScope1("GetParam"); - // uint32_t Length = 0; - // TEST_QUIC_STATUS( - // QUIC_STATUS_BUFFER_TOO_SMALL, - // Connection.GetParam( - // QUIC_PARAM_CONN_QUIC_VERSION, - // &Length, - // nullptr)); - // TEST_EQUAL(Length, sizeof(uint32_t)); - - // uint32_t Version = 65535; - // { - // TestScopeLogger LogScope2("Version == 0 before start"); - // TEST_QUIC_SUCCEEDED( - // Connection.GetParam( - // QUIC_PARAM_CONN_QUIC_VERSION, - // &Length, - // &Version)); - // TEST_EQUAL(Version, 0); - // } + TestScopeLogger LogScope1("GetParam"); + uint32_t Length = 0; + TEST_QUIC_STATUS( + QUIC_STATUS_BUFFER_TOO_SMALL, + Connection.GetParam( + QUIC_PARAM_CONN_QUIC_VERSION, + &Length, + nullptr)); + TEST_EQUAL(Length, sizeof(uint32_t)); + + uint32_t Version = 65535; + { + TestScopeLogger LogScope2("Version == 0 before start"); + TEST_QUIC_SUCCEEDED( + Connection.GetParam( + QUIC_PARAM_CONN_QUIC_VERSION, + &Length, + &Version)); + TEST_EQUAL(Version, 0); + } { TestScopeLogger LogScope2("Version == 1 after start"); @@ -3285,14 +3285,12 @@ void QuicTest_QUIC_PARAM_CONN_QUIC_VERSION(MsQuicRegistration& Registration, MsQ QUIC_ADDRESS_FAMILY_INET, "localhost", 4433)); - CxPlatSleep(1000); - fprintf(stderr, "took 1 sec\n"); - // TEST_QUIC_SUCCEEDED( - // Connection.GetParam( - // QUIC_PARAM_CONN_QUIC_VERSION, - // &Length, - // &Version)); - // TEST_EQUAL(Version, 1); + TEST_QUIC_SUCCEEDED( + Connection.GetParam( + QUIC_PARAM_CONN_QUIC_VERSION, + &Length, + &Version)); + TEST_EQUAL(Version, 1); } } } @@ -4454,31 +4452,30 @@ void QuicTestConnectionParam() MsQuicConfiguration ClientConfiguration(Registration, Alpn, ClientCertCredConfig); QuicTest_QUIC_PARAM_CONN_QUIC_VERSION(Registration, ClientConfiguration); - fprintf(stderr, "exited Connection scope\n"); - // QuicTest_QUIC_PARAM_CONN_LOCAL_ADDRESS(Registration, ClientConfiguration); - // QuicTest_QUIC_PARAM_CONN_REMOTE_ADDRESS(Registration, ClientConfiguration); - // QuicTest_QUIC_PARAM_CONN_IDEAL_PROCESSOR(Registration); - // QuicTest_QUIC_PARAM_CONN_SETTINGS(Registration, ClientConfiguration); - // QuicTest_QUIC_PARAM_CONN_STATISTICS(Registration); - // QuicTest_QUIC_PARAM_CONN_STATISTICS_PLAT(Registration); - // QuicTest_QUIC_PARAM_CONN_SHARE_UDP_BINDING(Registration, ClientConfiguration); - // QuicTest_QUIC_PARAM_CONN_LOCAL_BIDI_STREAM_COUNT(Registration); - // QuicTest_QUIC_PARAM_CONN_LOCAL_UNIDI_STREAM_COUNT(Registration); - // QuicTest_QUIC_PARAM_CONN_MAX_STREAM_IDS(Registration); - // QuicTest_QUIC_PARAM_CONN_CLOSE_REASON_PHRASE(Registration); - // QuicTest_QUIC_PARAM_CONN_STREAM_SCHEDULING_SCHEME(Registration); - // QuicTest_QUIC_PARAM_CONN_DATAGRAM_RECEIVE_ENABLED(Registration, ClientConfiguration); - // QuicTest_QUIC_PARAM_CONN_DATAGRAM_SEND_ENABLED(Registration); - // QuicTest_QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION(Registration, ClientConfiguration); - // // QUIC_PARAM_CONN_RESUMPTION_TICKET is covered by TestConnection.cpp and EventTest.cpp - // QuicTest_QUIC_PARAM_CONN_PEER_CERTIFICATE_VALID(Registration); - // QuicTest_QUIC_PARAM_CONN_LOCAL_INTERFACE(Registration, ClientConfiguration); - // QuicTest_QUIC_PARAM_CONN_TLS_SECRETS(Registration, ClientConfiguration); - // // QUIC_PARAM_CONN_VERSION_SETTINGS is covered by QuicTestVersionSettings - // QuicTest_QUIC_PARAM_CONN_CIBIR_ID(Registration, ClientConfiguration); - // QuicTest_QUIC_PARAM_CONN_STATISTICS_V2(Registration); - // QuicTest_QUIC_PARAM_CONN_STATISTICS_V2_PLAT(Registration); - // QuicTest_QUIC_PARAM_CONN_ORIG_DEST_CID(Registration, ClientConfiguration); + QuicTest_QUIC_PARAM_CONN_LOCAL_ADDRESS(Registration, ClientConfiguration); + QuicTest_QUIC_PARAM_CONN_REMOTE_ADDRESS(Registration, ClientConfiguration); + QuicTest_QUIC_PARAM_CONN_IDEAL_PROCESSOR(Registration); + QuicTest_QUIC_PARAM_CONN_SETTINGS(Registration, ClientConfiguration); + QuicTest_QUIC_PARAM_CONN_STATISTICS(Registration); + QuicTest_QUIC_PARAM_CONN_STATISTICS_PLAT(Registration); + QuicTest_QUIC_PARAM_CONN_SHARE_UDP_BINDING(Registration, ClientConfiguration); + QuicTest_QUIC_PARAM_CONN_LOCAL_BIDI_STREAM_COUNT(Registration); + QuicTest_QUIC_PARAM_CONN_LOCAL_UNIDI_STREAM_COUNT(Registration); + QuicTest_QUIC_PARAM_CONN_MAX_STREAM_IDS(Registration); + QuicTest_QUIC_PARAM_CONN_CLOSE_REASON_PHRASE(Registration); + QuicTest_QUIC_PARAM_CONN_STREAM_SCHEDULING_SCHEME(Registration); + QuicTest_QUIC_PARAM_CONN_DATAGRAM_RECEIVE_ENABLED(Registration, ClientConfiguration); + QuicTest_QUIC_PARAM_CONN_DATAGRAM_SEND_ENABLED(Registration); + QuicTest_QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION(Registration, ClientConfiguration); + // QUIC_PARAM_CONN_RESUMPTION_TICKET is covered by TestConnection.cpp and EventTest.cpp + QuicTest_QUIC_PARAM_CONN_PEER_CERTIFICATE_VALID(Registration); + QuicTest_QUIC_PARAM_CONN_LOCAL_INTERFACE(Registration, ClientConfiguration); + QuicTest_QUIC_PARAM_CONN_TLS_SECRETS(Registration, ClientConfiguration); + // QUIC_PARAM_CONN_VERSION_SETTINGS is covered by QuicTestVersionSettings + QuicTest_QUIC_PARAM_CONN_CIBIR_ID(Registration, ClientConfiguration); + QuicTest_QUIC_PARAM_CONN_STATISTICS_V2(Registration); + QuicTest_QUIC_PARAM_CONN_STATISTICS_V2_PLAT(Registration); + QuicTest_QUIC_PARAM_CONN_ORIG_DEST_CID(Registration, ClientConfiguration); } // diff --git a/src/test/lib/HandshakeTest.cpp b/src/test/lib/HandshakeTest.cpp index 5224af3981..68a3e35af4 100644 --- a/src/test/lib/HandshakeTest.cpp +++ b/src/test/lib/HandshakeTest.cpp @@ -3079,7 +3079,7 @@ GetTestInterfaceIndices( { CXPLAT_ADAPTER_ADDRESS* Addresses = nullptr; uint32_t AddressesCount = 0; - if (CxPlatDataPathGetLocalAddresses(nullptr, &Addresses, &AddressesCount) == QUIC_STATUS_NOT_SUPPORTED) { + if (UseDuoNic || CxPlatDataPathGetLocalAddresses(nullptr, &Addresses, &AddressesCount) == QUIC_STATUS_NOT_SUPPORTED) { return false; // Not currently supported by this platform. } @@ -3132,7 +3132,7 @@ QuicTestInterfaceBinding( MsQuicConnection Connection1(Registration); TEST_QUIC_SUCCEEDED(Connection1.GetInitStatus()); TEST_QUIC_SUCCEEDED(Connection1.SetLocalInterface(LoopbackInterfaceIndex)); - TEST_QUIC_SUCCEEDED(Connection1.Start(ClientConfiguration, ServerLocalAddr.GetFamily(), QUIC_TEST_LOOPBACK_FOR_AF(ServerLocalAddr.GetFamily()), ServerLocalAddr.GetPort())); + TEST_QUIC_SUCCEEDED(Connection1.Start(ClientConfiguration, ServerLocalAddr.GetFamily(), QUIC_LOCALHOST_FOR_AF(ServerLocalAddr.GetFamily()), ServerLocalAddr.GetPort())); TEST_TRUE(Connection1.HandshakeCompleteEvent.WaitTimeout(TestWaitTimeout)); TEST_TRUE(Connection1.HandshakeComplete); From 4a5c27de78d3d66d4853da3205c6ecdace533053 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Thu, 17 Aug 2023 23:31:40 -0700 Subject: [PATCH 40/87] fix tests in msquicplatformtest --- src/platform/datapath_raw_win.c | 9 ++- src/platform/datapath_raw_xdp_win.c | 2 +- src/platform/datapath_win.c | 109 ++-------------------------- src/platform/datapath_winuser.c | 45 ++++++++---- 4 files changed, 41 insertions(+), 124 deletions(-) diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index cec6c73d7b..ebf35357b0 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -322,8 +322,10 @@ CxPlatInitRawSocket( if (!Socket->Connected) { Socket->Wildcard = TRUE; } - } else { - CXPLAT_FRE_ASSERT(Socket->Connected); // Assumes only connected sockets fully specify local address + } else if (!Socket->Connected) { + // Assumes only connected sockets fully specify local address + Status = QUIC_STATUS_INVALID_STATE; + goto Error; } } else { if (!Socket->Connected) { @@ -346,8 +348,7 @@ CxPlatInitRawSocket( if (QUIC_FAILED(Status)) { if (Socket != NULL) { CxPlatRundownUninitialize(&Socket->Rundown); - CXPLAT_FREE(Socket, QUIC_POOL_SOCKET); - Socket->RawDatapath = NULL; + CxPlatZeroMemory(Socket, sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); Socket = NULL; } } diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 0ef2b31cdc..313ff71a53 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -1314,7 +1314,7 @@ MANGLE(CxPlatSocketUpdateQeo)( // Raw socket was not created. return QUIC_STATUS_INVALID_STATE; } - XDP_DATAPATH* Xdp = (XDP_DATAPATH*)Socket->Datapath; + XDP_DATAPATH* Xdp = (XDP_DATAPATH*)Socket->RawDatapath; XDP_QUIC_CONNECTION Connections[2]; CXPLAT_FRE_ASSERT(OffloadCount == 2); // TODO - Refactor so upper layer struct matches XDP struct diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 9fc2751a69..72b72c70d6 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -382,43 +382,8 @@ CxPlatSocketCreateUdp( _Out_ CXPLAT_SOCKET** NewSocket ) { -#pragma warning(push) -#pragma warning(suppress:4701) QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - // Raw (Sock (Base (addrs))) - // alloc memory by sizeof RAW - // call CxPlatSocketCreateUdp with NewSocket as Sock - // Call CxPlatInitRawSocket with NewSocket as Raw - - // BOOLEAN IsServerSocket = Config->RemoteAddress == NULL; - // uint16_t SocketCount = IsServerSocket ? Datapath->ProcCount : 1; - // // TODO: check Datapath->RawDataPath and shrink allocation size - // uint32_t RawSocketLength = CxPlatGetRawSocketSize() + SocketCount * sizeof(CXPLAT_SOCKET_PROC); - // CXPLAT_SOCKET_RAW* RawSocket = CXPLAT_ALLOC_PAGED(RawSocketLength, QUIC_POOL_SOCKET); - // CXPLAT_SOCKET* Socket = NULL; - // if (RawSocket == NULL) { - // QuicTraceEvent( - // AllocFailure, - // "Allocation of '%s' failed. (%llu bytes)", - // "CXPLAT_SOCKET", - // RawSocketLength); - // Status = QUIC_STATUS_OUT_OF_MEMORY; - // goto Error; - // } - // fprintf(stderr, "RawSocket allocated %p\n", RawSocket); - - // ZeroMemory(RawSocket, RawSocketLength); - // Socket = CxPlatRawToSocket(RawSocket); - - // QuicTraceEvent( - // DatapathCreated, - // "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", - // Socket, - // CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress), - // CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress)); - - Status = DataPathUserFuncs.CxPlatSocketCreateUdp( Datapath, Config, @@ -438,21 +403,12 @@ CxPlatSocketCreateUdp( if (QUIC_FAILED(Status)) { QuicTraceLogVerbose( RawSockCreateFail, - "[sock] Failed to create raw socket, status:%d", Status); + "[sock] Failed to create raw socket, status:%d", Status); Status = QUIC_STATUS_SUCCESS; } } - // *NewSocket = Socket; - // RawSocket = NULL; - // Socket = NULL; - Error: - // if (Socket) { - // // TODO: break by MultiBindListener test by the RawSocket doesn't have ->Datapath - // CxPlatSocketDelete(Socket); - // } -#pragma warning(pop) return Status; } @@ -466,62 +422,12 @@ CxPlatSocketCreateTcp( _Out_ CXPLAT_SOCKET** NewSocket ) { - return DataPathUserFuncs.CxPlatSocketCreateTcp( Datapath, LocalAddress, RemoteAddress, CallbackContext, NewSocket); - -// QUIC_STATUS Status = QUIC_STATUS_SUCCESS; -// CXPLAT_SOCKET* Socket = NULL; -// uint32_t RawSocketLength = CxPlatGetRawSocketSize() + sizeof(CXPLAT_SOCKET_PROC); -// CXPLAT_SOCKET_RAW* RawSocket = CXPLAT_ALLOC_PAGED(RawSocketLength, QUIC_POOL_SOCKET); -// if (RawSocket == NULL) { -// QuicTraceEvent( -// AllocFailure, -// "Allocation of '%s' failed. (%llu bytes)", -// "CXPLAT_SOCKET", -// RawSocketLength); -// Status = QUIC_STATUS_OUT_OF_MEMORY; -// goto Error; -// } -// fprintf(stderr, "Tcp Socket allocated :%p\n", Socket); - -// QuicTraceEvent( -// DatapathCreated, -// "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", -// Socket, -// CASTED_CLOG_BYTEARRAY(LocalAddress ? sizeof(*LocalAddress) : 0, LocalAddress), -// CASTED_CLOG_BYTEARRAY(RemoteAddress ? sizeof(*RemoteAddress) : 0, RemoteAddress)); - -// ZeroMemory(RawSocket, RawSocketLength); -// CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); - -// QUIC_STATUS Status = DataPathUserFuncs.CxPlatSocketCreateTcp( -// Datapath, -// LocalAddress, -// RemoteAddress, -// CallbackContext, -// Socket); -// if (QUIC_FAILED(Status)) { -// QuicTraceLogVerbose( -// SockCreateFail, -// "[sock] Failed to create Tcp socket, status:%d", Status); -// goto Error; -// } - -// *NewSocket = Socket; -// RawSocket = NULL; -// Socket = NULL; - -// Error: -// if (RawSocket) { -// CxPlatSocketDelete(Socket); -// } - -// return Status; } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -543,18 +449,13 @@ CxPlatSocketCreateTcpListener( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatSocketDelete( - _In_ CXPLAT_SOCKET* Socket // TODO: consider which type to be used + _In_ CXPLAT_SOCKET* Socket ) { - // TODO: bubble up common logic if (Socket->Datapath && Socket->Datapath->RawDataPath) { CxPlatRawSocketDelete(CxPlatSocketToRaw(Socket)); } - DataPathUserFuncs.CxPlatSocketDelete(Socket); - - // TODO: TCP socket cannot be free as RawSocket - // CXPLAT_FREE(CxPlatSocketToRaw(Socket), QUIC_POOL_SOCKET); } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -566,9 +467,9 @@ CxPlatSocketUpdateQeo( _In_ uint32_t OffloadCount ) { - if (Socket->Datapath && Socket->Datapath->RawDataPath) { - CXPLAT_SOCKET_RAW* RawSocket = CxPlatSocketToRaw(Socket); - return XDP_CxPlatSocketUpdateQeo(RawSocket, Offloads, OffloadCount); + if (Socket->Datapath && Socket->Datapath->RawDataPath && + !IS_LOOPBACK(Offloads[0].Address)) { + return XDP_CxPlatSocketUpdateQeo(CxPlatSocketToRaw(Socket), Offloads, OffloadCount); } return QUIC_STATUS_NOT_SUPPORTED; } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 682afd0964..5861bbc4c6 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -792,7 +792,7 @@ CxPlatDataPathQuerySockoptSupport( } } - // Datapath->Features |= CXPLAT_DATAPATH_FEATURE_TCP; + Datapath->Features |= CXPLAT_DATAPATH_FEATURE_TCP; Error: @@ -1991,17 +1991,18 @@ CxPlatSocketCreateTcpInternal( CXPLAT_DBG_ASSERT(Datapath->TcpHandlers.Receive != NULL); CXPLAT_SOCKET_PROC* SocketProc = NULL; - uint32_t SocketLength = sizeof(CXPLAT_SOCKET) + sizeof(CXPLAT_SOCKET_PROC); - CXPLAT_SOCKET* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); - if (Socket == NULL) { + uint32_t RawSocketLength = CxPlatGetRawSocketSize() + sizeof(CXPLAT_SOCKET_PROC); + CXPLAT_SOCKET_RAW* RawSocket = CXPLAT_ALLOC_PAGED(RawSocketLength, QUIC_POOL_SOCKET); + if (RawSocket == NULL) { QuicTraceEvent( AllocFailure, "Allocation of '%s' failed. (%llu bytes)", "CXPLAT_SOCKET", - SocketLength); + RawSocketLength); Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; } + CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); QuicTraceEvent( DatapathCreated, @@ -2010,7 +2011,7 @@ CxPlatSocketCreateTcpInternal( CASTED_CLOG_BYTEARRAY(LocalAddress ? sizeof(*LocalAddress) : 0, LocalAddress), CASTED_CLOG_BYTEARRAY(RemoteAddress ? sizeof(*RemoteAddress) : 0, RemoteAddress)); - ZeroMemory(Socket, SocketLength); + ZeroMemory(RawSocket, RawSocketLength); Socket->Datapath = Datapath; Socket->ClientContext = RecvCallbackContext; Socket->HasFixedRemoteAddress = TRUE; @@ -2215,13 +2216,14 @@ CxPlatSocketCreateTcpInternal( *NewSocket = (CXPLAT_SOCKET*)Socket; Socket = NULL; + RawSocket = NULL; Status = QUIC_STATUS_SUCCESS; Error: - if (Socket != NULL) { - MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET*)Socket); + if (RawSocket != NULL) { + MANGLE(CxPlatSocketDelete)(CxPlatRawToSocket(RawSocket)); } return Status; @@ -2263,17 +2265,29 @@ MANGLE(CxPlatSocketCreateTcpListener)( CXPLAT_DBG_ASSERT(Datapath->TcpHandlers.Receive != NULL); CXPLAT_SOCKET_PROC* SocketProc = NULL; - uint32_t SocketLength = sizeof(CXPLAT_SOCKET) + sizeof(CXPLAT_SOCKET_PROC); - CXPLAT_SOCKET* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); - if (Socket == NULL) { + // uint32_t SocketLength = sizeof(CXPLAT_SOCKET) + sizeof(CXPLAT_SOCKET_PROC); + // CXPLAT_SOCKET* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); + // if (Socket == NULL) { + // QuicTraceEvent( + // AllocFailure, + // "Allocation of '%s' failed. (%llu bytes)", + // "CXPLAT_SOCKET", + // SocketLength); + // Status = QUIC_STATUS_OUT_OF_MEMORY; + // goto Error; + // } + uint32_t RawSocketLength = CxPlatGetRawSocketSize() + sizeof(CXPLAT_SOCKET_PROC); + CXPLAT_SOCKET_RAW* RawSocket = CXPLAT_ALLOC_PAGED(RawSocketLength, QUIC_POOL_SOCKET); + if (RawSocket == NULL) { QuicTraceEvent( AllocFailure, "Allocation of '%s' failed. (%llu bytes)", "CXPLAT_SOCKET", - SocketLength); + RawSocketLength); Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; } + CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); QuicTraceEvent( DatapathCreated, @@ -2282,7 +2296,7 @@ MANGLE(CxPlatSocketCreateTcpListener)( CASTED_CLOG_BYTEARRAY(LocalAddress ? sizeof(*LocalAddress) : 0, LocalAddress), CASTED_CLOG_BYTEARRAY(0, NULL)); - ZeroMemory(Socket, SocketLength); + ZeroMemory(RawSocket, RawSocketLength); Socket->Datapath = Datapath; Socket->ClientContext = RecvCallbackContext; Socket->HasFixedRemoteAddress = FALSE; @@ -2456,12 +2470,13 @@ MANGLE(CxPlatSocketCreateTcpListener)( *NewSocket = (CXPLAT_SOCKET*)Socket; Socket = NULL; + RawSocket = NULL; Status = QUIC_STATUS_SUCCESS; Error: - if (Socket != NULL) { - MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET*)Socket); + if (RawSocket != NULL) { + MANGLE(CxPlatSocketDelete)(CxPlatRawToSocket(RawSocket)); } return Status; From 2a202bc5f7c6652701a2cdbe5c2cc49bb29d4c9f Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Fri, 18 Aug 2023 00:08:20 -0700 Subject: [PATCH 41/87] fix tcp with duonic --- src/platform/datapath_win.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 72b72c70d6..49dba7598b 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -535,7 +535,9 @@ CxPlatSendDataAlloc( ) { CXPLAT_SEND_DATA* SendData = NULL; - if (Socket->Datapath && Socket->Datapath->RawDataPath && + // TODO: fallback? + if (Socket->Type == 0 && // CXPLAT_SOCKET_UDP + Socket->Datapath && Socket->Datapath->RawDataPath && !IS_LOOPBACK(Config->Route->RemoteAddress)) { SendData = XDP_CxPlatSendDataAlloc(CxPlatSocketToRaw(Socket), Config); if (SendData) { From 72599845eefd60ca1022876f6e3112d48f7439c3 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Fri, 18 Aug 2023 19:38:17 -0700 Subject: [PATCH 42/87] update clog --- src/generated/linux/datapath_raw_win.c.clog.h | 64 ---------------- .../linux/datapath_raw_win.c.clog.h.lttng.h | 73 ------------------- src/generated/linux/datapath_winuser.c.clog.h | 36 ++------- .../linux/datapath_winuser.c.clog.h.lttng.h | 35 ++------- src/platform/datapath_winuser.c | 11 --- 5 files changed, 12 insertions(+), 207 deletions(-) diff --git a/src/generated/linux/datapath_raw_win.c.clog.h b/src/generated/linux/datapath_raw_win.c.clog.h index a771cadfda..8c8d0dc506 100644 --- a/src/generated/linux/datapath_raw_win.c.clog.h +++ b/src/generated/linux/datapath_raw_win.c.clog.h @@ -14,10 +14,6 @@ #include "datapath_raw_win.c.clog.h.lttng.h" #endif #include -#ifndef _clog_MACRO_QuicTraceLogError -#define _clog_MACRO_QuicTraceLogError 1 -#define QuicTraceLogError(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) -#endif #ifndef _clog_MACRO_QuicTraceEvent #define _clog_MACRO_QuicTraceEvent 1 #define QuicTraceEvent(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) @@ -25,26 +21,6 @@ #ifdef __cplusplus extern "C" { #endif -/*---------------------------------------------------------- -// Decoder Ring for DatapathResolveHostNameFailed -// [%p] Couldn't resolve hostname '%s' to an IP address -// QuicTraceLogError( - DatapathResolveHostNameFailed, - "[%p] Couldn't resolve hostname '%s' to an IP address", - Datapath, - HostName); -// arg2 = arg2 = Datapath = arg2 -// arg3 = arg3 = HostName = arg3 -----------------------------------------------------------*/ -#ifndef _clog_4_ARGS_TRACE_DatapathResolveHostNameFailed -#define _clog_4_ARGS_TRACE_DatapathResolveHostNameFailed(uniqueId, encoded_arg_string, arg2, arg3)\ -tracepoint(CLOG_DATAPATH_RAW_WIN_C, DatapathResolveHostNameFailed , arg2, arg3);\ - -#endif - - - - /*---------------------------------------------------------- // Decoder Ring for AllocFailure // Allocation of '%s' failed. (%llu bytes) @@ -85,46 +61,6 @@ tracepoint(CLOG_DATAPATH_RAW_WIN_C, LibraryErrorStatus , arg2, arg3);\ -/*---------------------------------------------------------- -// Decoder Ring for LibraryError -// [ lib] ERROR, %s. -// QuicTraceEvent( - LibraryError, - "[ lib] ERROR, %s.", - "Resolving hostname to IP"); -// arg2 = arg2 = "Resolving hostname to IP" = arg2 -----------------------------------------------------------*/ -#ifndef _clog_3_ARGS_TRACE_LibraryError -#define _clog_3_ARGS_TRACE_LibraryError(uniqueId, encoded_arg_string, arg2)\ -tracepoint(CLOG_DATAPATH_RAW_WIN_C, LibraryError , arg2);\ - -#endif - - - - -/*---------------------------------------------------------- -// Decoder Ring for DatapathCreated -// [data][%p] Created, local=%!ADDR!, remote=%!ADDR! -// QuicTraceEvent( - DatapathCreated, - "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", - *NewSocket, - CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress), - CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress)); -// arg2 = arg2 = *NewSocket = arg2 -// arg3 = arg3 = CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress) = arg3 -// arg4 = arg4 = CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress) = arg4 -----------------------------------------------------------*/ -#ifndef _clog_7_ARGS_TRACE_DatapathCreated -#define _clog_7_ARGS_TRACE_DatapathCreated(uniqueId, encoded_arg_string, arg2, arg3, arg3_len, arg4, arg4_len)\ -tracepoint(CLOG_DATAPATH_RAW_WIN_C, DatapathCreated , arg2, arg3_len, arg3, arg4_len, arg4);\ - -#endif - - - - /*---------------------------------------------------------- // Decoder Ring for DatapathRecv // [data][%p] Recv %u bytes (segment=%hu) Src=%!ADDR! Dst=%!ADDR! diff --git a/src/generated/linux/datapath_raw_win.c.clog.h.lttng.h b/src/generated/linux/datapath_raw_win.c.clog.h.lttng.h index 652b9d6278..5f0e811562 100644 --- a/src/generated/linux/datapath_raw_win.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_raw_win.c.clog.h.lttng.h @@ -1,29 +1,6 @@ -/*---------------------------------------------------------- -// Decoder Ring for DatapathResolveHostNameFailed -// [%p] Couldn't resolve hostname '%s' to an IP address -// QuicTraceLogError( - DatapathResolveHostNameFailed, - "[%p] Couldn't resolve hostname '%s' to an IP address", - Datapath, - HostName); -// arg2 = arg2 = Datapath = arg2 -// arg3 = arg3 = HostName = arg3 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_WIN_C, DatapathResolveHostNameFailed, - TP_ARGS( - const void *, arg2, - const char *, arg3), - TP_FIELDS( - ctf_integer_hex(uint64_t, arg2, arg2) - ctf_string(arg3, arg3) - ) -) - - - /*---------------------------------------------------------- // Decoder Ring for AllocFailure // Allocation of '%s' failed. (%llu bytes) @@ -70,56 +47,6 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_WIN_C, LibraryErrorStatus, -/*---------------------------------------------------------- -// Decoder Ring for LibraryError -// [ lib] ERROR, %s. -// QuicTraceEvent( - LibraryError, - "[ lib] ERROR, %s.", - "Resolving hostname to IP"); -// arg2 = arg2 = "Resolving hostname to IP" = arg2 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_WIN_C, LibraryError, - TP_ARGS( - const char *, arg2), - TP_FIELDS( - ctf_string(arg2, arg2) - ) -) - - - -/*---------------------------------------------------------- -// Decoder Ring for DatapathCreated -// [data][%p] Created, local=%!ADDR!, remote=%!ADDR! -// QuicTraceEvent( - DatapathCreated, - "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", - *NewSocket, - CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress), - CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress)); -// arg2 = arg2 = *NewSocket = arg2 -// arg3 = arg3 = CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress) = arg3 -// arg4 = arg4 = CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress) = arg4 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_WIN_C, DatapathCreated, - TP_ARGS( - const void *, arg2, - unsigned int, arg3_len, - const void *, arg3, - unsigned int, arg4_len, - const void *, arg4), - TP_FIELDS( - ctf_integer_hex(uint64_t, arg2, arg2) - ctf_integer(unsigned int, arg3_len, arg3_len) - ctf_sequence(char, arg3, arg3, unsigned int, arg3_len) - ctf_integer(unsigned int, arg4_len, arg4_len) - ctf_sequence(char, arg4, arg4, unsigned int, arg4_len) - ) -) - - - /*---------------------------------------------------------- // Decoder Ring for DatapathRecv // [data][%p] Recv %u bytes (segment=%hu) Src=%!ADDR! Dst=%!ADDR! diff --git a/src/generated/linux/datapath_winuser.c.clog.h b/src/generated/linux/datapath_winuser.c.clog.h index d77ffd43af..5d0014256d 100644 --- a/src/generated/linux/datapath_winuser.c.clog.h +++ b/src/generated/linux/datapath_winuser.c.clog.h @@ -22,10 +22,6 @@ #define _clog_MACRO_QuicTraceLogVerbose 1 #define QuicTraceLogVerbose(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) #endif -#ifndef _clog_MACRO_QuicTraceLogError -#define _clog_MACRO_QuicTraceLogError 1 -#define QuicTraceLogError(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) -#endif #ifndef _clog_MACRO_QuicTraceEvent #define _clog_MACRO_QuicTraceEvent 1 #define QuicTraceEvent(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) @@ -255,26 +251,6 @@ tracepoint(CLOG_DATAPATH_WINUSER_C, DatapathTooLarge , arg2, arg3_len, arg3);\ -/*---------------------------------------------------------- -// Decoder Ring for DatapathResolveHostNameFailed -// [%p] Couldn't resolve hostname '%s' to an IP address -// QuicTraceLogError( - DatapathResolveHostNameFailed, - "[%p] Couldn't resolve hostname '%s' to an IP address", - Datapath, - HostName); -// arg2 = arg2 = Datapath = arg2 -// arg3 = arg3 = HostName = arg3 -----------------------------------------------------------*/ -#ifndef _clog_4_ARGS_TRACE_DatapathResolveHostNameFailed -#define _clog_4_ARGS_TRACE_DatapathResolveHostNameFailed(uniqueId, encoded_arg_string, arg2, arg3)\ -tracepoint(CLOG_DATAPATH_WINUSER_C, DatapathResolveHostNameFailed , arg2, arg3);\ - -#endif - - - - /*---------------------------------------------------------- // Decoder Ring for LibraryErrorStatus // [ lib] ERROR, %u, %s. @@ -299,12 +275,12 @@ tracepoint(CLOG_DATAPATH_WINUSER_C, LibraryErrorStatus , arg2, arg3);\ // Decoder Ring for AllocFailure // Allocation of '%s' failed. (%llu bytes) // QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_DATAPATH", - DatapathLength); -// arg2 = arg2 = "CXPLAT_DATAPATH" = arg2 -// arg3 = arg3 = DatapathLength = arg3 + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "PIP_ADAPTER_ADDRESSES", + AdapterAddressesSize); +// arg2 = arg2 = "PIP_ADAPTER_ADDRESSES" = arg2 +// arg3 = arg3 = AdapterAddressesSize = arg3 ----------------------------------------------------------*/ #ifndef _clog_4_ARGS_TRACE_AllocFailure #define _clog_4_ARGS_TRACE_AllocFailure(uniqueId, encoded_arg_string, arg2, arg3)\ diff --git a/src/generated/linux/datapath_winuser.c.clog.h.lttng.h b/src/generated/linux/datapath_winuser.c.clog.h.lttng.h index 9822316967..248eb7c4d7 100644 --- a/src/generated/linux/datapath_winuser.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_winuser.c.clog.h.lttng.h @@ -245,29 +245,6 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_WINUSER_C, DatapathTooLarge, -/*---------------------------------------------------------- -// Decoder Ring for DatapathResolveHostNameFailed -// [%p] Couldn't resolve hostname '%s' to an IP address -// QuicTraceLogError( - DatapathResolveHostNameFailed, - "[%p] Couldn't resolve hostname '%s' to an IP address", - Datapath, - HostName); -// arg2 = arg2 = Datapath = arg2 -// arg3 = arg3 = HostName = arg3 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_WINUSER_C, DatapathResolveHostNameFailed, - TP_ARGS( - const void *, arg2, - const char *, arg3), - TP_FIELDS( - ctf_integer_hex(uint64_t, arg2, arg2) - ctf_string(arg3, arg3) - ) -) - - - /*---------------------------------------------------------- // Decoder Ring for LibraryErrorStatus // [ lib] ERROR, %u, %s. @@ -295,12 +272,12 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_WINUSER_C, LibraryErrorStatus, // Decoder Ring for AllocFailure // Allocation of '%s' failed. (%llu bytes) // QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_DATAPATH", - DatapathLength); -// arg2 = arg2 = "CXPLAT_DATAPATH" = arg2 -// arg3 = arg3 = DatapathLength = arg3 + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "PIP_ADAPTER_ADDRESSES", + AdapterAddressesSize); +// arg2 = arg2 = "PIP_ADAPTER_ADDRESSES" = arg2 +// arg3 = arg3 = AdapterAddressesSize = arg3 ----------------------------------------------------------*/ TRACEPOINT_EVENT(CLOG_DATAPATH_WINUSER_C, AllocFailure, TP_ARGS( diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 3880ebc578..4e79f11919 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -2572,17 +2572,6 @@ MANGLE(CxPlatSocketCreateTcpListener)( CXPLAT_DBG_ASSERT(Datapath->TcpHandlers.Receive != NULL); CXPLAT_SOCKET_PROC* SocketProc = NULL; - // uint32_t SocketLength = sizeof(CXPLAT_SOCKET) + sizeof(CXPLAT_SOCKET_PROC); - // CXPLAT_SOCKET* Socket = CXPLAT_ALLOC_PAGED(SocketLength, QUIC_POOL_SOCKET); - // if (Socket == NULL) { - // QuicTraceEvent( - // AllocFailure, - // "Allocation of '%s' failed. (%llu bytes)", - // "CXPLAT_SOCKET", - // SocketLength); - // Status = QUIC_STATUS_OUT_OF_MEMORY; - // goto Error; - // } uint32_t RawSocketLength = CxPlatGetRawSocketSize() + sizeof(CXPLAT_SOCKET_PROC); CXPLAT_SOCKET_RAW* RawSocket = CXPLAT_ALLOC_PAGED(RawSocketLength, QUIC_POOL_SOCKET); if (RawSocket == NULL) { From 86a0e664c003c3f6b76582eeef6db1d8aad4d888 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Fri, 18 Aug 2023 19:39:07 -0700 Subject: [PATCH 43/87] use xdp v1 --- src/platform/datapath_raw_xdp_win.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 1af7507b29..de842db826 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -1019,10 +1019,8 @@ CxPlatDpRawInitialize( { XDP_DATAPATH* Xdp = (XDP_DATAPATH*)Datapath; QUIC_STATUS Status; - CxPlatListInitializeHead(&Xdp->Interfaces); - if (QUIC_FAILED(XdpLoadApi(XDP_VERSION_PRERELEASE, &Xdp->XdpApiLoadContext, &Xdp->XdpApi))) { - // if (QUIC_FAILED(XdpLoadApi(XDP_API_VERSION_1, &Xdp->XdpApiLoadContext, &Xdp->XdpApi))) { + if (QUIC_FAILED(XdpLoadApi(XDP_API_VERSION_1, &Xdp->XdpApiLoadContext, &Xdp->XdpApi))) { Status = QUIC_STATUS_NOT_SUPPORTED; goto Error; } From fc78d24698e70720e6cbaa79cc916d0468707cb5 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 23 Aug 2023 20:52:05 -0700 Subject: [PATCH 44/87] WIP cleanup --- src/core/binding.h | 10 +- src/inc/quic_datapath.h | 46 +--- src/platform/datapath_raw.h | 10 + src/platform/datapath_raw_socket.c | 20 +- src/platform/datapath_raw_xdp.h | 16 +- src/platform/datapath_raw_xdp_win.c | 2 +- src/platform/datapath_win.c | 74 ++---- src/platform/datapath_winuser.c | 5 +- src/platform/platform_internal.h | 376 ++++++++++++++++------------ 9 files changed, 289 insertions(+), 270 deletions(-) diff --git a/src/core/binding.h b/src/core/binding.h index 0bd8157f47..87795e2fdc 100644 --- a/src/core/binding.h +++ b/src/core/binding.h @@ -161,12 +161,12 @@ typedef struct QUIC_RX_PACKET { // BOOLEAN HasNonProbingFrame : 1; - // - // - // - uint8_t BufferFrom : 2; + // // + // // + // // + // uint8_t BufferFrom : 2; - uint8_t Reserved : 2; + // uint8_t Reserved : 2; }; }; diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 8dff28163b..2fdf5696ac 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -127,7 +127,6 @@ PacketSizeFromUdpPayloadSize( // typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; -typedef struct CXPLAT_DATAPATH_BASE CXPLAT_DATAPATH_BASE; typedef struct CXPLAT_DATAPATH_RAW CXPLAT_DATAPATH_RAW; typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; @@ -136,20 +135,6 @@ typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; // typedef struct CXPLAT_SOCKET_RAW CXPLAT_SOCKET_RAW; typedef struct CXPLAT_SOCKET CXPLAT_SOCKET; -typedef struct CXPLAT_SOCKET_BASE CXPLAT_SOCKET_BASE; - - -typedef struct CXPLAT_SOCKET_BASE { - // - // The local address and port. - // - QUIC_ADDR LocalAddress; - - // - // The remote address and port. - // - QUIC_ADDR RemoteAddress; -} CXPLAT_SOCKET_BASE; typedef struct CXPLAT_UDP_CONFIG CXPLAT_UDP_CONFIG; @@ -170,15 +155,16 @@ typedef enum CXPLAT_BUFFER_FROM { // Structure that maintains the 'per send' context. // typedef struct CXPLAT_SEND_DATA_INTERNAL CXPLAT_SEND_DATA_INTERNAL; +typedef struct CXPLAT_SEND_DATA CXPLAT_SEND_DATA; -typedef struct CXPLAT_SEND_DATA { - uint16_t BufferFrom : 2; +// typedef struct CXPLAT_SEND_DATA { +// uint16_t BufferFrom : 2; - // - // The type of ECN markings needed for send. - // - uint8_t ECN; // CXPLAT_ECN_TYPE -} CXPLAT_SEND_DATA; +// // +// // The type of ECN markings needed for send. +// // +// uint8_t ECN; // CXPLAT_ECN_TYPE +// } CXPLAT_SEND_DATA; // // Contains a pointer and length. @@ -831,8 +817,8 @@ CxPlatUpdateRoute( ); typedef struct CXPLAT_DATAPATH_FUNCTIONS { - CXPLAT_RECV_DATA* (*CxPlatDataPathRecvPacketToRecvData)(const CXPLAT_RECV_PACKET* const Context); - CXPLAT_RECV_PACKET* (*CxPlatDataPathRecvDataToRecvPacket)(const CXPLAT_RECV_DATA* const Datagram); + // CXPLAT_RECV_DATA* (*CxPlatDataPathRecvPacketToRecvData)(const CXPLAT_RECV_PACKET* const Context); + // CXPLAT_RECV_PACKET* (*CxPlatDataPathRecvDataToRecvPacket)(const CXPLAT_RECV_DATA* const Datagram); QUIC_STATUS (*CxPlatDataPathInitialize)(_In_ uint32_t ClientRecvContextLength, _In_opt_ QUIC_EXECUTION_CONFIG* Config, _Out_ CXPLAT_DATAPATH* DataPath); @@ -894,18 +880,6 @@ typedef struct CXPLAT_DATAPATH_FUNCTIONS { extern const struct CXPLAT_DATAPATH_FUNCTIONS DataPathUserFuncs; extern const struct CXPLAT_DATAPATH_FUNCTIONS DataPathXdpFuncs; // RawFuncs? -typedef struct CXPLAT_DATAPATH_BASE { - // - // The UDP callback function pointers. - // - CXPLAT_UDP_DATAPATH_CALLBACKS UdpHandlers; - - // - // The TCP callback function pointers. - // - CXPLAT_TCP_DATAPATH_CALLBACKS TcpHandlers; -} CXPLAT_DATAPATH_BASE; - _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatInitRawSocket( diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index 405528abee..cf1c5a7819 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -270,6 +270,16 @@ typedef struct CXPLAT_SOCKET_RAW { CXPLAT_SOCKET; } CXPLAT_SOCKET_RAW; +inline CXPLAT_SOCKET* +CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket) { + return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); +} +// TODO: rename +inline CXPLAT_SOCKET_RAW* +CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { + return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); +} + BOOLEAN CxPlatSockPoolInitialize( _Inout_ CXPLAT_SOCKET_POOL* Pool diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index 0b79a58556..97dfaadc4c 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -23,16 +23,16 @@ CxPlatGetRawSocketSize () { return sizeof(CXPLAT_SOCKET_RAW); } -// TODO: rename -CXPLAT_SOCKET* -CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket) { - return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); -} -// TODO: rename -CXPLAT_SOCKET_RAW* -CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { - return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); -} +// // TODO: rename +// CXPLAT_SOCKET* +// CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket) { +// return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); +// } +// // TODO: rename +// CXPLAT_SOCKET_RAW* +// CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { +// return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); +// } CXPLAT_SOCKET_RAW* CxPlatGetSocket( diff --git a/src/platform/datapath_raw_xdp.h b/src/platform/datapath_raw_xdp.h index b0529dab61..56ed7db3a3 100644 --- a/src/platform/datapath_raw_xdp.h +++ b/src/platform/datapath_raw_xdp.h @@ -25,14 +25,14 @@ typedef struct XDP_PARTITION XDP_PARTITION; typedef struct XDP_DATAPATH XDP_DATAPATH; typedef struct XDP_QUEUE XDP_QUEUE; -// -// Type of IO. -// -typedef enum DATAPATH_XDP_IO_TYPE { - DATAPATH_XDP_IO_SIGNATURE = 'XDPD', - DATAPATH_XDP_IO_RECV = DATAPATH_XDP_IO_SIGNATURE + 1, - DATAPATH_XDP_IO_SEND = DATAPATH_XDP_IO_SIGNATURE + 2 -} DATAPATH_XDP_IO_TYPE; +// // +// // Type of IO. +// // +// typedef enum DATAPATH_XDP_IO_TYPE { +// DATAPATH_XDP_IO_SIGNATURE = 'XDPD', +// DATAPATH_XDP_IO_RECV = DATAPATH_XDP_IO_SIGNATURE + 1, +// DATAPATH_XDP_IO_SEND = DATAPATH_XDP_IO_SIGNATURE + 2 +// } DATAPATH_XDP_IO_TYPE; // // IO header for SQE->CQE based completions. diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index ac6be5439f..5514bbde15 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -1584,7 +1584,7 @@ CxPlatXdpRx( if (Packet->RecvData.Buffer) { Packet->RecvData.Allocated = TRUE; - Packet->BufferFrom = CXPLAT_BUFFER_FROM_XDP; + Packet->RecvData.BufferFrom = CXPLAT_BUFFER_FROM_XDP; Buffers[PacketCount++] = &Packet->RecvData; } else { CxPlatListPushEntry(&Queue->PartitionRxPool, (CXPLAT_SLIST_ENTRY*)Packet); diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index f7db114dfe..71f812a3d6 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -20,59 +20,23 @@ #pragma warning(disable:4100) // unreferenced #pragma warning(disable:6101) // uninitialized -#define IS_LOOPBACK(RemoteAddress) ((RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET && \ - RemoteAddress.Ipv4.sin_addr.S_un.S_addr == htonl(INADDR_LOOPBACK)) || \ - (RemoteAddress.si_family == QUIC_ADDRESS_FAMILY_INET6 && \ - IN6_IS_ADDR_LOOPBACK(&RemoteAddress.Ipv6.sin6_addr))) - -CXPLAT_RECV_DATA* -CxPlatDataPathRecvPacketToRecvData( - _In_ const CXPLAT_RECV_PACKET* const Context, - _In_ uint16_t BufferFrom - ) -{ - if (BufferFrom == CXPLAT_BUFFER_FROM_USER) { - return DataPathUserFuncs.CxPlatDataPathRecvPacketToRecvData(Context); - } else if (BufferFrom == CXPLAT_BUFFER_FROM_XDP) { - return XDP_CxPlatDataPathRecvPacketToRecvData(Context); - } else { - CXPLAT_DBG_ASSERT(FALSE); - } - return NULL; -} - -CXPLAT_RECV_PACKET* -CxPlatDataPathRecvDataToRecvPacket( - _In_ const CXPLAT_RECV_DATA* const Datagram - ) -{ - if (Datagram->BufferFrom == CXPLAT_BUFFER_FROM_USER) { - return DataPathUserFuncs.CxPlatDataPathRecvDataToRecvPacket(Datagram); - } else if (Datagram->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { - return XDP_CxPlatDataPathRecvDataToRecvPacket(Datagram); - } else { - CXPLAT_DBG_ASSERT(FALSE); - } - return NULL; -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -BOOLEAN -CxPlatRawDataPathAvailable( - _In_ CXPLAT_DATAPATH* Datapath - ) -{ - return Datapath->RawDataPath != NULL; -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -BOOLEAN -CxPlatRawSocketAvailable( - _In_ CXPLAT_SOCKET* Socket - ) -{ - return Socket->Datapath && CxPlatRawDataPathAvailable(Socket->Datapath); -} +#define IS_LOOPBACK(Address) ((Address.si_family == QUIC_ADDRESS_FAMILY_INET && \ + Address.Ipv4.sin_addr.S_un.S_addr == htonl(INADDR_LOOPBACK)) || \ + (Address.si_family == QUIC_ADDRESS_FAMILY_INET6 && \ + IN6_IS_ADDR_LOOPBACK(&Address.Ipv6.sin6_addr))) + +#define RawDataPathAvailable(Datapath) ((Datapath)->RawDataPath != NULL) +#define RawSocketAvailable(Socket) ((Socket)->Datapath && RawDataPathAvailable((Socket)->Datapath)) + +// CXPLAT_SOCKET* +// CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket) { +// return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); +// } +// // TODO: rename +// CXPLAT_SOCKET_RAW* +// CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { +// return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); +// } _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS @@ -645,9 +609,11 @@ CxPlatDataPathProcessCqe( case CXPLAT_CQE_TYPE_SOCKET_IO: { DATAPATH_IO_SQE* Sqe = CONTAINING_RECORD(CxPlatCqeUserData(Cqe), DATAPATH_IO_SQE, DatapathSqe); - if (Sqe->IoType == 1480872005 || Sqe->IoType == 1480872006) { + if (Sqe->IoType == DATAPATH_XDP_IO_RECV || Sqe->IoType == DATAPATH_XDP_IO_SEND) { + fprintf(stderr, "Recv XDP %d\n", Sqe->IoType); XDP_CxPlatDataPathProcessCqe(Cqe); } else { + fprintf(stderr, "Recv Sock %d\n", Sqe->IoType); DataPathUserFuncs.CxPlatDataPathProcessCqe(Cqe); } break; diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 0999f4304a..e5a1da9c2e 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -3612,6 +3612,7 @@ CxPlatDataPathUdpRecvComplete( CXPLAT_RECV_DATA* RecvDataChain = NULL; CXPLAT_RECV_DATA** DatagramChainTail = &RecvDataChain; + CXPLAT_DATAPATH* Datapath = SocketProc->Parent->Datapath; CXPLAT_RECV_DATA* Datagram; PUCHAR RecvPayload = ((PUCHAR)IoBlock) + Datapath->RecvPayloadOffset; @@ -4988,8 +4989,8 @@ MANGLE(CxPlatUpdateRoute)( } const struct CXPLAT_DATAPATH_FUNCTIONS DataPathUserFuncs = { - MANGLE(CxPlatDataPathRecvPacketToRecvData), - MANGLE(CxPlatDataPathRecvDataToRecvPacket), + // MANGLE(CxPlatDataPathRecvPacketToRecvData), + // MANGLE(CxPlatDataPathRecvDataToRecvPacket), MANGLE(CxPlatDataPathInitialize), MANGLE(CxPlatDataPathUninitialize), MANGLE(CxPlatDataPathUpdateConfig), diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 7e34556db0..37ab42cfed 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -39,6 +39,62 @@ #endif + +#ifdef _KERNEL_MODE +// #include "datapath_winkernel.h" +#elif _WIN32 +// #include "datapath_winuser.h" +#elif CX_PLATFORM_LINUX +// #include "datapath_posix.h" +#elif CX_PLATFORM_DARWIN +// #include "datapath_posix.h" +#else +#error "Unsupported Platform" +#endif + +// TODO: create header files for each datapath + +typedef struct DATAPATH_SQE { + uint32_t CqeType; +#ifdef CXPLAT_SQE + CXPLAT_SQE Sqe; +#endif +} DATAPATH_SQE; + +// not needed? +typedef struct CXPLAT_DATAPATH_COMMON { + // + // The UDP callback function pointers. + // + CXPLAT_UDP_DATAPATH_CALLBACKS UdpHandlers; + + // + // The TCP callback function pointers. + // + CXPLAT_TCP_DATAPATH_CALLBACKS TcpHandlers; +} CXPLAT_DATAPATH_COMMON; + +typedef struct CXPLAT_SOCKET_COMMON { + // + // The local address and port. + // + QUIC_ADDR LocalAddress; + + // + // The remote address and port. + // + QUIC_ADDR RemoteAddress; +} CXPLAT_SOCKET_COMMON; + +typedef struct CXPLAT_SEND_DATA { + uint16_t BufferFrom : 2; + + // + // The type of ECN markings needed for send. + // + uint8_t ECN; // CXPLAT_ECN_TYPE +} CXPLAT_SEND_DATA; + #ifdef _KERNEL_MODE #define CXPLAT_BASE_REG_PATH L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\MsQuic\\Parameters\\" @@ -104,156 +160,6 @@ typedef struct CX_PLATFORM { } CX_PLATFORM; -#elif defined(CX_PLATFORM_LINUX) || defined(CX_PLATFORM_DARWIN) - -typedef struct CX_PLATFORM { - - void* Reserved; // Nothing right now. - -#ifdef DEBUG - // - // 1/Denominator of allocations to fail. - // Negative is Nth allocation to fail. - // - int32_t AllocFailDenominator; - - // - // Count of allocations. - // - long AllocCounter; -#endif - -} CX_PLATFORM; - -#else - -#error "Unsupported Platform" - -#endif - -#pragma warning(disable:4204) // nonstandard extension used: non-constant aggregate initializer -#pragma warning(disable:4200) // nonstandard extension used: zero-sized array in struct/union - -// -// Global Platform variables/state. -// -extern CX_PLATFORM CxPlatform; - -// -// PCP Receive Callback -// -CXPLAT_DATAPATH_RECEIVE_CALLBACK CxPlatPcpRecvCallback; - -#if _WIN32 // Some Windows Helpers - -// -// Converts IPv6 or IPV4 address to a (possibly mapped) IPv6. -// -inline -void -CxPlatConvertToMappedV6( - _In_ const QUIC_ADDR* InAddr, - _Out_ QUIC_ADDR* OutAddr - ) -{ - if (InAddr->si_family == QUIC_ADDRESS_FAMILY_INET) { - SCOPE_ID unspecified_scope = {0}; - IN6ADDR_SETV4MAPPED( - &OutAddr->Ipv6, - &InAddr->Ipv4.sin_addr, - unspecified_scope, - InAddr->Ipv4.sin_port); - } else { - *OutAddr = *InAddr; - } -} - -// -// Converts (possibly mapped) IPv6 address to a IPv6 or IPV4 address. Does -// support InAdrr == OutAddr. -// -#pragma warning(push) -#pragma warning(disable: 6101) // Intentially don't overwrite output if unable to convert -inline -void -CxPlatConvertFromMappedV6( - _In_ const QUIC_ADDR* InAddr, - _Out_ QUIC_ADDR* OutAddr - ) -{ - CXPLAT_DBG_ASSERT(InAddr->si_family == QUIC_ADDRESS_FAMILY_INET6); - if (IN6_IS_ADDR_V4MAPPED(&InAddr->Ipv6.sin6_addr)) { - OutAddr->si_family = QUIC_ADDRESS_FAMILY_INET; - OutAddr->Ipv4.sin_port = InAddr->Ipv6.sin6_port; - OutAddr->Ipv4.sin_addr = - *(IN_ADDR UNALIGNED *) - IN6_GET_ADDR_V4MAPPED(&InAddr->Ipv6.sin6_addr); - } else if (OutAddr != InAddr) { - *OutAddr = *InAddr; - } -} -#pragma warning(pop) - -#endif - -// -// Crypt Initialization -// - -QUIC_STATUS -CxPlatCryptInitialize( - void - ); - -void -CxPlatCryptUninitialize( - void - ); - -// -// Platform Worker APIs -// - -void -CxPlatWorkersInit( - void - ); - -void -CxPlatWorkersUninit( - void - ); - -BOOLEAN -CxPlatWorkersLazyStart( - _In_opt_ QUIC_EXECUTION_CONFIG* Config - ); - -CXPLAT_EVENTQ* -CxPlatWorkerGetEventQ( - _In_ uint16_t Index // Into the config processor array - ); - -void -CxPlatDataPathProcessCqe( - _In_ CXPLAT_CQE* Cqe - ); - -BOOLEAN // Returns FALSE no work was done. -CxPlatDataPathPoll( - _In_ void* Context, - _Out_ BOOLEAN* RemoveFromPolling - ); - -typedef struct DATAPATH_SQE { - uint32_t CqeType; -#ifdef CXPLAT_SQE - CXPLAT_SQE Sqe; -#endif -} DATAPATH_SQE; - -typedef struct CXPLAT_DATAPATH_PROC CXPLAT_DATAPATH_PROC; - // // Type of IO. // @@ -271,6 +177,15 @@ typedef enum DATAPATH_IO_TYPE { DATAPATH_IO_MAX } DATAPATH_IO_TYPE; +// +// Type of IO for XDP. +// +typedef enum DATAPATH_XDP_IO_TYPE { + DATAPATH_XDP_IO_SIGNATURE = 'XDPD', + DATAPATH_XDP_IO_RECV = DATAPATH_XDP_IO_SIGNATURE + 1, + DATAPATH_XDP_IO_SEND = DATAPATH_XDP_IO_SIGNATURE + 2 +} DATAPATH_XDP_IO_TYPE; + // // IO header for SQE->CQE based completions. // @@ -445,7 +360,7 @@ typedef struct QUIC_CACHEALIGN CXPLAT_SOCKET_PROC { // Main structure for tracking all UDP abstractions. // typedef struct CXPLAT_DATAPATH { - CXPLAT_DATAPATH_BASE; + CXPLAT_DATAPATH_COMMON; // // Function pointer to AcceptEx. @@ -528,7 +443,7 @@ typedef struct CXPLAT_DATAPATH { // Per-port state. Multiple sockets are created on each port. // typedef struct CXPLAT_SOCKET { - CXPLAT_SOCKET_BASE; + CXPLAT_SOCKET_COMMON; // // Parent datapath. @@ -600,15 +515,168 @@ typedef struct CXPLAT_SOCKET { } CXPLAT_SOCKET; -uint32_t -CxPlatGetRawSocketSize (); +typedef struct CXPLAT_SOCKET_RAW CXPLAT_SOCKET_RAW; +// TODO: rename CXPLAT_SOCKET* CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket); +// TODO: rename CXPLAT_SOCKET_RAW* CxPlatSocketToRaw(CXPLAT_SOCKET* Socket); +#elif defined(CX_PLATFORM_LINUX) || defined(CX_PLATFORM_DARWIN) + +typedef struct CX_PLATFORM { + + void* Reserved; // Nothing right now. + +#ifdef DEBUG + // + // 1/Denominator of allocations to fail. + // Negative is Nth allocation to fail. + // + int32_t AllocFailDenominator; + + // + // Count of allocations. + // + long AllocCounter; +#endif + +} CX_PLATFORM; + +#else + +#error "Unsupported Platform" + +#endif + +#pragma warning(disable:4204) // nonstandard extension used: non-constant aggregate initializer +#pragma warning(disable:4200) // nonstandard extension used: zero-sized array in struct/union + +// +// Global Platform variables/state. +// +extern CX_PLATFORM CxPlatform; + +// +// PCP Receive Callback +// +CXPLAT_DATAPATH_RECEIVE_CALLBACK CxPlatPcpRecvCallback; + +#if _WIN32 // Some Windows Helpers + +// +// Converts IPv6 or IPV4 address to a (possibly mapped) IPv6. +// +inline +void +CxPlatConvertToMappedV6( + _In_ const QUIC_ADDR* InAddr, + _Out_ QUIC_ADDR* OutAddr + ) +{ + if (InAddr->si_family == QUIC_ADDRESS_FAMILY_INET) { + SCOPE_ID unspecified_scope = {0}; + IN6ADDR_SETV4MAPPED( + &OutAddr->Ipv6, + &InAddr->Ipv4.sin_addr, + unspecified_scope, + InAddr->Ipv4.sin_port); + } else { + *OutAddr = *InAddr; + } +} + +// +// Converts (possibly mapped) IPv6 address to a IPv6 or IPV4 address. Does +// support InAdrr == OutAddr. +// +#pragma warning(push) +#pragma warning(disable: 6101) // Intentially don't overwrite output if unable to convert +inline +void +CxPlatConvertFromMappedV6( + _In_ const QUIC_ADDR* InAddr, + _Out_ QUIC_ADDR* OutAddr + ) +{ + CXPLAT_DBG_ASSERT(InAddr->si_family == QUIC_ADDRESS_FAMILY_INET6); + if (IN6_IS_ADDR_V4MAPPED(&InAddr->Ipv6.sin6_addr)) { + OutAddr->si_family = QUIC_ADDRESS_FAMILY_INET; + OutAddr->Ipv4.sin_port = InAddr->Ipv6.sin6_port; + OutAddr->Ipv4.sin_addr = + *(IN_ADDR UNALIGNED *) + IN6_GET_ADDR_V4MAPPED(&InAddr->Ipv6.sin6_addr); + } else if (OutAddr != InAddr) { + *OutAddr = *InAddr; + } +} +#pragma warning(pop) + +#endif + +// +// Crypt Initialization +// + +QUIC_STATUS +CxPlatCryptInitialize( + void + ); + +void +CxPlatCryptUninitialize( + void + ); + +// +// Platform Worker APIs +// + +void +CxPlatWorkersInit( + void + ); + +void +CxPlatWorkersUninit( + void + ); + +BOOLEAN +CxPlatWorkersLazyStart( + _In_opt_ QUIC_EXECUTION_CONFIG* Config + ); + +CXPLAT_EVENTQ* +CxPlatWorkerGetEventQ( + _In_ uint16_t Index // Into the config processor array + ); + +void +CxPlatDataPathProcessCqe( + _In_ CXPLAT_CQE* Cqe + ); + +BOOLEAN // Returns FALSE no work was done. +CxPlatDataPathPoll( + _In_ void* Context, + _Out_ BOOLEAN* RemoveFromPolling + ); + +typedef struct CXPLAT_DATAPATH_PROC CXPLAT_DATAPATH_PROC; + +uint32_t +CxPlatGetRawSocketSize (); + +// CXPLAT_SOCKET* +// CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket); + +// CXPLAT_SOCKET_RAW* +// CxPlatSocketToRaw(CXPLAT_SOCKET* Socket); + // // Queries the raw datapath stack for the total size needed to allocate the // datapath structure. From 5e4ab19621c2c25e397a49ebd6dbee9152e98999 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 23 Aug 2023 21:48:46 -0700 Subject: [PATCH 45/87] refactoring CXPLAT_SEND_DATA --- src/inc/quic_datapath.h | 32 +++++--------- src/platform/datapath_raw.h | 14 +++--- src/platform/datapath_raw_socket.c | 8 ++-- src/platform/datapath_raw_win.c | 10 ++--- src/platform/datapath_raw_xdp_win.c | 6 +-- src/platform/datapath_win.c | 55 ++++++++++-------------- src/platform/datapath_winuser.c | 66 ++++++++++++++--------------- src/platform/platform_internal.h | 4 +- 8 files changed, 88 insertions(+), 107 deletions(-) diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 2fdf5696ac..5ec0277305 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -124,7 +124,7 @@ PacketSizeFromUdpPayloadSize( // // The top level datapath handle type. // -// typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; +typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; typedef struct CXPLAT_DATAPATH_RAW CXPLAT_DATAPATH_RAW; @@ -154,18 +154,8 @@ typedef enum CXPLAT_BUFFER_FROM { // // Structure that maintains the 'per send' context. // -typedef struct CXPLAT_SEND_DATA_INTERNAL CXPLAT_SEND_DATA_INTERNAL; typedef struct CXPLAT_SEND_DATA CXPLAT_SEND_DATA; -// typedef struct CXPLAT_SEND_DATA { -// uint16_t BufferFrom : 2; - -// // -// // The type of ECN markings needed for send. -// // -// uint8_t ECN; // CXPLAT_ECN_TYPE -// } CXPLAT_SEND_DATA; - // // Contains a pointer and length. // @@ -863,15 +853,15 @@ typedef struct CXPLAT_DATAPATH_FUNCTIONS { void (*CxPlatRecvDataReturn)(_In_opt_ CXPLAT_RECV_DATA* RecvDataChain); CXPLAT_SEND_DATA* (*CxPlatSendDataAlloc)(_In_ CXPLAT_SOCKET* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config); - void (*CxPlatSendDataFree)(_In_ CXPLAT_SEND_DATA_INTERNAL* SendData); - QUIC_BUFFER* (*CxPlatSendDataAllocBuffer)(_In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + void (*CxPlatSendDataFree)(_In_ CXPLAT_SEND_DATA* SendData); + QUIC_BUFFER* (*CxPlatSendDataAllocBuffer)(_In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength); - void (*CxPlatSendDataFreeBuffer)(_In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + void (*CxPlatSendDataFreeBuffer)(_In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer); - BOOLEAN (*CxPlatSendDataIsFull)(_In_ CXPLAT_SEND_DATA_INTERNAL* SendData); + BOOLEAN (*CxPlatSendDataIsFull)(_In_ CXPLAT_SEND_DATA* SendData); QUIC_STATUS (*CxPlatSocketSend)(_In_ CXPLAT_SOCKET* Socket, _In_ const CXPLAT_ROUTE* Route, - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData); + _In_ CXPLAT_SEND_DATA* SendData); void (*CxPlatDataPathProcessCqe)(_In_ CXPLAT_CQE* Cqe); void (*QuicCopyRouteInfo)(_Inout_ CXPLAT_ROUTE* DstRoute, _In_ CXPLAT_ROUTE* SrcRoutee); @@ -956,28 +946,28 @@ XDP_CxPlatSendDataAlloc( _IRQL_requires_max_(DISPATCH_LEVEL) void XDP_CxPlatSendDataFree( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ); _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* XDP_CxPlatSendDataAllocBuffer( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ); _IRQL_requires_max_(DISPATCH_LEVEL) void XDP_CxPlatSendDataFreeBuffer( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ); _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN XDP_CxPlatSendDataIsFull( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ); _IRQL_requires_max_(DISPATCH_LEVEL) @@ -985,7 +975,7 @@ QUIC_STATUS XDP_CxPlatSocketSend( _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route, - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ); void diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index cf1c5a7819..638a37d08e 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -82,12 +82,12 @@ typedef struct CXPLAT_INTERFACE { } OffloadStatus; } CXPLAT_INTERFACE; -typedef struct CXPLAT_SEND_DATA_INTERNAL { - CXPLAT_SEND_DATA; +typedef struct CXPLAT_SEND_DATA { + CXPLAT_SEND_DATA_COMMON; QUIC_BUFFER Buffer; -} CXPLAT_SEND_DATA_INTERNAL; +} CXPLAT_SEND_DATA; // // Initializes the raw datapath stack. @@ -225,7 +225,7 @@ CxPlatDpRawTxAlloc( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawTxFree( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ); // @@ -234,7 +234,7 @@ CxPlatDpRawTxFree( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawTxEnqueue( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ); // @@ -264,8 +264,8 @@ typedef struct CXPLAT_SOCKET_RAW { uint8_t CibirId[6]; // CIBIR ID data BOOLEAN UseTcp; // Quic over TCP - CXPLAT_SEND_DATA_INTERNAL* PausedTcpSend; // Paused TCP send data *before* framing - CXPLAT_SEND_DATA_INTERNAL* CachedRstSend; // Cached TCP RST send data *after* framing + CXPLAT_SEND_DATA* PausedTcpSend; // Paused TCP send data *before* framing + CXPLAT_SEND_DATA* CachedRstSend; // Cached TCP RST send data *after* framing CXPLAT_SOCKET; } CXPLAT_SOCKET_RAW; diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index 97dfaadc4c..10370c2112 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -519,7 +519,7 @@ CxPlatDpRawSocketAckFin( CXPLAT_ROUTE* Route = Packet->Route; CXPLAT_SEND_CONFIG SendConfig = { Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA_INTERNAL *SendData = (CXPLAT_SEND_DATA_INTERNAL*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); + CXPLAT_SEND_DATA *SendData = (CXPLAT_SEND_DATA*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); if (SendData == NULL) { return; } @@ -558,7 +558,7 @@ CxPlatDpRawSocketAckSyn( CXPLAT_ROUTE* Route = Packet->Route; CXPLAT_SEND_CONFIG SendConfig = { Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA_INTERNAL *SendData = (CXPLAT_SEND_DATA_INTERNAL*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); + CXPLAT_SEND_DATA *SendData = (CXPLAT_SEND_DATA*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); if (SendData == NULL) { return; } @@ -607,7 +607,7 @@ CxPlatDpRawSocketAckSyn( TH_ACK); CxPlatDpRawTxEnqueue(SendData); - SendData = (CXPLAT_SEND_DATA_INTERNAL*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); + SendData = (CXPLAT_SEND_DATA*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); if (SendData == NULL) { return; } @@ -642,7 +642,7 @@ CxPlatDpRawSocketSyn( CXPLAT_DBG_ASSERT(Socket->UseTcp); CXPLAT_SEND_CONFIG SendConfig = { (CXPLAT_ROUTE*)Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA_INTERNAL *SendData = (CXPLAT_SEND_DATA_INTERNAL*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); + CXPLAT_SEND_DATA *SendData = (CXPLAT_SEND_DATA*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); if (SendData == NULL) { return; } diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index ebf35357b0..0369859317 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -529,7 +529,7 @@ _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* MANGLE(CxPlatSendDataAllocBuffer)( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ) { @@ -540,7 +540,7 @@ MANGLE(CxPlatSendDataAllocBuffer)( _IRQL_requires_max_(DISPATCH_LEVEL) void MANGLE(CxPlatSendDataFree)( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ) { CxPlatDpRawTxFree(SendData); @@ -549,7 +549,7 @@ MANGLE(CxPlatSendDataFree)( _IRQL_requires_max_(DISPATCH_LEVEL) void MANGLE(CxPlatSendDataFreeBuffer)( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ) { @@ -559,7 +559,7 @@ MANGLE(CxPlatSendDataFreeBuffer)( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN MANGLE(CxPlatSendDataIsFull)( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ) { return TRUE; @@ -572,7 +572,7 @@ QUIC_STATUS MANGLE(CxPlatSocketSend)( _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route, - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ) { if (Socket->UseTcp && diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 5514bbde15..cd83038334 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -101,7 +101,7 @@ typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) XDP_RX_PACKET { } XDP_RX_PACKET; typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) XDP_TX_PACKET { - CXPLAT_SEND_DATA_INTERNAL; + CXPLAT_SEND_DATA; XDP_QUEUE* Queue; CXPLAT_LIST_ENTRY Link; uint8_t FrameBuffer[MAX_ETH_FRAME_SIZE]; @@ -1700,7 +1700,7 @@ CxPlatDpRawTxAlloc( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawTxFree( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ) { XDP_TX_PACKET* Packet = (XDP_TX_PACKET*)SendData; @@ -1710,7 +1710,7 @@ CxPlatDpRawTxFree( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawTxEnqueue( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ) { XDP_TX_PACKET* Packet = (XDP_TX_PACKET*)SendData; diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 71f812a3d6..84c2836e79 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -27,16 +27,7 @@ #define RawDataPathAvailable(Datapath) ((Datapath)->RawDataPath != NULL) #define RawSocketAvailable(Socket) ((Socket)->Datapath && RawDataPathAvailable((Socket)->Datapath)) - -// CXPLAT_SOCKET* -// CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket) { -// return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); -// } -// // TODO: rename -// CXPLAT_SOCKET_RAW* -// CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { -// return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); -// } +#define SendBufferFrom(SendData) ((CXPLAT_SEND_DATA_COMMON*)(SendData))->BufferFrom _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS @@ -505,12 +496,12 @@ CxPlatSendDataAlloc( !IS_LOOPBACK(Config->Route->RemoteAddress)) { SendData = XDP_CxPlatSendDataAlloc(CxPlatSocketToRaw(Socket), Config); if (SendData) { - SendData->BufferFrom = CXPLAT_BUFFER_FROM_XDP; + SendBufferFrom(SendData) = CXPLAT_BUFFER_FROM_XDP; } } else { SendData = DataPathUserFuncs.CxPlatSendDataAlloc(Socket, Config); if (SendData) { - SendData->BufferFrom = CXPLAT_BUFFER_FROM_USER; + SendBufferFrom(SendData) = CXPLAT_BUFFER_FROM_USER; } } return SendData; @@ -522,10 +513,10 @@ CxPlatSendDataFree( _In_ CXPLAT_SEND_DATA* SendData ) { - if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { - DataPathUserFuncs.CxPlatSendDataFree((CXPLAT_SEND_DATA_INTERNAL*)SendData); - } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { - XDP_CxPlatSendDataFree((CXPLAT_SEND_DATA_INTERNAL*)SendData); + if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { + DataPathUserFuncs.CxPlatSendDataFree(SendData); + } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { + XDP_CxPlatSendDataFree(SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -539,10 +530,10 @@ CxPlatSendDataAllocBuffer( _In_ uint16_t MaxBufferLength ) { - if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { - return DataPathUserFuncs.CxPlatSendDataAllocBuffer((CXPLAT_SEND_DATA_INTERNAL*)SendData, MaxBufferLength); - } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { - return XDP_CxPlatSendDataAllocBuffer((CXPLAT_SEND_DATA_INTERNAL*)SendData, MaxBufferLength); + if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { + return DataPathUserFuncs.CxPlatSendDataAllocBuffer(SendData, MaxBufferLength); + } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { + return XDP_CxPlatSendDataAllocBuffer(SendData, MaxBufferLength); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -556,10 +547,10 @@ CxPlatSendDataFreeBuffer( _In_ QUIC_BUFFER* Buffer ) { - if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { - DataPathUserFuncs.CxPlatSendDataFreeBuffer((CXPLAT_SEND_DATA_INTERNAL*)SendData, Buffer); - } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { - XDP_CxPlatSendDataFreeBuffer((CXPLAT_SEND_DATA_INTERNAL*)SendData, Buffer); + if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { + DataPathUserFuncs.CxPlatSendDataFreeBuffer(SendData, Buffer); + } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { + XDP_CxPlatSendDataFreeBuffer(SendData, Buffer); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -571,10 +562,10 @@ CxPlatSendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ) { - if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { - return DataPathUserFuncs.CxPlatSendDataIsFull((CXPLAT_SEND_DATA_INTERNAL*)SendData); - } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { - return XDP_CxPlatSendDataIsFull((CXPLAT_SEND_DATA_INTERNAL*)SendData); + if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { + return DataPathUserFuncs.CxPlatSendDataIsFull(SendData); + } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { + return XDP_CxPlatSendDataIsFull(SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -589,10 +580,10 @@ CxPlatSocketSend( _In_ CXPLAT_SEND_DATA* SendData ) { - if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_USER) { - return DataPathUserFuncs.CxPlatSocketSend(Socket, Route, (CXPLAT_SEND_DATA_INTERNAL*)SendData); - } else if (SendData->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { - return XDP_CxPlatSocketSend(CxPlatSocketToRaw(Socket), Route, (CXPLAT_SEND_DATA_INTERNAL*)SendData); + if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { + return DataPathUserFuncs.CxPlatSocketSend(Socket, Route, SendData); + } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { + return XDP_CxPlatSocketSend(CxPlatSocketToRaw(Socket), Route, SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index e5a1da9c2e..46667ecfe4 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -204,14 +204,14 @@ typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) CXPLAT_RIO_SEND_BUFFE // // This send buffer's send data. // - CXPLAT_SEND_DATA_INTERNAL* SendData; + CXPLAT_SEND_DATA* SendData; } CXPLAT_RIO_SEND_BUFFER_HEADER; // // Send context. // -typedef struct CXPLAT_SEND_DATA_INTERNAL { - CXPLAT_SEND_DATA; +typedef struct CXPLAT_SEND_DATA { + CXPLAT_SEND_DATA_COMMON; // // The per-processor socket for this send data. // @@ -296,7 +296,7 @@ typedef struct CXPLAT_SEND_DATA_INTERNAL { // The V6-mapped remote address to send to. // QUIC_ADDR MappedRemoteAddress; -} CXPLAT_SEND_DATA_INTERNAL; +} CXPLAT_SEND_DATA; // // // // Per-processor socket state. @@ -837,7 +837,7 @@ CxPlatDataPathStartRioSends( void CxPlatSendDataComplete( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ CXPLAT_SEND_DATA* SendData, _In_ ULONG IoResult ); @@ -1200,13 +1200,13 @@ MANGLE(CxPlatDataPathInitialize)( CxPlatPoolInitialize( FALSE, - sizeof(CXPLAT_SEND_DATA_INTERNAL), + sizeof(CXPLAT_SEND_DATA), QUIC_POOL_PLATFORM_SENDCTX, &Datapath->Partitions[i].SendDataPool); CxPlatPoolInitializeEx( FALSE, - sizeof(CXPLAT_SEND_DATA_INTERNAL), + sizeof(CXPLAT_SEND_DATA), QUIC_POOL_PLATFORM_SENDCTX, 0, RioSendDataAllocate, @@ -2804,7 +2804,7 @@ CxPlatSocketContextRelease( while (!CxPlatListIsEmpty(&SocketProc->RioSendOverflow)) { CXPLAT_LIST_ENTRY* Entry = CxPlatListRemoveHead(&SocketProc->RioSendOverflow); CxPlatSendDataComplete( - CONTAINING_RECORD(Entry, CXPLAT_SEND_DATA_INTERNAL, RioOverflowEntry), + CONTAINING_RECORD(Entry, CXPLAT_SEND_DATA, RioOverflowEntry), WSA_OPERATION_ABORTED); } @@ -4133,7 +4133,7 @@ RioSendDataAllocate( CXPLAT_DATAPATH_PARTITION* DatapathProc = CXPLAT_CONTAINING_RECORD(Pool, CXPLAT_DATAPATH_PARTITION, RioSendDataPool); CXPLAT_DATAPATH* Datapath = DatapathProc->Datapath; - CXPLAT_SEND_DATA_INTERNAL* SendData; + CXPLAT_SEND_DATA* SendData; SendData = CxPlatLargeAlloc(Size, Tag); @@ -4156,7 +4156,7 @@ RioSendDataFree( _Inout_ CXPLAT_POOL* Pool ) { - CXPLAT_SEND_DATA_INTERNAL* SendData = Entry; + CXPLAT_SEND_DATA* SendData = Entry; UNREFERENCED_PARAMETER(Pool); @@ -4184,7 +4184,7 @@ MANGLE(CxPlatSendDataAlloc)( CXPLAT_POOL* SendDataPool = Socket->UseRio ? &DatapathProc->RioSendDataPool : &DatapathProc->SendDataPool; - CXPLAT_SEND_DATA_INTERNAL* SendData = CxPlatPoolAlloc(SendDataPool); + CXPLAT_SEND_DATA* SendData = CxPlatPoolAlloc(SendDataPool); if (SendData != NULL) { SendData->Owner = DatapathProc; @@ -4222,7 +4222,7 @@ MANGLE(CxPlatSendDataAlloc)( _IRQL_requires_max_(DISPATCH_LEVEL) void MANGLE(CxPlatSendDataFree)( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ) { for (UINT8 i = 0; i < SendData->WsaBufferCount; ++i) { @@ -4315,7 +4315,7 @@ RioSendBufferFree( static BOOLEAN CxPlatSendDataCanAllocSendSegment( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ CXPLAT_SEND_DATA* SendData, _In_ UINT16 MaxBufferLength ) { @@ -4337,7 +4337,7 @@ CxPlatSendDataCanAllocSendSegment( static BOOLEAN CxPlatSendDataCanAllocSend( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ CXPLAT_SEND_DATA* SendData, _In_ UINT16 MaxBufferLength ) { @@ -4350,7 +4350,7 @@ CxPlatSendDataCanAllocSend( static void CxPlatSendDataFinalizeSendBuffer( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ) { if (SendData->ClientBuffer.len == 0) { @@ -4392,7 +4392,7 @@ _Success_(return != NULL) static WSABUF* CxPlatSendDataAllocDataBuffer( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ) { CXPLAT_DBG_ASSERT(SendData->WsaBufferCount < SendData->Owner->Datapath->MaxSendBatchSize); @@ -4411,7 +4411,7 @@ _Success_(return != NULL) static QUIC_BUFFER* CxPlatSendDataAllocPacketBuffer( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ CXPLAT_SEND_DATA* SendData, _In_ UINT16 MaxBufferLength ) { @@ -4426,7 +4426,7 @@ _Success_(return != NULL) static QUIC_BUFFER* CxPlatSendDataAllocSegmentBuffer( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ CXPLAT_SEND_DATA* SendData, _In_ UINT16 MaxBufferLength ) { @@ -4461,7 +4461,7 @@ _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* MANGLE(CxPlatSendDataAllocBuffer)( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ) { @@ -4484,7 +4484,7 @@ MANGLE(CxPlatSendDataAllocBuffer)( _IRQL_requires_max_(DISPATCH_LEVEL) void MANGLE(CxPlatSendDataFreeBuffer)( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ) { @@ -4515,7 +4515,7 @@ MANGLE(CxPlatSendDataFreeBuffer)( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN MANGLE(CxPlatSendDataIsFull)( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ) { return !CxPlatSendDataCanAllocSend(SendData, SendData->SegmentSize); @@ -4523,7 +4523,7 @@ MANGLE(CxPlatSendDataIsFull)( void CxPlatSendDataComplete( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ CXPLAT_SEND_DATA* SendData, _In_ ULONG IoResult ) { @@ -4552,7 +4552,7 @@ CxPlatSendDataComplete( _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS CxPlatSocketSendWithRio( - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData, + _In_ CXPLAT_SEND_DATA* SendData, _In_ WSAMSG* WSAMhdr ) { @@ -4564,12 +4564,12 @@ CxPlatSocketSendWithRio( PRIO_CMSG_BUFFER RioCmsg = (PRIO_CMSG_BUFFER)SendData->CtrlBuf; RemoteAddr.BufferId = SendData->RioBufferId; - RemoteAddr.Offset = FIELD_OFFSET(CXPLAT_SEND_DATA_INTERNAL, MappedRemoteAddress); + RemoteAddr.Offset = FIELD_OFFSET(CXPLAT_SEND_DATA, MappedRemoteAddress); RemoteAddr.Length = sizeof(SendData->MappedRemoteAddress); RioCmsg->TotalLength = RIO_CMSG_BASE_SIZE + WSAMhdr->Control.len; Control.BufferId = SendData->RioBufferId; - Control.Offset = FIELD_OFFSET(CXPLAT_SEND_DATA_INTERNAL, CtrlBuf); + Control.Offset = FIELD_OFFSET(CXPLAT_SEND_DATA, CtrlBuf); Control.Length = RioCmsg->TotalLength; // @@ -4617,7 +4617,7 @@ _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS CxPlatSocketSendInline( _In_ const QUIC_ADDR* LocalAddress, - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ) { CXPLAT_SOCKET_PROC* SocketProc = SendData->SocketProc; @@ -4764,7 +4764,7 @@ CxPlatSocketSendInline( QUIC_STATUS CxPlatSocketSendEnqueue( _In_ const CXPLAT_ROUTE* Route, - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ) { SendData->LocalAddress = Route->LocalAddress; @@ -4782,7 +4782,7 @@ QUIC_STATUS MANGLE(CxPlatSocketSend)( _In_ CXPLAT_SOCKET* Socket, _In_ const CXPLAT_ROUTE* Route, - _In_ CXPLAT_SEND_DATA_INTERNAL* SendData + _In_ CXPLAT_SEND_DATA* SendData ) { CXPLAT_DBG_ASSERT(Socket != NULL && Route != NULL && SendData != NULL); @@ -4826,7 +4826,7 @@ CxPlatDataPathSocketProcessQueuedSend( ) { UNREFERENCED_PARAMETER(Cqe); - CXPLAT_SEND_DATA_INTERNAL* SendData = CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA_INTERNAL, Sqe); + CXPLAT_SEND_DATA* SendData = CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA, Sqe); CXPLAT_SOCKET_PROC* SocketProc = SendData->SocketProc; if (CxPlatRundownAcquire(&SocketProc->RundownRef)) { @@ -4845,7 +4845,7 @@ CxPlatDataPathStartRioSends( while (!CxPlatListIsEmpty(&SocketProc->RioSendOverflow) && SocketProc->RioSendCount < RIO_SEND_QUEUE_DEPTH) { CXPLAT_LIST_ENTRY* Entry = CxPlatListRemoveHead(&SocketProc->RioSendOverflow); - CXPLAT_SEND_DATA_INTERNAL* SendData = CONTAINING_RECORD(Entry, CXPLAT_SEND_DATA_INTERNAL, RioOverflowEntry); + CXPLAT_SEND_DATA* SendData = CONTAINING_RECORD(Entry, CXPLAT_SEND_DATA, RioOverflowEntry); // // RIO always queues sends. @@ -4884,14 +4884,14 @@ MANGLE(CxPlatDataPathProcessCqe)( break; case DATAPATH_IO_SEND: - SocketProc = CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA_INTERNAL, Sqe)->SocketProc; + SocketProc = CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA, Sqe)->SocketProc; CxPlatSendDataComplete( - CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA_INTERNAL, Sqe), + CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA, Sqe), RtlNtStatusToDosError((NTSTATUS)Cqe->Internal)); break; case DATAPATH_IO_QUEUE_SEND: - SocketProc = CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA_INTERNAL, Sqe)->SocketProc; + SocketProc = CONTAINING_RECORD(Sqe, CXPLAT_SEND_DATA, Sqe)->SocketProc; CxPlatDataPathSocketProcessQueuedSend(Sqe, Cqe); break; diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 37ab42cfed..81f8e2c13d 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -86,14 +86,14 @@ typedef struct CXPLAT_SOCKET_COMMON { QUIC_ADDR RemoteAddress; } CXPLAT_SOCKET_COMMON; -typedef struct CXPLAT_SEND_DATA { +typedef struct CXPLAT_SEND_DATA_COMMON { uint16_t BufferFrom : 2; // // The type of ECN markings needed for send. // uint8_t ECN; // CXPLAT_ECN_TYPE -} CXPLAT_SEND_DATA; +} CXPLAT_SEND_DATA_COMMON; #ifdef _KERNEL_MODE From cbd2d08f0c1a22373139e7add7c91aaa38761661 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 23 Aug 2023 22:53:57 -0700 Subject: [PATCH 46/87] remove mangling and function pointer --- src/inc/quic_datapath.h | 212 --------------- src/platform/datapath_raw.h | 1 - src/platform/datapath_raw_socket.c | 26 +- src/platform/datapath_raw_socket_win.c | 2 +- src/platform/datapath_raw_win.c | 114 ++------ src/platform/datapath_raw_xdp_win.c | 4 +- src/platform/datapath_win.c | 86 +++--- src/platform/datapath_winuser.c | 112 +++----- src/platform/platform_internal.h | 348 ++++++++++++++++++++++++- 9 files changed, 439 insertions(+), 466 deletions(-) diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 5ec0277305..933175e4ff 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -806,218 +806,6 @@ CxPlatUpdateRoute( _In_ CXPLAT_ROUTE* SrcRoute ); -typedef struct CXPLAT_DATAPATH_FUNCTIONS { - // CXPLAT_RECV_DATA* (*CxPlatDataPathRecvPacketToRecvData)(const CXPLAT_RECV_PACKET* const Context); - // CXPLAT_RECV_PACKET* (*CxPlatDataPathRecvDataToRecvPacket)(const CXPLAT_RECV_DATA* const Datagram); - QUIC_STATUS (*CxPlatDataPathInitialize)(_In_ uint32_t ClientRecvContextLength, - _In_opt_ QUIC_EXECUTION_CONFIG* Config, - _Out_ CXPLAT_DATAPATH* DataPath); - void (*CxPlatDataPathUninitialize)(_In_ CXPLAT_DATAPATH* Datapath); - void (*CxPlatDataPathUpdateConfig)(_In_ CXPLAT_DATAPATH* Datapath, - _In_ QUIC_EXECUTION_CONFIG* Config); - uint32_t (*CxPlatDataPathGetSupportedFeatures)(_In_ CXPLAT_DATAPATH* Datapath); - BOOLEAN (*CxPlatDataPathIsPaddingPreferred)(_In_ CXPLAT_DATAPATH* Datapath); - QUIC_STATUS (*CxPlatDataPathGetLocalAddresses)(_In_ CXPLAT_DATAPATH* Datapath, - _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) - CXPLAT_ADAPTER_ADDRESS** Addresses, - _Out_ uint32_t* AddressesCount); - QUIC_STATUS (*CxPlatDataPathGetGatewayAddresses)(_In_ CXPLAT_DATAPATH* Datapath, - _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) - QUIC_ADDR** GatewayAddresses, - _Out_ uint32_t* GatewayAddressesCount); - QUIC_STATUS (*CxPlatDataPathResolveAddress)(_In_ CXPLAT_DATAPATH* Datapath, - _In_z_ const char* HostName, - _Inout_ QUIC_ADDR* Address); - QUIC_STATUS (*CxPlatSocketCreateUdp)(_In_ CXPLAT_DATAPATH* Datapath, - _In_ const CXPLAT_UDP_CONFIG* Config, - _Out_ CXPLAT_SOCKET** Socket); - QUIC_STATUS (*CxPlatSocketCreateTcp)(_In_ CXPLAT_DATAPATH* Datapath, - _In_opt_ const QUIC_ADDR* LocalAddress, - _In_ const QUIC_ADDR* RemoteAddress, - _In_opt_ void* CallbackContext, - _Out_ CXPLAT_SOCKET** Socket); - QUIC_STATUS (*CxPlatSocketCreateTcpListener)(_In_ CXPLAT_DATAPATH* Datapath, - _In_opt_ const QUIC_ADDR* LocalAddress, - _In_opt_ void* RecvCallbackContext, - _Out_ CXPLAT_SOCKET** NewSocket); - void (*CxPlatSocketDelete)(_In_ CXPLAT_SOCKET* Socket); - QUIC_STATUS (*CxPlatSocketUpdateQeo)(_In_ CXPLAT_SOCKET* Socket, - _In_reads_(OffloadCount) - const CXPLAT_QEO_CONNECTION* Offloads, - _In_ uint32_t OffloadCount); - UINT16 (*CxPlatSocketGetLocalMtu)(_In_ CXPLAT_SOCKET* Socket); - void (*CxPlatSocketGetLocalAddress)(_In_ CXPLAT_SOCKET* Socket, - _Out_ QUIC_ADDR* Address); - void (*CxPlatSocketGetRemoteAddress)(_In_ CXPLAT_SOCKET* Socket, - _Out_ QUIC_ADDR* Address); - void (*CxPlatRecvDataReturn)(_In_opt_ CXPLAT_RECV_DATA* RecvDataChain); - CXPLAT_SEND_DATA* (*CxPlatSendDataAlloc)(_In_ CXPLAT_SOCKET* Socket, - _Inout_ CXPLAT_SEND_CONFIG* Config); - void (*CxPlatSendDataFree)(_In_ CXPLAT_SEND_DATA* SendData); - QUIC_BUFFER* (*CxPlatSendDataAllocBuffer)(_In_ CXPLAT_SEND_DATA* SendData, - _In_ uint16_t MaxBufferLength); - void (*CxPlatSendDataFreeBuffer)(_In_ CXPLAT_SEND_DATA* SendData, - _In_ QUIC_BUFFER* Buffer); - BOOLEAN (*CxPlatSendDataIsFull)(_In_ CXPLAT_SEND_DATA* SendData); - QUIC_STATUS (*CxPlatSocketSend)(_In_ CXPLAT_SOCKET* Socket, - _In_ const CXPLAT_ROUTE* Route, - _In_ CXPLAT_SEND_DATA* SendData); - void (*CxPlatDataPathProcessCqe)(_In_ CXPLAT_CQE* Cqe); - void (*QuicCopyRouteInfo)(_Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoutee); -} CXPLAT_DATAPATH_FUNCTIONS; - -extern const struct CXPLAT_DATAPATH_FUNCTIONS DataPathUserFuncs; -extern const struct CXPLAT_DATAPATH_FUNCTIONS DataPathXdpFuncs; // RawFuncs? - -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -CxPlatInitRawSocket( - _In_ CXPLAT_DATAPATH_RAW* DataPath, - _In_ const CXPLAT_UDP_CONFIG* Config, - _Out_ CXPLAT_SOCKET_RAW* NewSocket - ); - -_IRQL_requires_max_(PASSIVE_LEVEL) -void -CxPlatRawSocketDelete( - _In_ CXPLAT_SOCKET_RAW* Socket - ); - -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -CxPlatInitRawDataPath( - _In_ uint32_t ClientRecvContextLength, - _In_opt_ QUIC_EXECUTION_CONFIG* Config, - _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, - _Out_ CXPLAT_DATAPATH_RAW* DataPath - ); - -// TODO: rename as generic for raw - -_IRQL_requires_max_(PASSIVE_LEVEL) -void -XDP_CxPlatDataPathUninitialize( - _In_ CXPLAT_DATAPATH_RAW* Datapath - ); - -_IRQL_requires_max_(PASSIVE_LEVEL) -void -XDP_CxPlatDataPathUpdateConfig( - _In_ CXPLAT_DATAPATH_RAW* Datapath, - _In_ QUIC_EXECUTION_CONFIG* Config - ); - -_IRQL_requires_max_(DISPATCH_LEVEL) -uint32_t -XDP_CxPlatDataPathGetSupportedFeatures( - _In_ CXPLAT_DATAPATH_RAW* Datapath - ); - -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -XDP_CxPlatSocketUpdateQeo( - _In_ CXPLAT_SOCKET_RAW* Socket, - _In_reads_(OffloadCount) - const CXPLAT_QEO_CONNECTION* Offloads, - _In_ uint32_t OffloadCount - ); - -_IRQL_requires_max_(DISPATCH_LEVEL) -UINT16 -XDP_CxPlatSocketGetLocalMtu( - _In_ CXPLAT_SOCKET_RAW* Socket - ); - -_IRQL_requires_max_(DISPATCH_LEVEL) -void -XDP_CxPlatRecvDataReturn( - _In_opt_ CXPLAT_RECV_DATA* RecvDataChain - ); - -_IRQL_requires_max_(DISPATCH_LEVEL) -_Success_(return != NULL) -CXPLAT_SEND_DATA* -XDP_CxPlatSendDataAlloc( - _In_ CXPLAT_SOCKET_RAW* Socket, - _Inout_ CXPLAT_SEND_CONFIG* Config - ); - -_IRQL_requires_max_(DISPATCH_LEVEL) -void -XDP_CxPlatSendDataFree( - _In_ CXPLAT_SEND_DATA* SendData - ); - -_IRQL_requires_max_(DISPATCH_LEVEL) -_Success_(return != NULL) -QUIC_BUFFER* -XDP_CxPlatSendDataAllocBuffer( - _In_ CXPLAT_SEND_DATA* SendData, - _In_ uint16_t MaxBufferLength - ); - -_IRQL_requires_max_(DISPATCH_LEVEL) -void -XDP_CxPlatSendDataFreeBuffer( - _In_ CXPLAT_SEND_DATA* SendData, - _In_ QUIC_BUFFER* Buffer - ); - -_IRQL_requires_max_(DISPATCH_LEVEL) -BOOLEAN -XDP_CxPlatSendDataIsFull( - _In_ CXPLAT_SEND_DATA* SendData - ); - -_IRQL_requires_max_(DISPATCH_LEVEL) -QUIC_STATUS -XDP_CxPlatSocketSend( - _In_ CXPLAT_SOCKET_RAW* Socket, - _In_ const CXPLAT_ROUTE* Route, - _In_ CXPLAT_SEND_DATA* SendData - ); - -void -XDP_CxPlatResolveRouteComplete( - _In_ void* Context, - _Inout_ CXPLAT_ROUTE* Route, - _In_reads_bytes_(6) const uint8_t* PhysicalAddress, - _In_ uint8_t PathId - ); - -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -XDP_CxPlatResolveRoute( - _In_ CXPLAT_SOCKET* Sock, - _Inout_ CXPLAT_ROUTE* Route, - _In_ uint8_t PathId, - _In_ void* Context, - _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback - ); - -void -XDP_CxPlatDataPathProcessCqe( - _In_ CXPLAT_CQE* Cqe - ); - -CXPLAT_RECV_PACKET* -XDP_CxPlatDataPathRecvDataToRecvPacket( - _In_ const CXPLAT_RECV_DATA* const Datagram - ); - -CXPLAT_RECV_DATA* -XDP_CxPlatDataPathRecvPacketToRecvData( - _In_ const CXPLAT_RECV_PACKET* const Context - ); - -_IRQL_requires_max_(PASSIVE_LEVEL) -void -XDP_CxPlatUpdateRoute( - _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute - ); - #if defined(__cplusplus) } #endif diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index 638a37d08e..087f7e83c2 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -17,7 +17,6 @@ typedef struct CXPLAT_SOCKET_POOL { } CXPLAT_SOCKET_POOL; -#define MANGLE(x) XDP_##x // // A worker thread for draining queued route resolution operations. diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index 10370c2112..f49c4b695d 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -23,17 +23,6 @@ CxPlatGetRawSocketSize () { return sizeof(CXPLAT_SOCKET_RAW); } -// // TODO: rename -// CXPLAT_SOCKET* -// CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket) { -// return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); -// } -// // TODO: rename -// CXPLAT_SOCKET_RAW* -// CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { -// return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); -// } - CXPLAT_SOCKET_RAW* CxPlatGetSocket( _In_ const CXPLAT_SOCKET_POOL* Pool, @@ -60,19 +49,8 @@ CxPlatGetSocket( return Socket; } -_IRQL_requires_max_(PASSIVE_LEVEL) -void -MANGLE(QuicCopyRouteInfo)( - _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute - ) -{ - CxPlatCopyMemory(DstRoute, SrcRoute, (uint8_t*)&SrcRoute->State - (uint8_t*)SrcRoute); - CxPlatUpdateRoute(DstRoute, SrcRoute); -} - void -MANGLE(CxPlatResolveRouteComplete)( +RawResolveRouteComplete( _In_ void* Context, _Inout_ CXPLAT_ROUTE* Route, _In_reads_bytes_(6) const uint8_t* PhysicalAddress, @@ -97,7 +75,7 @@ MANGLE(CxPlatResolveRouteComplete)( _IRQL_requires_max_(PASSIVE_LEVEL) void -MANGLE(CxPlatUpdateRoute)( +RawUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, _In_ CXPLAT_ROUTE* SrcRoute ) diff --git a/src/platform/datapath_raw_socket_win.c b/src/platform/datapath_raw_socket_win.c index fd1b6c26d3..78adf4b9d1 100644 --- a/src/platform/datapath_raw_socket_win.c +++ b/src/platform/datapath_raw_socket_win.c @@ -77,7 +77,7 @@ CxPlatRemoveSocket( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -MANGLE(CxPlatResolveRoute)( +RawResolveRoute( _In_ CXPLAT_SOCKET* Sock, _Inout_ CXPLAT_ROUTE* Route, _In_ uint8_t PathId, diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index 0369859317..a449b1a669 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -105,7 +105,7 @@ CxPlatDataPathRouteWorkerInitialize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatInitRawDataPath( +RawDataPathInitialize( _In_ uint32_t ClientRecvContextLength, _In_opt_ QUIC_EXECUTION_CONFIG* Config, _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, @@ -168,7 +168,7 @@ CxPlatInitRawDataPath( _IRQL_requires_max_(PASSIVE_LEVEL) void -MANGLE(CxPlatDataPathUninitialize)( +RawDataPathUninitialize( _In_ CXPLAT_DATAPATH_RAW* Datapath ) { @@ -201,7 +201,7 @@ CxPlatDataPathUninitializeComplete( _IRQL_requires_max_(PASSIVE_LEVEL) void -MANGLE(CxPlatDataPathUpdateConfig)( +RawDataPathUpdateConfig( _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ) @@ -211,7 +211,7 @@ MANGLE(CxPlatDataPathUpdateConfig)( _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t -MANGLE(CxPlatDataPathGetSupportedFeatures)( +RawDataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH_RAW* Datapath ) { @@ -220,7 +220,7 @@ MANGLE(CxPlatDataPathGetSupportedFeatures)( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -MANGLE(CxPlatDataPathIsPaddingPreferred)( +RawDataPathIsPaddingPreferred( _In_ CXPLAT_DATAPATH* Datapath ) { @@ -230,7 +230,7 @@ MANGLE(CxPlatDataPathIsPaddingPreferred)( _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS -MANGLE(CxPlatDataPathGetLocalAddresses)( +RawDataPathGetLocalAddresses( _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) CXPLAT_ADAPTER_ADDRESS** Addresses, @@ -243,7 +243,7 @@ MANGLE(CxPlatDataPathGetLocalAddresses)( _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS -MANGLE(CxPlatDataPathGetGatewayAddresses)( +RawDataPathGetGatewayAddresses( _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) QUIC_ADDR** GatewayAddresses, @@ -253,40 +253,9 @@ MANGLE(CxPlatDataPathGetGatewayAddresses)( return QUIC_STATUS_NOT_SUPPORTED; } -// void -// CxPlatDataPathPopulateTargetAddress( -// _In_ ADDRESS_FAMILY Family, -// _In_ ADDRINFOW *Ai, -// _Out_ SOCKADDR_INET* Address -// ) -// { -// if (Ai->ai_addr->sa_family == QUIC_ADDRESS_FAMILY_INET6) { -// // -// // Is this a mapped ipv4 one? -// // -// PSOCKADDR_IN6 SockAddr6 = (PSOCKADDR_IN6)Ai->ai_addr; - -// if (Family == QUIC_ADDRESS_FAMILY_UNSPEC && IN6ADDR_ISV4MAPPED(SockAddr6)) -// { -// PSOCKADDR_IN SockAddr4 = &Address->Ipv4; -// // -// // Get the ipv4 address from the mapped address. -// // -// SockAddr4->sin_family = QUIC_ADDRESS_FAMILY_INET; -// SockAddr4->sin_addr = -// *(IN_ADDR UNALIGNED *) -// IN6_GET_ADDR_V4MAPPED(&SockAddr6->sin6_addr); -// SockAddr4->sin_port = SockAddr6->sin6_port; -// return; -// } -// } - -// CxPlatCopyMemory(Address, Ai->ai_addr, Ai->ai_addrlen); -// } - _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatInitRawSocket( +RawSocketCreateUdp( _In_ CXPLAT_DATAPATH_RAW* Raw, _In_ const CXPLAT_UDP_CONFIG* Config, // _Out_ CXPLAT_SOCKET_BASE** NewSocket @@ -359,7 +328,7 @@ CxPlatInitRawSocket( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -MANGLE(CxPlatSocketCreateTcp)( +RawCxPlatSocketCreateTcp( _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, @@ -372,7 +341,7 @@ MANGLE(CxPlatSocketCreateTcp)( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -MANGLE(CxPlatSocketCreateTcpListener)( +RawSocketCreateTcpListener( _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ void* RecvCallbackContext, @@ -384,7 +353,7 @@ MANGLE(CxPlatSocketCreateTcpListener)( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatRawSocketDelete( +RawSocketDelete( _In_ CXPLAT_SOCKET_RAW* Socket ) { @@ -403,32 +372,11 @@ CxPlatRawSocketDelete( CxPlatDpRawTxEnqueue(Socket->CachedRstSend); } - // CXPLAT_FREE(Socket, QUIC_POOL_SOCKET); -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -void -MANGLE(CxPlatSocketDelete)( - _In_ CXPLAT_SOCKET_RAW* Socket - ) -{ - CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); - CxPlatRemoveSocket(&Socket->RawDatapath->SocketPool, Socket); - CxPlatRundownReleaseAndWait(&Socket->Rundown); - if (Socket->PausedTcpSend) { - CxPlatDpRawTxFree(Socket->PausedTcpSend); - } - - if (Socket->CachedRstSend) { - CxPlatDpRawTxEnqueue(Socket->CachedRstSend); - } - - CXPLAT_FREE(Socket, QUIC_POOL_SOCKET); } _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 -MANGLE(CxPlatSocketGetLocalMtu)( +RawSocketGetLocalMtu( _In_ CXPLAT_SOCKET_RAW* Socket ) { @@ -507,7 +455,7 @@ CxPlatDpRawRxEthernet( _IRQL_requires_max_(DISPATCH_LEVEL) void -MANGLE(CxPlatRecvDataReturn)( +RawRecvDataReturn( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ) { @@ -517,7 +465,7 @@ MANGLE(CxPlatRecvDataReturn)( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* -MANGLE(CxPlatSendDataAlloc)( +RawSendDataAlloc( _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) @@ -528,7 +476,7 @@ MANGLE(CxPlatSendDataAlloc)( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* -MANGLE(CxPlatSendDataAllocBuffer)( +RawSendDataAllocBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ) @@ -539,7 +487,7 @@ MANGLE(CxPlatSendDataAllocBuffer)( _IRQL_requires_max_(DISPATCH_LEVEL) void -MANGLE(CxPlatSendDataFree)( +RawSendDataFree( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -548,7 +496,7 @@ MANGLE(CxPlatSendDataFree)( _IRQL_requires_max_(DISPATCH_LEVEL) void -MANGLE(CxPlatSendDataFreeBuffer)( +RawSendDataFreeBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ) @@ -558,7 +506,7 @@ MANGLE(CxPlatSendDataFreeBuffer)( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -MANGLE(CxPlatSendDataIsFull)( +RawSendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -569,7 +517,7 @@ MANGLE(CxPlatSendDataIsFull)( _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS -MANGLE(CxPlatSocketSend)( +RawSocketSend( _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA* SendData @@ -675,27 +623,3 @@ CXPLAT_THREAD_CALLBACK(CxPlatRouteResolutionWorkerThread, Context) return 0; } - -CXPLAT_RECV_DATA* -MANGLE(CxPlatDataPathRecvPacketToRecvData)( - _In_ const CXPLAT_RECV_PACKET* const RecvPacket - ); - -CXPLAT_RECV_PACKET* -MANGLE(CxPlatDataPathRecvDataToRecvPacket)( - _In_ const CXPLAT_RECV_DATA* const RecvData - ); - -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -MANGLE(CxPlatSocketUpdateQeo)( - _In_ CXPLAT_SOCKET_RAW* Socket, - _In_reads_(OffloadCount) - const CXPLAT_QEO_CONNECTION* Offloads, - _In_ uint32_t OffloadCount - ); - -void -MANGLE(CxPlatDataPathProcessCqe)( - _In_ CXPLAT_CQE* Cqe - ); diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index cd83038334..651af8828c 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -1284,7 +1284,7 @@ CxPlatDpRawUpdateConfig( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -MANGLE(CxPlatSocketUpdateQeo)( +RawSocketUpdateQeo( _In_ CXPLAT_SOCKET_RAW* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, @@ -1905,7 +1905,7 @@ CxPlatXdpExecute( } void -MANGLE(CxPlatDataPathProcessCqe)( +RawDataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ) { diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 84c2836e79..6b6afe191d 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -19,6 +19,7 @@ #pragma warning(disable:4100) // unreferenced #pragma warning(disable:6101) // uninitialized +#pragma warning(disable:6386) // buffer overrun #define IS_LOOPBACK(Address) ((Address.si_family == QUIC_ADDRESS_FAMILY_INET && \ Address.Ipv4.sin_addr.S_un.S_addr == htonl(INADDR_LOOPBACK)) || \ @@ -94,7 +95,7 @@ CxPlatDataPathInitialize( Datapath->TcpHandlers = *TcpCallbacks; } Datapath->PartitionCount = (uint16_t)PartitionCount; - Status = DataPathUserFuncs.CxPlatDataPathInitialize( + Status = DataPathInitialize( ClientRecvContextLength, Config, Datapath); @@ -118,7 +119,7 @@ CxPlatDataPathInitialize( CxPlatZeroMemory(RawDataPath, RawDatapathSize); // Status = QUIC_STATUS_INVALID_PARAMETER; - Status = CxPlatInitRawDataPath( + Status = RawDataPathInitialize( ClientRecvContextLength, Config, Datapath, @@ -134,6 +135,7 @@ CxPlatDataPathInitialize( Datapath->RawDataPath = RawDataPath; *NewDataPath = Datapath; + Error: // TODO: error handling @@ -147,9 +149,9 @@ CxPlatDataPathUninitialize( ) { if (Datapath->RawDataPath) { - XDP_CxPlatDataPathUninitialize(Datapath->RawDataPath); - } - DataPathUserFuncs.CxPlatDataPathUninitialize(Datapath); + RawDataPathUninitialize(Datapath->RawDataPath); + } + DataPathUninitialize(Datapath); } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -159,9 +161,9 @@ CxPlatDataPathUpdateConfig( _In_ QUIC_EXECUTION_CONFIG* Config ) { - DataPathUserFuncs.CxPlatDataPathUpdateConfig(Datapath, Config); + DataPathUpdateConfig(Datapath, Config); if (Datapath->RawDataPath) { - XDP_CxPlatDataPathUpdateConfig(Datapath->RawDataPath, Config); + RawDataPathUpdateConfig(Datapath->RawDataPath, Config); } } @@ -172,10 +174,10 @@ CxPlatDataPathGetSupportedFeatures( ) { if (Datapath->RawDataPath) { - return DataPathUserFuncs.CxPlatDataPathGetSupportedFeatures(Datapath) | - XDP_CxPlatDataPathGetSupportedFeatures(Datapath->RawDataPath); + return DataPathGetSupportedFeatures(Datapath) | + RawDataPathGetSupportedFeatures(Datapath->RawDataPath); } - return DataPathUserFuncs.CxPlatDataPathGetSupportedFeatures(Datapath); + return DataPathGetSupportedFeatures(Datapath); } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -185,7 +187,7 @@ CxPlatDataPathIsPaddingPreferred( ) { // FIXME: Which flag should be taken? - // return DataPathUserFuncs.CxPlatDataPathIsPaddingPreferred(Datapath); + // return DataPathIsPaddingPreferred(Datapath); return 0; } @@ -200,7 +202,7 @@ CxPlatDataPathGetLocalAddresses( ) { // TODO: XDP doesn't support, could be inlined here - return DataPathUserFuncs.CxPlatDataPathGetLocalAddresses( + return DataPathGetLocalAddresses( Datapath, Addresses, AddressesCount); @@ -217,7 +219,7 @@ CxPlatDataPathGetGatewayAddresses( ) { // TODO: XDP doesn't support, Could be inlined here. - return DataPathUserFuncs.CxPlatDataPathGetGatewayAddresses( + return DataPathGetGatewayAddresses( Datapath, GatewayAddresses, GatewayAddressesCount); @@ -339,7 +341,7 @@ CxPlatSocketCreateUdp( { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - Status = DataPathUserFuncs.CxPlatSocketCreateUdp( + Status = SocketCreateUdp( Datapath, Config, NewSocket); @@ -351,7 +353,7 @@ CxPlatSocketCreateUdp( } if (Datapath->RawDataPath) { - Status = CxPlatInitRawSocket( + Status = RawSocketCreateUdp( Datapath->RawDataPath, Config, CxPlatSocketToRaw(*NewSocket)); @@ -377,7 +379,7 @@ CxPlatSocketCreateTcp( _Out_ CXPLAT_SOCKET** NewSocket ) { - return DataPathUserFuncs.CxPlatSocketCreateTcp( + return SocketCreateTcp( Datapath, LocalAddress, RemoteAddress, @@ -394,7 +396,7 @@ CxPlatSocketCreateTcpListener( _Out_ CXPLAT_SOCKET** NewSocket ) { - return DataPathUserFuncs.CxPlatSocketCreateTcpListener( + return SocketCreateTcpListener( Datapath, LocalAddress, RecvCallbackContext, @@ -408,9 +410,9 @@ CxPlatSocketDelete( ) { if (Socket->Datapath && Socket->Datapath->RawDataPath) { - CxPlatRawSocketDelete(CxPlatSocketToRaw(Socket)); + RawSocketDelete(CxPlatSocketToRaw(Socket)); } - DataPathUserFuncs.CxPlatSocketDelete(Socket); + SocketDelete(Socket); } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -424,7 +426,7 @@ CxPlatSocketUpdateQeo( { if (Socket->Datapath && Socket->Datapath->RawDataPath && !IS_LOOPBACK(Offloads[0].Address)) { - return XDP_CxPlatSocketUpdateQeo(CxPlatSocketToRaw(Socket), Offloads, OffloadCount); + return RawSocketUpdateQeo(CxPlatSocketToRaw(Socket), Offloads, OffloadCount); } return QUIC_STATUS_NOT_SUPPORTED; } @@ -438,7 +440,7 @@ CxPlatSocketGetLocalMtu( CXPLAT_DBG_ASSERT(Socket != NULL); if (Socket->Datapath && Socket->Datapath->RawDataPath && !IS_LOOPBACK(Socket->RemoteAddress)) { - XDP_CxPlatSocketGetLocalMtu(CxPlatSocketToRaw(Socket)); + RawSocketGetLocalMtu(CxPlatSocketToRaw(Socket)); } return Socket->Mtu; } @@ -473,9 +475,9 @@ CxPlatRecvDataReturn( { CXPLAT_DBG_ASSERT(RecvDataChain != NULL); if (RecvDataChain->BufferFrom == CXPLAT_BUFFER_FROM_USER) { - DataPathUserFuncs.CxPlatRecvDataReturn(RecvDataChain); + RecvDataReturn(RecvDataChain); } else if (RecvDataChain->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { - XDP_CxPlatRecvDataReturn(RecvDataChain); + RawRecvDataReturn(RecvDataChain); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -494,12 +496,12 @@ CxPlatSendDataAlloc( if (Socket->Type == 0 && // CXPLAT_SOCKET_UDP Socket->Datapath && Socket->Datapath->RawDataPath && !IS_LOOPBACK(Config->Route->RemoteAddress)) { - SendData = XDP_CxPlatSendDataAlloc(CxPlatSocketToRaw(Socket), Config); + SendData = RawSendDataAlloc(CxPlatSocketToRaw(Socket), Config); if (SendData) { SendBufferFrom(SendData) = CXPLAT_BUFFER_FROM_XDP; } } else { - SendData = DataPathUserFuncs.CxPlatSendDataAlloc(Socket, Config); + SendData = SendDataAlloc(Socket, Config); if (SendData) { SendBufferFrom(SendData) = CXPLAT_BUFFER_FROM_USER; } @@ -514,9 +516,9 @@ CxPlatSendDataFree( ) { if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { - DataPathUserFuncs.CxPlatSendDataFree(SendData); + SendDataFree(SendData); } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { - XDP_CxPlatSendDataFree(SendData); + RawSendDataFree(SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -531,9 +533,9 @@ CxPlatSendDataAllocBuffer( ) { if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { - return DataPathUserFuncs.CxPlatSendDataAllocBuffer(SendData, MaxBufferLength); + return SendDataAllocBuffer(SendData, MaxBufferLength); } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { - return XDP_CxPlatSendDataAllocBuffer(SendData, MaxBufferLength); + return RawSendDataAllocBuffer(SendData, MaxBufferLength); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -548,9 +550,9 @@ CxPlatSendDataFreeBuffer( ) { if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { - DataPathUserFuncs.CxPlatSendDataFreeBuffer(SendData, Buffer); + SendDataFreeBuffer(SendData, Buffer); } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { - XDP_CxPlatSendDataFreeBuffer(SendData, Buffer); + RawSendDataFreeBuffer(SendData, Buffer); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -563,9 +565,9 @@ CxPlatSendDataIsFull( ) { if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { - return DataPathUserFuncs.CxPlatSendDataIsFull(SendData); + return SendDataIsFull(SendData); } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { - return XDP_CxPlatSendDataIsFull(SendData); + return RawSendDataIsFull(SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -581,9 +583,9 @@ CxPlatSocketSend( ) { if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { - return DataPathUserFuncs.CxPlatSocketSend(Socket, Route, SendData); + return SocketSend(Socket, Route, SendData); } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { - return XDP_CxPlatSocketSend(CxPlatSocketToRaw(Socket), Route, SendData); + return RawSocketSend(CxPlatSocketToRaw(Socket), Route, SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -601,16 +603,14 @@ CxPlatDataPathProcessCqe( DATAPATH_IO_SQE* Sqe = CONTAINING_RECORD(CxPlatCqeUserData(Cqe), DATAPATH_IO_SQE, DatapathSqe); if (Sqe->IoType == DATAPATH_XDP_IO_RECV || Sqe->IoType == DATAPATH_XDP_IO_SEND) { - fprintf(stderr, "Recv XDP %d\n", Sqe->IoType); - XDP_CxPlatDataPathProcessCqe(Cqe); + RawDataPathProcessCqe(Cqe); } else { - fprintf(stderr, "Recv Sock %d\n", Sqe->IoType); - DataPathUserFuncs.CxPlatDataPathProcessCqe(Cqe); + DataPathProcessCqe(Cqe); } break; } case CXPLAT_CQE_TYPE_SOCKET_SHUTDOWN: { - XDP_CxPlatDataPathProcessCqe(Cqe); + RawDataPathProcessCqe(Cqe); break; } default: CXPLAT_DBG_ASSERT(FALSE); break; @@ -641,7 +641,7 @@ CxPlatResolveRouteComplete( ) { if (Route->State != RouteResolved) { - XDP_CxPlatResolveRouteComplete(Connection, Route, PhysicalAddress, PathId); + RawResolveRouteComplete(Connection, Route, PhysicalAddress, PathId); } } @@ -660,7 +660,7 @@ CxPlatResolveRoute( { if (Socket->Datapath && Socket->Datapath->RawDataPath && !IS_LOOPBACK(Route->RemoteAddress)) { - return XDP_CxPlatResolveRoute(Socket, Route, PathId, Context, Callback); + return RawResolveRoute(Socket, Route, PathId, Context, Callback); } Route->State = RouteResolved; return QUIC_STATUS_SUCCESS; @@ -674,7 +674,7 @@ CxPlatUpdateRoute( ) { if (!IS_LOOPBACK(SrcRoute->RemoteAddress)) { - XDP_CxPlatUpdateRoute(DstRoute, SrcRoute); + RawUpdateRoute(DstRoute, SrcRoute); } } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 46667ecfe4..5363a7c282 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -17,8 +17,6 @@ #pragma warning(disable:4116) // unnamed type definition in parentheses -#define MANGLE(x) SOCKET_##x - // // This IOCTL allows for creating per-processor sockets for the same UDP port. // This is used to get better parallelization to improve performance. @@ -636,7 +634,7 @@ typedef struct CXPLAT_SEND_DATA { _IRQL_requires_max_(PASSIVE_LEVEL) void -MANGLE(CxPlatSocketDelete)( +SocketDelete( _In_ CXPLAT_SOCKET* Socket ); @@ -1100,7 +1098,7 @@ typedef LONG (WINAPI *FuncRtlGetVersion)(RTL_OSVERSIONINFOW *); _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -MANGLE(CxPlatDataPathInitialize)( +DataPathInitialize( _In_ uint32_t ClientRecvDataLength, _In_opt_ QUIC_EXECUTION_CONFIG* Config, _Out_ CXPLAT_DATAPATH* Datapath @@ -1318,7 +1316,7 @@ CxPlatProcessorContextRelease( _IRQL_requires_max_(PASSIVE_LEVEL) void -MANGLE(CxPlatDataPathUninitialize)( +DataPathUninitialize( _In_ CXPLAT_DATAPATH* Datapath ) { @@ -1334,7 +1332,7 @@ MANGLE(CxPlatDataPathUninitialize)( _IRQL_requires_max_(PASSIVE_LEVEL) void -MANGLE(CxPlatDataPathUpdateConfig)( +DataPathUpdateConfig( _In_ CXPLAT_DATAPATH* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ) @@ -1345,7 +1343,7 @@ MANGLE(CxPlatDataPathUpdateConfig)( _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t -MANGLE(CxPlatDataPathGetSupportedFeatures)( +DataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH* Datapath ) { @@ -1354,7 +1352,7 @@ MANGLE(CxPlatDataPathGetSupportedFeatures)( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -MANGLE(CxPlatDataPathIsPaddingPreferred)( +DataPathIsPaddingPreferred( _In_ CXPLAT_DATAPATH* Datapath ) { @@ -1364,7 +1362,7 @@ MANGLE(CxPlatDataPathIsPaddingPreferred)( _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS -MANGLE(CxPlatDataPathGetLocalAddresses)( +DataPathGetLocalAddresses( _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) CXPLAT_ADAPTER_ADDRESS** Addresses, @@ -1478,7 +1476,7 @@ MANGLE(CxPlatDataPathGetLocalAddresses)( _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS -MANGLE(CxPlatDataPathGetGatewayAddresses)( +DataPathGetGatewayAddresses( _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) QUIC_ADDR** GatewayAddresses, @@ -1585,7 +1583,7 @@ MANGLE(CxPlatDataPathGetGatewayAddresses)( // TODO: DELETE _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -MANGLE(CxPlatDataPathResolveAddress)( +DataPathResolveAddress( _In_ CXPLAT_DATAPATH* Datapath, _In_z_ const char* HostName, _Inout_ QUIC_ADDR* Address @@ -1640,7 +1638,7 @@ CxPlatSocketEnqueueSqe( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -MANGLE(CxPlatSocketCreateUdp)( +SocketCreateUdp( _In_ CXPLAT_DATAPATH* Datapath, _In_ const CXPLAT_UDP_CONFIG* Config, _Out_ CXPLAT_SOCKET** NewSocket @@ -2241,7 +2239,7 @@ MANGLE(CxPlatSocketCreateUdp)( Error: if (RawSocket != NULL) { - MANGLE(CxPlatSocketDelete)(CxPlatRawToSocket(RawSocket)); + SocketDelete(CxPlatRawToSocket(RawSocket)); } return Status; @@ -2500,7 +2498,7 @@ CxPlatSocketCreateTcpInternal( Error: if (RawSocket != NULL) { - MANGLE(CxPlatSocketDelete)(CxPlatRawToSocket(RawSocket)); + SocketDelete(CxPlatRawToSocket(RawSocket)); } return Status; @@ -2508,7 +2506,7 @@ CxPlatSocketCreateTcpInternal( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -MANGLE(CxPlatSocketCreateTcp)( +SocketCreateTcp( _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, @@ -2528,7 +2526,7 @@ MANGLE(CxPlatSocketCreateTcp)( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -MANGLE(CxPlatSocketCreateTcpListener)( +SocketCreateTcpListener( _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ void* RecvCallbackContext, @@ -2742,7 +2740,7 @@ MANGLE(CxPlatSocketCreateTcpListener)( Error: if (RawSocket != NULL) { - MANGLE(CxPlatSocketDelete)(CxPlatRawToSocket(RawSocket)); + SocketDelete(CxPlatRawToSocket(RawSocket)); } return Status; @@ -2750,7 +2748,7 @@ MANGLE(CxPlatSocketCreateTcpListener)( _IRQL_requires_max_(PASSIVE_LEVEL) void -MANGLE(CxPlatSocketDelete)( +SocketDelete( _In_ CXPLAT_SOCKET* Socket ) { @@ -2815,7 +2813,7 @@ CxPlatSocketContextRelease( } } else { if (SocketProc->AcceptSocket != NULL) { - MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET*)SocketProc->AcceptSocket); + SocketDelete((CXPLAT_SOCKET*)SocketProc->AcceptSocket); SocketProc->AcceptSocket = NULL; } } @@ -2915,7 +2913,7 @@ CxPlatSocketContextUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -MANGLE(CxPlatSocketUpdateQeo)( +SocketUpdateQeo( _In_ CXPLAT_SOCKET* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, @@ -2931,7 +2929,7 @@ MANGLE(CxPlatSocketUpdateQeo)( // TODO: remove? _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 -MANGLE(CxPlatSocketGetLocalMtu)( +SocketGetLocalMtu( _In_ CXPLAT_SOCKET* Socket ) { @@ -2942,7 +2940,7 @@ MANGLE(CxPlatSocketGetLocalMtu)( // TODO: remove _IRQL_requires_max_(DISPATCH_LEVEL) void -MANGLE(CxPlatSocketGetLocalAddress)( +SocketGetLocalAddress( _In_ CXPLAT_SOCKET* Socket, _Out_ QUIC_ADDR* Address ) @@ -2954,7 +2952,7 @@ MANGLE(CxPlatSocketGetLocalAddress)( // TODO: remove _IRQL_requires_max_(DISPATCH_LEVEL) void -MANGLE(CxPlatSocketGetRemoteAddress)( +SocketGetRemoteAddress( _In_ CXPLAT_SOCKET* Socket, _Out_ QUIC_ADDR* Address ) @@ -3226,7 +3224,7 @@ CxPlatDataPathSocketProcessAcceptCompletion( Error: if (ListenerSocketProc->AcceptSocket != NULL) { - MANGLE(CxPlatSocketDelete)((CXPLAT_SOCKET*)ListenerSocketProc->AcceptSocket); + SocketDelete((CXPLAT_SOCKET*)ListenerSocketProc->AcceptSocket); ListenerSocketProc->AcceptSocket = NULL; } @@ -4011,7 +4009,7 @@ CxPlatFreeRxIoBlock( _IRQL_requires_max_(DISPATCH_LEVEL) void -MANGLE(CxPlatRecvDataReturn)( +RecvDataReturn( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ) { @@ -4168,7 +4166,7 @@ RioSendDataFree( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* -MANGLE(CxPlatSendDataAlloc)( +SendDataAlloc( _In_ CXPLAT_SOCKET* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) @@ -4221,7 +4219,7 @@ MANGLE(CxPlatSendDataAlloc)( _IRQL_requires_max_(DISPATCH_LEVEL) void -MANGLE(CxPlatSendDataFree)( +SendDataFree( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -4460,7 +4458,7 @@ CxPlatSendDataAllocSegmentBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* -MANGLE(CxPlatSendDataAllocBuffer)( +SendDataAllocBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ) @@ -4483,7 +4481,7 @@ MANGLE(CxPlatSendDataAllocBuffer)( _IRQL_requires_max_(DISPATCH_LEVEL) void -MANGLE(CxPlatSendDataFreeBuffer)( +SendDataFreeBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ) @@ -4514,7 +4512,7 @@ MANGLE(CxPlatSendDataFreeBuffer)( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -MANGLE(CxPlatSendDataIsFull)( +SendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -4546,7 +4544,7 @@ CxPlatSendDataComplete( SendData->TotalSize); } - MANGLE(CxPlatSendDataFree)(SendData); + SendDataFree(SendData); } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -4602,7 +4600,7 @@ CxPlatSocketSendWithRio( SocketProc->Parent, WsaError, "RIOSendEx"); - MANGLE(CxPlatSendDataFree)(SendData); + SendDataFree(SendData); return HRESULT_FROM_WIN32(WsaError); } @@ -4779,7 +4777,7 @@ CxPlatSocketSendEnqueue( _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS -MANGLE(CxPlatSocketSend)( +SocketSend( _In_ CXPLAT_SOCKET* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA* SendData @@ -4857,7 +4855,7 @@ CxPlatDataPathStartRioSends( } void -MANGLE(CxPlatDataPathProcessCqe)( +DataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ) { @@ -4935,18 +4933,8 @@ MANGLE(CxPlatDataPathProcessCqe)( } } -_IRQL_requires_max_(PASSIVE_LEVEL) -void -MANGLE(QuicCopyRouteInfo)( - _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute - ) -{ - *DstRoute = *SrcRoute; -} - void -MANGLE(CxPlatResolveRouteComplete)( +ResolveRouteComplete( _In_ void* Context, _Inout_ CXPLAT_ROUTE* Route, _In_reads_bytes_(6) const uint8_t* PhysicalAddress, @@ -4961,7 +4949,7 @@ MANGLE(CxPlatResolveRouteComplete)( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -MANGLE(CxPlatResolveRoute)( +ResolveRoute( _In_ CXPLAT_SOCKET* Socket, _Inout_ CXPLAT_ROUTE* Route, _In_ uint8_t PathId, @@ -4979,7 +4967,7 @@ MANGLE(CxPlatResolveRoute)( _IRQL_requires_max_(PASSIVE_LEVEL) void -MANGLE(CxPlatUpdateRoute)( +UpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, _In_ CXPLAT_ROUTE* SrcRoute ) @@ -4987,33 +4975,3 @@ MANGLE(CxPlatUpdateRoute)( UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); } - -const struct CXPLAT_DATAPATH_FUNCTIONS DataPathUserFuncs = { - // MANGLE(CxPlatDataPathRecvPacketToRecvData), - // MANGLE(CxPlatDataPathRecvDataToRecvPacket), - MANGLE(CxPlatDataPathInitialize), - MANGLE(CxPlatDataPathUninitialize), - MANGLE(CxPlatDataPathUpdateConfig), - MANGLE(CxPlatDataPathGetSupportedFeatures), - MANGLE(CxPlatDataPathIsPaddingPreferred), - MANGLE(CxPlatDataPathGetLocalAddresses), - MANGLE(CxPlatDataPathGetGatewayAddresses), - MANGLE(CxPlatDataPathResolveAddress), - MANGLE(CxPlatSocketCreateUdp), - MANGLE(CxPlatSocketCreateTcp), - MANGLE(CxPlatSocketCreateTcpListener), - MANGLE(CxPlatSocketDelete), - MANGLE(CxPlatSocketUpdateQeo), - MANGLE(CxPlatSocketGetLocalMtu), - MANGLE(CxPlatSocketGetLocalAddress), - MANGLE(CxPlatSocketGetRemoteAddress), - MANGLE(CxPlatRecvDataReturn), - MANGLE(CxPlatSendDataAlloc), - MANGLE(CxPlatSendDataFree), - MANGLE(CxPlatSendDataAllocBuffer), - MANGLE(CxPlatSendDataFreeBuffer), - MANGLE(CxPlatSendDataIsFull), - MANGLE(CxPlatSocketSend), - MANGLE(CxPlatDataPathProcessCqe), - MANGLE(QuicCopyRouteInfo) - }; diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 81f8e2c13d..5227ad11b5 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -517,6 +517,192 @@ typedef struct CXPLAT_SOCKET { typedef struct CXPLAT_SOCKET_RAW CXPLAT_SOCKET_RAW; +// TODO: move to common area +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +SocketCreateUdp( + _In_ CXPLAT_DATAPATH* DataPath, + _In_ const CXPLAT_UDP_CONFIG* Config, + _Out_ CXPLAT_SOCKET** NewSocket + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +SocketCreateTcp( + _In_ CXPLAT_DATAPATH* Datapath, + _In_opt_ const QUIC_ADDR* LocalAddress, + _In_ const QUIC_ADDR* RemoteAddress, + _In_opt_ void* CallbackContext, + _Out_ CXPLAT_SOCKET** Socket + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +SocketCreateTcpListener( + _In_ CXPLAT_DATAPATH* Datapath, + _In_opt_ const QUIC_ADDR* LocalAddress, + _In_opt_ void* RecvCallbackContext, + _Out_ CXPLAT_SOCKET** NewSocket + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +SocketDelete( + _In_ CXPLAT_SOCKET* Socket + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +DataPathInitialize( + _In_ uint32_t ClientRecvContextLength, + _In_opt_ QUIC_EXECUTION_CONFIG* Config, + _Out_ CXPLAT_DATAPATH* DataPath + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +DataPathUninitialize( + _In_ CXPLAT_DATAPATH* Datapath + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +DataPathUpdateConfig( + _In_ CXPLAT_DATAPATH* Datapath, + _In_ QUIC_EXECUTION_CONFIG* Config + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +uint32_t +DataPathGetSupportedFeatures( + _In_ CXPLAT_DATAPATH* Datapath + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +_Success_(QUIC_SUCCEEDED(return)) +QUIC_STATUS +DataPathGetLocalAddresses( + _In_ CXPLAT_DATAPATH* Datapath, + _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) + CXPLAT_ADAPTER_ADDRESS** Addresses, + _Out_ uint32_t* AddressesCount + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +_Success_(QUIC_SUCCEEDED(return)) +QUIC_STATUS +DataPathGetGatewayAddresses( + _In_ CXPLAT_DATAPATH* Datapath, + _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) + QUIC_ADDR** GatewayAddresses, + _Out_ uint32_t* GatewayAddressesCount + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +SocketUpdateQeo( + _In_ CXPLAT_SOCKET* Socket, + _In_reads_(OffloadCount) + const CXPLAT_QEO_CONNECTION* Offloads, + _In_ uint32_t OffloadCount + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +UINT16 +SocketGetLocalMtu( + _In_ CXPLAT_SOCKET* Socket + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +RecvDataReturn( + _In_opt_ CXPLAT_RECV_DATA* RecvDataChain + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +CXPLAT_SEND_DATA* +SendDataAlloc( + _In_ CXPLAT_SOCKET* Socket, + _Inout_ CXPLAT_SEND_CONFIG* Config + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +SendDataFree( + _In_ CXPLAT_SEND_DATA* SendData + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +QUIC_BUFFER* +SendDataAllocBuffer( + _In_ CXPLAT_SEND_DATA* SendData, + _In_ uint16_t MaxBufferLength + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +SendDataFreeBuffer( + _In_ CXPLAT_SEND_DATA* SendData, + _In_ QUIC_BUFFER* Buffer + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +BOOLEAN +SendDataIsFull( + _In_ CXPLAT_SEND_DATA* SendData + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +QUIC_STATUS +SocketSend( + _In_ CXPLAT_SOCKET* Socket, + _In_ const CXPLAT_ROUTE* Route, + _In_ CXPLAT_SEND_DATA* SendData + ); + +void +ResolveRouteComplete( + _In_ void* Context, + _Inout_ CXPLAT_ROUTE* Route, + _In_reads_bytes_(6) const uint8_t* PhysicalAddress, + _In_ uint8_t PathId + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +ResolveRoute( + _In_ CXPLAT_SOCKET* Sock, + _Inout_ CXPLAT_ROUTE* Route, + _In_ uint8_t PathId, + _In_ void* Context, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback + ); + +void +DataPathProcessCqe( + _In_ CXPLAT_CQE* Cqe + ); + +CXPLAT_RECV_PACKET* +DataPathRecvDataToRecvPacket( + _In_ const CXPLAT_RECV_DATA* const Datagram + ); + +CXPLAT_RECV_DATA* +DataPathRecvPacketToRecvData( + _In_ const CXPLAT_RECV_PACKET* const Context + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +UpdateRoute( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ); + + // TODO: rename CXPLAT_SOCKET* CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket); @@ -525,6 +711,157 @@ CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket); CXPLAT_SOCKET_RAW* CxPlatSocketToRaw(CXPLAT_SOCKET* Socket); +uint32_t +CxPlatGetRawSocketSize (); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +RawSocketCreateUdp( + _In_ CXPLAT_DATAPATH_RAW* DataPath, + _In_ const CXPLAT_UDP_CONFIG* Config, + _Out_ CXPLAT_SOCKET_RAW* NewSocket + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +RawSocketDelete( + _In_ CXPLAT_SOCKET_RAW* Socket + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +RawDataPathInitialize( + _In_ uint32_t ClientRecvContextLength, + _In_opt_ QUIC_EXECUTION_CONFIG* Config, + _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, + _Out_ CXPLAT_DATAPATH_RAW* DataPath + ); + +// TODO: rename as generic for raw + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +RawDataPathUninitialize( + _In_ CXPLAT_DATAPATH_RAW* Datapath + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +RawDataPathUpdateConfig( + _In_ CXPLAT_DATAPATH_RAW* Datapath, + _In_ QUIC_EXECUTION_CONFIG* Config + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +uint32_t +RawDataPathGetSupportedFeatures( + _In_ CXPLAT_DATAPATH_RAW* Datapath + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +RawSocketUpdateQeo( + _In_ CXPLAT_SOCKET_RAW* Socket, + _In_reads_(OffloadCount) + const CXPLAT_QEO_CONNECTION* Offloads, + _In_ uint32_t OffloadCount + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +UINT16 +RawSocketGetLocalMtu( + _In_ CXPLAT_SOCKET_RAW* Socket + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +RawRecvDataReturn( + _In_opt_ CXPLAT_RECV_DATA* RecvDataChain + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +CXPLAT_SEND_DATA* +RawSendDataAlloc( + _In_ CXPLAT_SOCKET_RAW* Socket, + _Inout_ CXPLAT_SEND_CONFIG* Config + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +RawSendDataFree( + _In_ CXPLAT_SEND_DATA* SendData + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +QUIC_BUFFER* +RawSendDataAllocBuffer( + _In_ CXPLAT_SEND_DATA* SendData, + _In_ uint16_t MaxBufferLength + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +RawSendDataFreeBuffer( + _In_ CXPLAT_SEND_DATA* SendData, + _In_ QUIC_BUFFER* Buffer + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +BOOLEAN +RawSendDataIsFull( + _In_ CXPLAT_SEND_DATA* SendData + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +QUIC_STATUS +RawSocketSend( + _In_ CXPLAT_SOCKET_RAW* Socket, + _In_ const CXPLAT_ROUTE* Route, + _In_ CXPLAT_SEND_DATA* SendData + ); + +void +RawResolveRouteComplete( + _In_ void* Context, + _Inout_ CXPLAT_ROUTE* Route, + _In_reads_bytes_(6) const uint8_t* PhysicalAddress, + _In_ uint8_t PathId + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +RawResolveRoute( + _In_ CXPLAT_SOCKET* Sock, + _Inout_ CXPLAT_ROUTE* Route, + _In_ uint8_t PathId, + _In_ void* Context, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback + ); + +void +RawDataPathProcessCqe( + _In_ CXPLAT_CQE* Cqe + ); + +CXPLAT_RECV_PACKET* +RawDataPathRecvDataToRecvPacket( + _In_ const CXPLAT_RECV_DATA* const Datagram + ); + +CXPLAT_RECV_DATA* +RawDataPathRecvPacketToRecvData( + _In_ const CXPLAT_RECV_PACKET* const Context + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +RawUpdateRoute( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ); + #elif defined(CX_PLATFORM_LINUX) || defined(CX_PLATFORM_DARWIN) typedef struct CX_PLATFORM { @@ -666,17 +1003,6 @@ CxPlatDataPathPoll( _Out_ BOOLEAN* RemoveFromPolling ); -typedef struct CXPLAT_DATAPATH_PROC CXPLAT_DATAPATH_PROC; - -uint32_t -CxPlatGetRawSocketSize (); - -// CXPLAT_SOCKET* -// CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket); - -// CXPLAT_SOCKET_RAW* -// CxPlatSocketToRaw(CXPLAT_SOCKET* Socket); - // // Queries the raw datapath stack for the total size needed to allocate the // datapath structure. From ef10a2756796c4ca735e673152043e1619451e60 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 23 Aug 2023 22:59:48 -0700 Subject: [PATCH 47/87] remove unnecessary change in test --- src/test/bin/quic_gtest.cpp | 1 + src/test/lib/ApiTest.cpp | 23 ----------------------- src/test/lib/HandshakeTest.cpp | 4 ---- src/test/lib/MtuTest.cpp | 4 ++-- 4 files changed, 3 insertions(+), 29 deletions(-) diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index aa6f404365..0c80c4db31 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -1747,6 +1747,7 @@ TEST_P(WithSend0RttArgs1, Send0Rtt) { return; } #endif + TestLoggerT Logger("Send0Rtt", GetParam()); if (TestingKernelMode) { QUIC_RUN_CONNECT_AND_PING_PARAMS Params = { diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index d3b18aa4fd..5fddd7f06e 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -2155,28 +2155,6 @@ void QuicTestStatefulGlobalSetParam() &ActualFeatures)); TEST_NOT_EQUAL(ActualFeatures, 0); } - - { - TestScopeLogger LogScope1("Get QUIC_PARAM_GLOBAL_DATAPATH_FEATURES after Datapath is made (MsQuicLib.Datapath)"); - uint32_t Length = 0; - TEST_QUIC_STATUS( - QUIC_STATUS_BUFFER_TOO_SMALL, - MsQuic->GetParam( - nullptr, - QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, - &Length, - nullptr)); - TEST_EQUAL(Length, sizeof(uint32_t)); - - uint32_t ActualFeatures = 0; - TEST_QUIC_SUCCEEDED( - MsQuic->GetParam( - nullptr, - QUIC_PARAM_GLOBAL_DATAPATH_FEATURES, - &Length, - &ActualFeatures)); - TEST_NOT_EQUAL(ActualFeatures, 0); - } } void QuicTestGlobalParam() @@ -3238,7 +3216,6 @@ void QuicTest_QUIC_PARAM_CONN_QUIC_VERSION(MsQuicRegistration& Registration, MsQ TestScopeLogger LogScope0("QUIC_PARAM_CONN_QUIC_VERSION"); MsQuicConnection Connection(Registration); TEST_QUIC_SUCCEEDED(Connection.GetInitStatus()); - // // SetParam // diff --git a/src/test/lib/HandshakeTest.cpp b/src/test/lib/HandshakeTest.cpp index 5d06bb6ac8..62a6c927f8 100644 --- a/src/test/lib/HandshakeTest.cpp +++ b/src/test/lib/HandshakeTest.cpp @@ -14,10 +14,6 @@ #include "HandshakeTest.cpp.clog.h" #endif -#if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) -extern bool UseQTIP; -#endif - QUIC_TEST_DATAPATH_HOOKS DatapathHooks::FuncTable = { DatapathHooks::CreateCallback, DatapathHooks::GetLocalAddressCallback, diff --git a/src/test/lib/MtuTest.cpp b/src/test/lib/MtuTest.cpp index bf126ec22d..07f4e1eea2 100644 --- a/src/test/lib/MtuTest.cpp +++ b/src/test/lib/MtuTest.cpp @@ -51,8 +51,6 @@ struct ResetSettings { void QuicTestMtuSettings() { - MsQuicRegistration Registration(true); - TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) const uint16_t DefaultMaximumMtu = UseQTIP ? 1488 : 1500; // reserve 12B for TCP header #else @@ -81,6 +79,8 @@ QuicTestMtuSettings() TEST_EQUAL(NewSettings.MaximumMtu, UpdatedSettings.MaximumMtu); } + MsQuicRegistration Registration(true); + TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); MsQuicAlpn Alpn("MsQuicTest"); { { From 87ae517e3654d4b57e6544018b08da40fa6785d3 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Thu, 24 Aug 2023 18:17:46 -0700 Subject: [PATCH 48/87] fix comments --- src/core/binding.c | 2 -- src/core/binding.h | 7 ------- src/core/path.h | 7 +++++++ src/inc/quic_datapath.h | 26 +++++++------------------ src/platform/CMakeLists.txt | 10 +++++++++- src/platform/datapath_raw_socket.c | 4 ++-- src/platform/datapath_raw_xdp_win.c | 2 +- src/platform/datapath_win.c | 30 ++++++++++++++--------------- src/platform/datapath_winuser.c | 4 ++-- src/platform/platform_internal.h | 24 +---------------------- src/platform/platform_worker.c | 2 ++ src/test/bin/quic_gtest.cpp | 3 +++ src/test/bin/quic_gtest.h | 1 - src/test/lib/DataTest.cpp | 1 - src/test/lib/HandshakeTest.cpp | 4 ++-- src/test/lib/MtuTest.cpp | 1 + 16 files changed, 52 insertions(+), 76 deletions(-) diff --git a/src/core/binding.c b/src/core/binding.c index f3d752edfd..b8bd787f9c 100644 --- a/src/core/binding.c +++ b/src/core/binding.c @@ -1150,7 +1150,6 @@ QuicBindingPreprocessDatagram( CxPlatZeroMemory(&Packet->PacketNumber, sizeof(QUIC_RX_PACKET) - sizeof(uint64_t)); Packet->AvailBuffer = Datagram->Buffer; Packet->AvailBufferLength = Datagram->BufferLength; - Packet->BufferFrom = Datagram->BufferFrom; *ReleaseDatagram = TRUE; @@ -1667,7 +1666,6 @@ QuicBindingReceive( Packet->SourceCidLen = 0; Packet->KeyType = QUIC_PACKET_KEY_INITIAL; Packet->Flags = 0; - Packet->BufferFrom = Datagram->BufferFrom; CXPLAT_DBG_ASSERT(Packet->PacketId != 0); QuicTraceEvent( diff --git a/src/core/binding.h b/src/core/binding.h index 87795e2fdc..ade9ff76d6 100644 --- a/src/core/binding.h +++ b/src/core/binding.h @@ -160,13 +160,6 @@ typedef struct QUIC_RX_PACKET { // Flag indicating the packet contained a non-probing frame. // BOOLEAN HasNonProbingFrame : 1; - - // // - // // - // // - // uint8_t BufferFrom : 2; - - // uint8_t Reserved : 2; }; }; diff --git a/src/core/path.h b/src/core/path.h index 158f6fc14f..d6dc6f07df 100644 --- a/src/core/path.h +++ b/src/core/path.h @@ -302,6 +302,13 @@ QuicConnGetPathForDatagram( _In_ const CXPLAT_RECV_DATA* Datagram ); +_IRQL_requires_max_(PASSIVE_LEVEL) +void +QuicCopyRouteInfo( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ); + // // Plumbs new or removes existing QUIC encryption offload information. // diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 933175e4ff..c95e104056 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -138,18 +138,13 @@ typedef struct CXPLAT_SOCKET CXPLAT_SOCKET; typedef struct CXPLAT_UDP_CONFIG CXPLAT_UDP_CONFIG; -// -// Can be defined to whatever the client needs. -// -typedef struct CXPLAT_RECV_PACKET CXPLAT_RECV_PACKET; - -typedef enum CXPLAT_BUFFER_FROM { - CXPLAT_BUFFER_FROM_UNKNOWN = 0, - CXPLAT_BUFFER_FROM_USER, - CXPLAT_BUFFER_FROM_KERNEL, - CXPLAT_BUFFER_FROM_XDP, +typedef enum CXPLAT_DATAPATH_TYPE { + CXPLAT_DATAPATH_TYPE_UNKNOWN = 0, + CXPLAT_DATAPATH_TYPE_USER, + CXPLAT_DATAPATH_TYPE_KERNEL, + CXPLAT_DATAPATH_TYPE_XDP, // DPDK? -} CXPLAT_BUFFER_FROM; +} CXPLAT_DATAPATH_TYPE; // // Structure that maintains the 'per send' context. @@ -244,7 +239,7 @@ typedef struct CXPLAT_RECV_DATA { // uint16_t Allocated : 1; // Used for debugging. Set to FALSE on free. uint16_t QueuedOnConnection : 1; // Used for debugging. - uint16_t BufferFrom : 2; + uint16_t DatapathType : 2; uint16_t Reserved : 4; uint16_t ReservedEx : 8; @@ -779,13 +774,6 @@ CxPlatResolveRouteComplete( _In_ uint8_t PathId ); -_IRQL_requires_max_(PASSIVE_LEVEL) -void -QuicCopyRouteInfo( - _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute - ); - // // Tries to resolve route and neighbor for the given destination address. // diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt index ded90c0519..db46830da3 100644 --- a/src/platform/CMakeLists.txt +++ b/src/platform/CMakeLists.txt @@ -12,7 +12,15 @@ endif() set(SOURCES crypt.c hashtable.c pcp.c platform_worker.c toeplitz.c) if("${CX_PLATFORM}" STREQUAL "windows") - set(SOURCES ${SOURCES} platform_winuser.c storage_winuser.c datapath_win.c datapath_winuser.c datapath_raw_win.c datapath_raw_socket.c datapath_raw_socket_win.c datapath_raw_xdp_win.c) + set(SOURCES ${SOURCES} + platform_winuser.c + storage_winuser.c + datapath_win.c + datapath_winuser.c + datapath_raw_win.c + datapath_raw_socket.c + datapath_raw_socket_win.c + datapath_raw_xdp_win.c) else() set(SOURCES ${SOURCES} inline.c platform_posix.c storage_posix.c cgroup.c) if(CX_PLATFORM STREQUAL "linux" AND NOT CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index f49c4b695d..ec8456af9d 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -141,7 +141,7 @@ CxPlatDpRawParseUdp( Packet->Buffer = (uint8_t*)Udp->Data; Packet->BufferLength = QuicNetByteSwapShort(Udp->Length) - sizeof(UDP_HEADER); - Packet->BufferFrom = CXPLAT_BUFFER_FROM_XDP; + Packet->DatapathType = CXPLAT_DATAPATH_TYPE_XDP; } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -211,7 +211,7 @@ CxPlatDpRawParseTcp( Packet->Buffer = (uint8_t*)(Tcp) + HeaderLength; Packet->BufferLength = Length; - Packet->BufferFrom = CXPLAT_BUFFER_FROM_XDP; + Packet->DatapathType = CXPLAT_DATAPATH_TYPE_XDP; Packet->ReservedEx = HeaderLength; } diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 651af8828c..9ed199a7e9 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -1584,7 +1584,7 @@ CxPlatXdpRx( if (Packet->RecvData.Buffer) { Packet->RecvData.Allocated = TRUE; - Packet->RecvData.BufferFrom = CXPLAT_BUFFER_FROM_XDP; + Packet->RecvData.DatapathType = CXPLAT_DATAPATH_TYPE_XDP; Buffers[PacketCount++] = &Packet->RecvData; } else { CxPlatListPushEntry(&Queue->PartitionRxPool, (CXPLAT_SLIST_ENTRY*)Packet); diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 6b6afe191d..167277e053 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -28,7 +28,7 @@ #define RawDataPathAvailable(Datapath) ((Datapath)->RawDataPath != NULL) #define RawSocketAvailable(Socket) ((Socket)->Datapath && RawDataPathAvailable((Socket)->Datapath)) -#define SendBufferFrom(SendData) ((CXPLAT_SEND_DATA_COMMON*)(SendData))->BufferFrom +#define DatapathType(SendData) ((CXPLAT_SEND_DATA_COMMON*)(SendData))->DatapathType _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS @@ -474,9 +474,9 @@ CxPlatRecvDataReturn( ) { CXPLAT_DBG_ASSERT(RecvDataChain != NULL); - if (RecvDataChain->BufferFrom == CXPLAT_BUFFER_FROM_USER) { + if (RecvDataChain->DatapathType == CXPLAT_DATAPATH_TYPE_USER) { RecvDataReturn(RecvDataChain); - } else if (RecvDataChain->BufferFrom == CXPLAT_BUFFER_FROM_XDP) { + } else if (RecvDataChain->DatapathType == CXPLAT_DATAPATH_TYPE_XDP) { RawRecvDataReturn(RecvDataChain); } else { CXPLAT_DBG_ASSERT(FALSE); @@ -498,12 +498,12 @@ CxPlatSendDataAlloc( !IS_LOOPBACK(Config->Route->RemoteAddress)) { SendData = RawSendDataAlloc(CxPlatSocketToRaw(Socket), Config); if (SendData) { - SendBufferFrom(SendData) = CXPLAT_BUFFER_FROM_XDP; + DatapathType(SendData) = CXPLAT_DATAPATH_TYPE_XDP; } } else { SendData = SendDataAlloc(Socket, Config); if (SendData) { - SendBufferFrom(SendData) = CXPLAT_BUFFER_FROM_USER; + DatapathType(SendData) = CXPLAT_DATAPATH_TYPE_USER; } } return SendData; @@ -515,9 +515,9 @@ CxPlatSendDataFree( _In_ CXPLAT_SEND_DATA* SendData ) { - if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { + if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { SendDataFree(SendData); - } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { + } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { RawSendDataFree(SendData); } else { CXPLAT_DBG_ASSERT(FALSE); @@ -532,9 +532,9 @@ CxPlatSendDataAllocBuffer( _In_ uint16_t MaxBufferLength ) { - if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { + if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { return SendDataAllocBuffer(SendData, MaxBufferLength); - } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { + } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { return RawSendDataAllocBuffer(SendData, MaxBufferLength); } else { CXPLAT_DBG_ASSERT(FALSE); @@ -549,9 +549,9 @@ CxPlatSendDataFreeBuffer( _In_ QUIC_BUFFER* Buffer ) { - if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { + if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { SendDataFreeBuffer(SendData, Buffer); - } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { + } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { RawSendDataFreeBuffer(SendData, Buffer); } else { CXPLAT_DBG_ASSERT(FALSE); @@ -564,9 +564,9 @@ CxPlatSendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ) { - if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { + if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { return SendDataIsFull(SendData); - } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { + } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { return RawSendDataIsFull(SendData); } else { CXPLAT_DBG_ASSERT(FALSE); @@ -582,9 +582,9 @@ CxPlatSocketSend( _In_ CXPLAT_SEND_DATA* SendData ) { - if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_USER) { + if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { return SocketSend(Socket, Route, SendData); - } else if (SendBufferFrom(SendData) == CXPLAT_BUFFER_FROM_XDP) { + } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { return RawSocketSend(CxPlatSocketToRaw(Socket), Route, SendData); } else { CXPLAT_DBG_ASSERT(FALSE); diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 5363a7c282..9021f22784 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -3712,7 +3712,7 @@ CxPlatDataPathUdpRecvComplete( SocketProc->DatapathProc->PartitionIndex % SocketProc->DatapathProc->Datapath->PartitionCount; Datagram->TypeOfService = (uint8_t)ECN; Datagram->Allocated = TRUE; - Datagram->BufferFrom = CXPLAT_BUFFER_FROM_USER; + Datagram->DatapathType = CXPLAT_DATAPATH_TYPE_USER; Datagram->QueuedOnConnection = FALSE; RecvPayload += MessageLength; @@ -3970,7 +3970,7 @@ CxPlatDataPathTcpRecvComplete( Data->PartitionIndex = SocketProc->DatapathProc->PartitionIndex; Data->TypeOfService = 0; Data->Allocated = TRUE; - Data->BufferFrom = CXPLAT_BUFFER_FROM_USER; + Data->DatapathType = CXPLAT_DATAPATH_TYPE_USER; Data->QueuedOnConnection = FALSE; IoBlock->ReferenceCount++; IoBlock = NULL; diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 5227ad11b5..c2b833f2c2 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -87,7 +87,7 @@ typedef struct CXPLAT_SOCKET_COMMON { } CXPLAT_SOCKET_COMMON; typedef struct CXPLAT_SEND_DATA_COMMON { - uint16_t BufferFrom : 2; + uint16_t DatapathType : 2; // // The type of ECN markings needed for send. @@ -685,16 +685,6 @@ DataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ); -CXPLAT_RECV_PACKET* -DataPathRecvDataToRecvPacket( - _In_ const CXPLAT_RECV_DATA* const Datagram - ); - -CXPLAT_RECV_DATA* -DataPathRecvPacketToRecvData( - _In_ const CXPLAT_RECV_PACKET* const Context - ); - _IRQL_requires_max_(PASSIVE_LEVEL) void UpdateRoute( @@ -737,8 +727,6 @@ RawDataPathInitialize( _Out_ CXPLAT_DATAPATH_RAW* DataPath ); -// TODO: rename as generic for raw - _IRQL_requires_max_(PASSIVE_LEVEL) void RawDataPathUninitialize( @@ -845,16 +833,6 @@ RawDataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ); -CXPLAT_RECV_PACKET* -RawDataPathRecvDataToRecvPacket( - _In_ const CXPLAT_RECV_DATA* const Datagram - ); - -CXPLAT_RECV_DATA* -RawDataPathRecvPacketToRecvData( - _In_ const CXPLAT_RECV_PACKET* const Context - ); - _IRQL_requires_max_(PASSIVE_LEVEL) void RawUpdateRoute( diff --git a/src/platform/platform_worker.c b/src/platform/platform_worker.c index c17d453696..bb3bec5a07 100644 --- a/src/platform/platform_worker.c +++ b/src/platform/platform_worker.c @@ -212,6 +212,7 @@ CxPlatWorkersLazyStart( CxPlatRundownInitialize(&CxPlatWorkerRundown); CxPlatLockRelease(&CxPlatWorkerLock); + return TRUE; Error: @@ -468,6 +469,7 @@ CXPLAT_THREAD_CALLBACK(CxPlatWorkerThread, Context) { CXPLAT_WORKER* Worker = (CXPLAT_WORKER*)Context; CXPLAT_DBG_ASSERT(Worker != NULL); + QuicTraceLogInfo( PlatformWorkerThreadStart, "[ lib][%p] Worker start", diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 0c80c4db31..ba06c23471 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -843,6 +843,9 @@ TEST_P(WithFamilyArgs, InterfaceBinding) { if (TestingKernelMode) { ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_INTERFACE_BINDING, GetParam().Family)); } else { + if (UseDuoNic) { + GTEST_SKIP_("DuoNIC is not supported"); + } QuicTestInterfaceBinding(GetParam().Family); } } diff --git a/src/test/bin/quic_gtest.h b/src/test/bin/quic_gtest.h index 515aec6341..63599907fa 100644 --- a/src/test/bin/quic_gtest.h +++ b/src/test/bin/quic_gtest.h @@ -8,7 +8,6 @@ #define QUIC_TEST_APIS 1 #include "quic_platform.h" -#include "quic_datapath.h" #include "MsQuicTests.h" #include "msquichelper.h" #include "quic_trace.h" diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index 9ba2436e5b..8de2558550 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -19,7 +19,6 @@ static bool UseQTIP = false; #elif defined(QUIC_API_ENABLE_PREVIEW_FEATURES) extern bool UseQTIP; #endif -extern uint64_t LARGE_SEND_SIZE; /* Helper function to estimate a maximum timeout for a test with a diff --git a/src/test/lib/HandshakeTest.cpp b/src/test/lib/HandshakeTest.cpp index 62a6c927f8..8b8504f096 100644 --- a/src/test/lib/HandshakeTest.cpp +++ b/src/test/lib/HandshakeTest.cpp @@ -3154,7 +3154,7 @@ GetTestInterfaceIndices( { CXPLAT_ADAPTER_ADDRESS* Addresses = nullptr; uint32_t AddressesCount = 0; - if (UseDuoNic || CxPlatDataPathGetLocalAddresses(nullptr, &Addresses, &AddressesCount) == QUIC_STATUS_NOT_SUPPORTED) { + if (CxPlatDataPathGetLocalAddresses(nullptr, &Addresses, &AddressesCount) == QUIC_STATUS_NOT_SUPPORTED) { return false; // Not currently supported by this platform. } @@ -3207,7 +3207,7 @@ QuicTestInterfaceBinding( MsQuicConnection Connection1(Registration); TEST_QUIC_SUCCEEDED(Connection1.GetInitStatus()); TEST_QUIC_SUCCEEDED(Connection1.SetLocalInterface(LoopbackInterfaceIndex)); - TEST_QUIC_SUCCEEDED(Connection1.Start(ClientConfiguration, ServerLocalAddr.GetFamily(), QUIC_LOCALHOST_FOR_AF(ServerLocalAddr.GetFamily()), ServerLocalAddr.GetPort())); + TEST_QUIC_SUCCEEDED(Connection1.Start(ClientConfiguration, ServerLocalAddr.GetFamily(), QUIC_TEST_LOOPBACK_FOR_AF(ServerLocalAddr.GetFamily()), ServerLocalAddr.GetPort())); TEST_TRUE(Connection1.HandshakeCompleteEvent.WaitTimeout(TestWaitTimeout)); TEST_TRUE(Connection1.HandshakeComplete); diff --git a/src/test/lib/MtuTest.cpp b/src/test/lib/MtuTest.cpp index 07f4e1eea2..4ca64cfb21 100644 --- a/src/test/lib/MtuTest.cpp +++ b/src/test/lib/MtuTest.cpp @@ -56,6 +56,7 @@ QuicTestMtuSettings() #else const uint16_t DefaultMaximumMtu = 1500; #endif + { // // Test setting on library works From 750dd03edde74b3b2eb50ed8791cd73145d03711 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Sun, 27 Aug 2023 18:28:19 -0700 Subject: [PATCH 49/87] cleanup --- src/inc/quic_datapath.h | 6 - src/platform/datapath_raw.h | 11 +- src/platform/datapath_raw_win.c | 4 - src/platform/datapath_raw_xdp.h | 9 - src/platform/datapath_win.c | 2 +- src/platform/datapath_winuser.c | 376 ++----------------------------- src/platform/platform_internal.h | 7 + src/test/lib/MtuTest.cpp | 2 +- 8 files changed, 26 insertions(+), 391 deletions(-) diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index c95e104056..b7d337d9cc 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -125,19 +125,13 @@ PacketSizeFromUdpPayloadSize( // The top level datapath handle type. // typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; - - typedef struct CXPLAT_DATAPATH_RAW CXPLAT_DATAPATH_RAW; -typedef struct CXPLAT_DATAPATH CXPLAT_DATAPATH; // // Represents a UDP or TCP abstraction. // -typedef struct CXPLAT_SOCKET_RAW CXPLAT_SOCKET_RAW; typedef struct CXPLAT_SOCKET CXPLAT_SOCKET; -typedef struct CXPLAT_UDP_CONFIG CXPLAT_UDP_CONFIG; - typedef enum CXPLAT_DATAPATH_TYPE { CXPLAT_DATAPATH_TYPE_UNKNOWN = 0, CXPLAT_DATAPATH_TYPE_USER, diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index 087f7e83c2..c01b836163 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -45,7 +45,6 @@ typedef struct QUIC_CACHEALIGN CXPLAT_ROUTE_RESOLUTION_WORKER { typedef struct CXPLAT_DATAPATH_RAW { // FIXME: want to inline CXPLAT_DATAPATH const CXPLAT_DATAPATH *ParentDataPath; - // CXPLAT_UDP_DATAPATH_CALLBACKS UdpHandlers; // common? CXPLAT_SOCKET_POOL SocketPool; @@ -59,7 +58,6 @@ typedef struct CXPLAT_DATAPATH_RAW { #endif BOOLEAN UseTcp; - // CXPLAT_DATAPATH; } CXPLAT_DATAPATH_RAW; #define ETH_MAC_ADDR_LEN 6 @@ -240,20 +238,13 @@ CxPlatDpRawTxEnqueue( // Raw Socket Interface // -// typedef struct CXPLAT_SOCKET_INTERNAL { -// CXPLAT_SOCKET; -// } CXPLAT_SOCKET_INTERNAL; - typedef struct CXPLAT_SOCKET_RAW { - // const CXPLAT_SOCKET_BASE* ParentSocket; CXPLAT_HASHTABLE_ENTRY Entry; CXPLAT_RUNDOWN_REF Rundown; CXPLAT_DATAPATH_RAW* RawDatapath; // - SOCKET AuxSocket; // TODO: remove? + SOCKET AuxSocket; void* CallbackContext; - // QUIC_ADDR LocalAddress; // - // QUIC_ADDR RemoteAddress; // BOOLEAN Wildcard; // Using a wildcard local address. Optimization // to avoid always reading LocalAddress. BOOLEAN Connected; // Bound to a remote address diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index a449b1a669..4fd60b1561 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -158,7 +158,6 @@ RawDataPathInitialize( if (SockPoolInitialized) { CxPlatSockPoolUninitialize(&DataPath->SocketPool); } - // CXPLAT_FREE(DataPath, QUIC_POOL_DATAPATH); CxPlatRundownRelease(&CxPlatWorkerRundown); } } @@ -258,17 +257,14 @@ QUIC_STATUS RawSocketCreateUdp( _In_ CXPLAT_DATAPATH_RAW* Raw, _In_ const CXPLAT_UDP_CONFIG* Config, - // _Out_ CXPLAT_SOCKET_BASE** NewSocket _Out_ CXPLAT_SOCKET_RAW* Socket ) { - // TODO: remove Socket->Remote/LocalAddress. Use (*NewSocket)->Remote/LocalAddress instead. #pragma warning(push) #pragma warning(disable:6001) // Using uninitialized memory CXPLAT_DBG_ASSERT(Socket != NULL); QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - // CxPlatZeroMemory(Socket, sizeof(CXPLAT_SOCKET_RAW)); CxPlatRundownInitialize(&Socket->Rundown); Socket->RawDatapath = Raw; Socket->CallbackContext = Config->CallbackContext; diff --git a/src/platform/datapath_raw_xdp.h b/src/platform/datapath_raw_xdp.h index 56ed7db3a3..d7d6532bde 100644 --- a/src/platform/datapath_raw_xdp.h +++ b/src/platform/datapath_raw_xdp.h @@ -25,15 +25,6 @@ typedef struct XDP_PARTITION XDP_PARTITION; typedef struct XDP_DATAPATH XDP_DATAPATH; typedef struct XDP_QUEUE XDP_QUEUE; -// // -// // Type of IO. -// // -// typedef enum DATAPATH_XDP_IO_TYPE { -// DATAPATH_XDP_IO_SIGNATURE = 'XDPD', -// DATAPATH_XDP_IO_RECV = DATAPATH_XDP_IO_SIGNATURE + 1, -// DATAPATH_XDP_IO_SEND = DATAPATH_XDP_IO_SIGNATURE + 2 -// } DATAPATH_XDP_IO_TYPE; - // // IO header for SQE->CQE based completions. // diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 167277e053..25de2d133b 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -493,7 +493,7 @@ CxPlatSendDataAlloc( { CXPLAT_SEND_DATA* SendData = NULL; // TODO: fallback? - if (Socket->Type == 0 && // CXPLAT_SOCKET_UDP + if (Socket->Type == CXPLAT_SOCKET_UDP && Socket->Datapath && Socket->Datapath->RawDataPath && !IS_LOOPBACK(Config->Route->RemoteAddress)) { SendData = RawSendDataAlloc(CxPlatSocketToRaw(Socket), Config); diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 9021f22784..afb6d3ea9e 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -104,15 +104,6 @@ CXPLAT_STATIC_ASSERT( ErrorCode == WSAECONNRESET \ ) -// typedef struct CXPLAT_DATAPATH_PARTITION CXPLAT_DATAPATH_PARTITION; // Per-processor datapath state. -// typedef struct CXPLAT_SOCKET_PROC CXPLAT_SOCKET_PROC; // Per-processor socket state. - -typedef enum CXPLAT_SOCKET_TYPE { - CXPLAT_SOCKET_UDP = 0, - CXPLAT_SOCKET_TCP_LISTENER = 1, - CXPLAT_SOCKET_TCP = 2, - CXPLAT_SOCKET_TCP_SERVER = 3 -} CXPLAT_SOCKET_TYPE; // // Contains all the info for a single RX IO operation. Multiple RX packets may @@ -296,341 +287,6 @@ typedef struct CXPLAT_SEND_DATA { QUIC_ADDR MappedRemoteAddress; } CXPLAT_SEND_DATA; -// // -// // Per-processor socket state. -// // -// typedef struct QUIC_CACHEALIGN CXPLAT_SOCKET_PROC { -// // -// // Used to synchronize clean up. -// // -// CXPLAT_REF_COUNT RefCount; - -// // -// // Submission queue event for IO completion -// // -// DATAPATH_IO_SQE IoSqe; - -// // -// // Submission queue event for RIO IO completion -// // -// DATAPATH_IO_SQE RioSqe; - -// // -// // The datapath per-processor context. -// // -// CXPLAT_DATAPATH_PARTITION* DatapathProc; - -// // -// // Parent CXPLAT_SOCKET. -// // -// CXPLAT_SOCKET* Parent; - -// // -// // Socket handle to the networking stack. -// // -// SOCKET Socket; - -// // -// // Rundown for synchronizing upcalls to the app and downcalls on the Socket. -// // -// CXPLAT_RUNDOWN_REF RundownRef; - -// // -// // Flag indicates the socket started processing IO. -// // -// BOOLEAN IoStarted : 1; - -// // -// // Flag indicates a persistent out-of-memory failure for the receive path. -// // -// BOOLEAN RecvFailure : 1; - -// // -// // Debug Flags -// // -// uint8_t Uninitialized : 1; -// uint8_t Freed : 1; - -// // -// // The set of parameters/state passed to WsaRecvMsg for the IP stack to -// // populate to indicate the result of the receive. -// // - -// union { -// // -// // Normal TCP/UDP socket data -// // -// struct { -// RIO_CQ RioCq; -// RIO_RQ RioRq; -// ULONG RioRecvCount; -// ULONG RioSendCount; -// CXPLAT_LIST_ENTRY RioSendOverflow; -// BOOLEAN RioNotifyArmed; -// }; -// // -// // TCP Listener socket data -// // -// struct { -// CXPLAT_SOCKET* AcceptSocket; -// char AcceptAddrSpace[ -// sizeof(SOCKADDR_INET) + 16 + -// sizeof(SOCKADDR_INET) + 16 -// ]; -// }; -// }; -// } CXPLAT_SOCKET_PROC; - -// // -// // Per-port state. Multiple sockets are created on each port. -// // -// typedef struct CXPLAT_SOCKET { - -// // -// // Parent datapath. -// // -// CXPLAT_DATAPATH* Datapath; - -// // -// // Client context pointer. -// // -// void *ClientContext; - -// // -// // The local address and port. -// // -// SOCKADDR_INET LocalAddress; - -// // -// // The remote address and port. -// // -// SOCKADDR_INET RemoteAddress; - -// // -// // Synchronization mechanism for cleanup. -// // -// CXPLAT_REF_COUNT RefCount; - -// // -// // The size of a receive buffer's payload. -// // -// uint32_t RecvBufLen; - -// // -// // The local interface's MTU. -// // -// uint16_t Mtu; - -// // -// // Socket type. -// // -// uint16_t Type : 2; // CXPLAT_SOCKET_TYPE - -// // -// // Flag indicates the socket has more than one socket, affinitized to all -// // the processors. -// // -// uint16_t NumPerProcessorSockets : 1; - -// // -// // Flag indicates the socket has a default remote destination. -// // -// uint16_t HasFixedRemoteAddress : 1; - -// // -// // Flag indicates the socket indicated a disconnect event. -// // -// uint16_t DisconnectIndicated : 1; - -// // -// // Flag indicates the binding is being used for PCP. -// // -// uint16_t PcpBinding : 1; - -// // -// // Flag indicates the socket is using RIO instead of traditional Winsock. -// // -// uint16_t UseRio : 1; - -// // -// // Debug flags. -// // -// uint16_t Uninitialized : 1; -// uint16_t Freed : 1; - -// // -// // Per-processor socket contexts. -// // -// CXPLAT_SOCKET_PROC PerProcSockets[0]; - -// } CXPLAT_SOCKET; - -// // -// // Represents a single IO completion port and thread for processing work that is -// // completed on a single processor. -// // -// typedef struct QUIC_CACHEALIGN CXPLAT_DATAPATH_PARTITION { - -// // -// // Parent datapath. -// // -// CXPLAT_DATAPATH* Datapath; - -// // -// // Event queue used for processing work. -// // -// CXPLAT_EVENTQ* EventQ; - -// // -// // Used to synchronize clean up. -// // -// CXPLAT_REF_COUNT RefCount; - -// // -// // The index into the execution config processor array. -// // -// uint16_t PartitionIndex; - -// // -// // Debug flags -// // -// uint8_t Uninitialized : 1; - -// // -// // Pool of send contexts to be shared by all sockets on this core. -// // -// CXPLAT_POOL SendDataPool; - -// // -// // Pool of send contexts to be shared by all RIO sockets on this core. -// // -// CXPLAT_POOL RioSendDataPool; - -// // -// // Pool of send buffers to be shared by all sockets on this core. -// // -// CXPLAT_POOL SendBufferPool; - -// // -// // Pool of large segmented send buffers to be shared by all sockets on this -// // core. -// // -// CXPLAT_POOL LargeSendBufferPool; - -// // -// // Pool of send buffers to be shared by all RIO sockets on this core. -// // -// CXPLAT_POOL RioSendBufferPool; - -// // -// // Pool of large segmented send buffers to be shared by all RIO sockets on -// // this core. -// // -// CXPLAT_POOL RioLargeSendBufferPool; - -// // -// // Pool of receive datagram contexts and buffers to be shared by all sockets -// // on this core. -// // -// CXPLAT_POOL RecvDatagramPool; - -// // -// // Pool of RIO receive datagram contexts and buffers to be shared by all -// // RIO sockets on this core. -// // -// CXPLAT_POOL RioRecvPool; - -// } CXPLAT_DATAPATH_PARTITION; - -// // -// // Main structure for tracking all UDP abstractions. -// // -// typedef struct CXPLAT_DATAPATH { - -// // -// // The UDP callback function pointers. -// // -// CXPLAT_UDP_DATAPATH_CALLBACKS UdpHandlers; - -// // -// // The TCP callback function pointers. -// // -// CXPLAT_TCP_DATAPATH_CALLBACKS TcpHandlers; - -// // -// // Function pointer to AcceptEx. -// // -// LPFN_ACCEPTEX AcceptEx; - -// // -// // Function pointer to ConnectEx. -// // -// LPFN_CONNECTEX ConnectEx; - -// // -// // Function pointer to WSASendMsg. -// // -// LPFN_WSASENDMSG WSASendMsg; - -// // -// // Function pointer to WSARecvMsg. -// // -// LPFN_WSARECVMSG WSARecvMsg; - -// // -// // Function pointer table for RIO. -// // -// RIO_EXTENSION_FUNCTION_TABLE RioDispatch; - -// // -// // Used to synchronize clean up. -// // -// CXPLAT_REF_COUNT RefCount; - -// // -// // Set of supported features. -// // -// uint32_t Features; - -// // -// // The size of each receive datagram array element, including client context, -// // internal context, and padding. -// // -// uint32_t DatagramStride; - -// // -// // The offset of the receive payload buffer from the start of the receive -// // context. -// // -// uint32_t RecvPayloadOffset; - -// // -// // The number of processors. -// // -// uint16_t PartitionCount; - -// // -// // Maximum batch sizes supported for send. -// // -// uint8_t MaxSendBatchSize; - -// // -// // Uses RIO interface instead of normal asyc IO. -// // -// uint8_t UseRio : 1; - -// // -// // Debug flags -// // -// uint8_t Uninitialized : 1; -// uint8_t Freed : 1; - -// // -// // Per-processor completion contexts. -// // -// CXPLAT_DATAPATH_PARTITION Partitions[0]; - -// } CXPLAT_DATAPATH; _IRQL_requires_max_(PASSIVE_LEVEL) void @@ -2489,7 +2145,7 @@ CxPlatSocketCreateTcpInternal( Socket->RemoteAddress.Ipv4.sin_port = 0; } - *NewSocket = (CXPLAT_SOCKET*)Socket; + *NewSocket = Socket; Socket = NULL; RawSocket = NULL; @@ -2732,7 +2388,7 @@ SocketCreateTcpListener( SocketProc->IoStarted = TRUE; - *NewSocket = (CXPLAT_SOCKET*)Socket; + *NewSocket = Socket; Socket = NULL; RawSocket = NULL; Status = QUIC_STATUS_SUCCESS; @@ -2813,7 +2469,7 @@ CxPlatSocketContextRelease( } } else { if (SocketProc->AcceptSocket != NULL) { - SocketDelete((CXPLAT_SOCKET*)SocketProc->AcceptSocket); + SocketDelete(SocketProc->AcceptSocket); SocketProc->AcceptSocket = NULL; } } @@ -2830,7 +2486,7 @@ CxPlatSocketContextRelease( } SocketProc->Freed = TRUE; - CxPlatSocketRelease((CXPLAT_SOCKET*)SocketProc->Parent); // + CxPlatSocketRelease(SocketProc->Parent); // } } @@ -3206,9 +2862,9 @@ CxPlatDataPathSocketProcessAcceptCompletion( AcceptSocketProc->IoStarted = TRUE; ListenerSocketProc->Parent->Datapath->TcpHandlers.Accept( - (CXPLAT_SOCKET*)ListenerSocketProc->Parent, + ListenerSocketProc->Parent, ListenerSocketProc->Parent->ClientContext, - (CXPLAT_SOCKET*)ListenerSocketProc->AcceptSocket, + ListenerSocketProc->AcceptSocket, &ListenerSocketProc->AcceptSocket->ClientContext); ListenerSocketProc->AcceptSocket = NULL; @@ -3224,7 +2880,7 @@ CxPlatDataPathSocketProcessAcceptCompletion( Error: if (ListenerSocketProc->AcceptSocket != NULL) { - SocketDelete((CXPLAT_SOCKET*)ListenerSocketProc->AcceptSocket); + SocketDelete(ListenerSocketProc->AcceptSocket); ListenerSocketProc->AcceptSocket = NULL; } @@ -3267,7 +2923,7 @@ CxPlatDataPathSocketProcessConnectCompletion( "ConnectEx Completed!"); SocketProc->Parent->Datapath->TcpHandlers.Connect( - (CXPLAT_SOCKET*)SocketProc->Parent, + SocketProc->Parent, SocketProc->Parent->ClientContext, TRUE); @@ -3285,7 +2941,7 @@ CxPlatDataPathSocketProcessConnectCompletion( "ConnectEx completion"); SocketProc->Parent->Datapath->TcpHandlers.Connect( - (CXPLAT_SOCKET*)SocketProc->Parent, + SocketProc->Parent, SocketProc->Parent->ClientContext, FALSE); } @@ -3578,7 +3234,7 @@ CxPlatDataPathUdpRecvComplete( CASTED_CLOG_BYTEARRAY(sizeof(*RemoteAddr), RemoteAddr)); #endif SocketProc->Parent->Datapath->UdpHandlers.Unreachable( - (CXPLAT_SOCKET*)SocketProc->Parent, + SocketProc->Parent, SocketProc->Parent->ClientContext, RemoteAddr); } @@ -3742,12 +3398,12 @@ CxPlatDataPathUdpRecvComplete( if (!SocketProc->Parent->PcpBinding) { SocketProc->Parent->Datapath->UdpHandlers.Receive( - (CXPLAT_SOCKET*)SocketProc->Parent, + SocketProc->Parent, SocketProc->Parent->ClientContext, RecvDataChain); } else { CxPlatPcpRecvCallback( - (CXPLAT_SOCKET*)SocketProc->Parent, + SocketProc->Parent, SocketProc->Parent->ClientContext, RecvDataChain); } @@ -3925,7 +3581,7 @@ CxPlatDataPathTcpRecvComplete( if (!SocketProc->Parent->DisconnectIndicated) { SocketProc->Parent->DisconnectIndicated = TRUE; SocketProc->Parent->Datapath->TcpHandlers.Connect( - (CXPLAT_SOCKET*)SocketProc->Parent, + SocketProc->Parent, SocketProc->Parent->ClientContext, FALSE); } @@ -3939,7 +3595,7 @@ CxPlatDataPathTcpRecvComplete( if (!SocketProc->Parent->DisconnectIndicated) { SocketProc->Parent->DisconnectIndicated = TRUE; SocketProc->Parent->Datapath->TcpHandlers.Connect( - (CXPLAT_SOCKET*)SocketProc->Parent, + SocketProc->Parent, SocketProc->Parent->ClientContext, FALSE); } @@ -3976,7 +3632,7 @@ CxPlatDataPathTcpRecvComplete( IoBlock = NULL; SocketProc->Parent->Datapath->TcpHandlers.Receive( - (CXPLAT_SOCKET*)SocketProc->Parent, + SocketProc->Parent, SocketProc->Parent->ClientContext, Data); @@ -4538,7 +4194,7 @@ CxPlatSendDataComplete( if (SocketProc->Parent->Type != CXPLAT_SOCKET_UDP) { SocketProc->Parent->Datapath->TcpHandlers.SendComplete( - (CXPLAT_SOCKET*)SocketProc->Parent, + SocketProc->Parent, SocketProc->Parent->ClientContext, IoResult, SendData->TotalSize); diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index c2b833f2c2..5e8271ceea 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -160,6 +160,13 @@ typedef struct CX_PLATFORM { } CX_PLATFORM; +typedef enum CXPLAT_SOCKET_TYPE { + CXPLAT_SOCKET_UDP = 0, + CXPLAT_SOCKET_TCP_LISTENER = 1, + CXPLAT_SOCKET_TCP = 2, + CXPLAT_SOCKET_TCP_SERVER = 3 +} CXPLAT_SOCKET_TYPE; + // // Type of IO. // diff --git a/src/test/lib/MtuTest.cpp b/src/test/lib/MtuTest.cpp index 4ca64cfb21..fa4a0e4ecf 100644 --- a/src/test/lib/MtuTest.cpp +++ b/src/test/lib/MtuTest.cpp @@ -80,7 +80,7 @@ QuicTestMtuSettings() TEST_EQUAL(NewSettings.MaximumMtu, UpdatedSettings.MaximumMtu); } - MsQuicRegistration Registration(true); + MsQuicRegistration Registration; TEST_QUIC_SUCCEEDED(Registration.GetInitStatus()); MsQuicAlpn Alpn("MsQuicTest"); { From e4525e94644f3c9ca267302e67f64c1f3b984836 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Sun, 27 Aug 2023 19:47:33 -0700 Subject: [PATCH 50/87] move logic to _winuser --- src/generated/linux/datapath_winuser.c.clog.h | 12 +-- .../linux/datapath_winuser.c.clog.h.lttng.h | 12 +-- src/platform/datapath_raw_win.c | 23 ++++-- src/platform/datapath_win.c | 78 ++----------------- src/platform/datapath_winuser.c | 61 +++++++++++++-- src/platform/platform_internal.h | 8 +- 6 files changed, 98 insertions(+), 96 deletions(-) diff --git a/src/generated/linux/datapath_winuser.c.clog.h b/src/generated/linux/datapath_winuser.c.clog.h index 5d0014256d..6382fa23c2 100644 --- a/src/generated/linux/datapath_winuser.c.clog.h +++ b/src/generated/linux/datapath_winuser.c.clog.h @@ -275,12 +275,12 @@ tracepoint(CLOG_DATAPATH_WINUSER_C, LibraryErrorStatus , arg2, arg3);\ // Decoder Ring for AllocFailure // Allocation of '%s' failed. (%llu bytes) // QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "PIP_ADAPTER_ADDRESSES", - AdapterAddressesSize); -// arg2 = arg2 = "PIP_ADAPTER_ADDRESSES" = arg2 -// arg3 = arg3 = AdapterAddressesSize = arg3 + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "CXPLAT_DATAPATH", + DatapathLength); +// arg2 = arg2 = "CXPLAT_DATAPATH" = arg2 +// arg3 = arg3 = DatapathLength = arg3 ----------------------------------------------------------*/ #ifndef _clog_4_ARGS_TRACE_AllocFailure #define _clog_4_ARGS_TRACE_AllocFailure(uniqueId, encoded_arg_string, arg2, arg3)\ diff --git a/src/generated/linux/datapath_winuser.c.clog.h.lttng.h b/src/generated/linux/datapath_winuser.c.clog.h.lttng.h index 248eb7c4d7..35d96341c5 100644 --- a/src/generated/linux/datapath_winuser.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_winuser.c.clog.h.lttng.h @@ -272,12 +272,12 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_WINUSER_C, LibraryErrorStatus, // Decoder Ring for AllocFailure // Allocation of '%s' failed. (%llu bytes) // QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "PIP_ADAPTER_ADDRESSES", - AdapterAddressesSize); -// arg2 = arg2 = "PIP_ADAPTER_ADDRESSES" = arg2 -// arg3 = arg3 = AdapterAddressesSize = arg3 + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "CXPLAT_DATAPATH", + DatapathLength); +// arg2 = arg2 = "CXPLAT_DATAPATH" = arg2 +// arg3 = arg3 = DatapathLength = arg3 ----------------------------------------------------------*/ TRACEPOINT_EVENT(CLOG_DATAPATH_WINUSER_C, AllocFailure, TP_ARGS( diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index 4fd60b1561..2dac8ae434 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -109,18 +109,28 @@ RawDataPathInitialize( _In_ uint32_t ClientRecvContextLength, _In_opt_ QUIC_EXECUTION_CONFIG* Config, _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, - _Out_ CXPLAT_DATAPATH_RAW* DataPath + _Out_ CXPLAT_DATAPATH_RAW** NewDataPath ) { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - // const size_t DatapathSize = CxPlatDpRawGetDatapathSize(Config); + const size_t DatapathSize = CxPlatDpRawGetDatapathSize(Config); BOOLEAN DpRawInitialized = FALSE; BOOLEAN SockPoolInitialized = FALSE; - // CXPLAT_FRE_ASSERT(DatapathSize > sizeof(CXPLAT_DATAPATH)); - if (DataPath == NULL) { + if (NewDataPath == NULL) { return QUIC_STATUS_INVALID_PARAMETER; } + + CXPLAT_DATAPATH_RAW* DataPath = CXPLAT_ALLOC_PAGED(DatapathSize, QUIC_POOL_DATAPATH); + if (DataPath == NULL) { + QuicTraceEvent( + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "CXPLAT_DATAPATH", + DatapathSize); + return QUIC_STATUS_OUT_OF_MEMORY; + } + CxPlatZeroMemory(DataPath, DatapathSize); CXPLAT_FRE_ASSERT(CxPlatRundownAcquire(&CxPlatWorkerRundown)); if (Config && (Config->Flags & QUIC_EXECUTION_CONFIG_FLAG_QTIP)) { @@ -144,11 +154,13 @@ RawDataPathInitialize( goto Error; } + *NewDataPath = DataPath; DataPath->ParentDataPath = ParentDataPath; + DataPath = NULL; Error: - if (QUIC_FAILED(Status)) { + if (DataPath != NULL) { #if DEBUG DataPath->Uninitialized = TRUE; #endif @@ -158,6 +170,7 @@ RawDataPathInitialize( if (SockPoolInitialized) { CxPlatSockPoolUninitialize(&DataPath->SocketPool); } + CXPLAT_FREE(DataPath, QUIC_POOL_DATAPATH); CxPlatRundownRelease(&CxPlatWorkerRundown); } } diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 25de2d133b..9a7cec6331 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -46,59 +46,12 @@ CxPlatDataPathInitialize( goto Error; } - // - // Init all Datapath - // - if (UdpCallbacks != NULL) { - if (UdpCallbacks->Receive == NULL || UdpCallbacks->Unreachable == NULL) { - Status = QUIC_STATUS_INVALID_PARAMETER; - goto Error; - } - } - if (TcpCallbacks != NULL) { - if (TcpCallbacks->Accept == NULL || - TcpCallbacks->Connect == NULL || - TcpCallbacks->Receive == NULL || - TcpCallbacks->SendComplete == NULL) { - Status = QUIC_STATUS_INVALID_PARAMETER; - goto Error; - } - } - if (!CxPlatWorkersLazyStart(Config)) { - Status = QUIC_STATUS_OUT_OF_MEMORY; - goto Error; - } - uint32_t PartitionCount = CxPlatProcMaxCount(); - if (Config && Config->ProcessorCount) { - PartitionCount = Config->ProcessorCount; - } - uint32_t DatapathLength = - sizeof(CXPLAT_DATAPATH) + - PartitionCount * sizeof(CXPLAT_DATAPATH_PARTITION); - - CXPLAT_DATAPATH* Datapath = (CXPLAT_DATAPATH*)CXPLAT_ALLOC_PAGED(DatapathLength, QUIC_POOL_DATAPATH); - if (Datapath == NULL) { - QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_DATAPATH", - DatapathLength); - Status = QUIC_STATUS_OUT_OF_MEMORY; - goto Error; - } - - RtlZeroMemory(Datapath, DatapathLength); - if (UdpCallbacks) { - Datapath->UdpHandlers = *UdpCallbacks; - } - if (TcpCallbacks) { - Datapath->TcpHandlers = *TcpCallbacks; - } - Datapath->PartitionCount = (uint16_t)PartitionCount; Status = DataPathInitialize( ClientRecvContextLength, + UdpCallbacks, + TcpCallbacks, Config, - Datapath); + NewDataPath); if (QUIC_FAILED(Status)) { QuicTraceLogVerbose( DatapathInitFail, @@ -106,38 +59,21 @@ CxPlatDataPathInitialize( goto Error; } - const size_t RawDatapathSize = CxPlatDpRawGetDatapathSize(Config); - CXPLAT_DATAPATH_RAW* RawDataPath = CXPLAT_ALLOC_PAGED(RawDatapathSize, QUIC_POOL_DATAPATH); - if (RawDataPath == NULL) { - QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_DATAPATH", - RawDatapathSize); - return QUIC_STATUS_OUT_OF_MEMORY; - } - CxPlatZeroMemory(RawDataPath, RawDatapathSize); - - // Status = QUIC_STATUS_INVALID_PARAMETER; Status = RawDataPathInitialize( ClientRecvContextLength, Config, - Datapath, - RawDataPath); + (*NewDataPath), + &((*NewDataPath)->RawDataPath)); if (QUIC_FAILED(Status)) { QuicTraceLogVerbose( RawDatapathInitFail, "[ raw] Failed to initialize raw datapath, status:%d", Status); Status = QUIC_STATUS_SUCCESS; - CXPLAT_FREE(RawDataPath, QUIC_POOL_DATAPATH); - RawDataPath = NULL; + CXPLAT_FREE((*NewDataPath)->RawDataPath, QUIC_POOL_DATAPATH); + (*NewDataPath)->RawDataPath = NULL; } - Datapath->RawDataPath = RawDataPath; - *NewDataPath = Datapath; - Error: - // TODO: error handling return Status; } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index afb6d3ea9e..f81226b89a 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -756,21 +756,45 @@ _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS DataPathInitialize( _In_ uint32_t ClientRecvDataLength, + _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, + _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, _In_opt_ QUIC_EXECUTION_CONFIG* Config, - _Out_ CXPLAT_DATAPATH* Datapath + _Out_ CXPLAT_DATAPATH** NewDatapath ) { int WsaError; QUIC_STATUS Status; WSADATA WsaData; + uint32_t PartitionCount = CxPlatProcMaxCount(); + uint32_t DatapathLength; + CXPLAT_DATAPATH* Datapath = NULL; BOOLEAN WsaInitialized = FALSE; - if (Datapath == NULL) { + if (NewDatapath == NULL) { Status = QUIC_STATUS_INVALID_PARAMETER; goto Exit; } + if (UdpCallbacks != NULL) { + if (UdpCallbacks->Receive == NULL || UdpCallbacks->Unreachable == NULL) { + Status = QUIC_STATUS_INVALID_PARAMETER; + goto Exit; + } + } + if (TcpCallbacks != NULL) { + if (TcpCallbacks->Accept == NULL || + TcpCallbacks->Connect == NULL || + TcpCallbacks->Receive == NULL || + TcpCallbacks->SendComplete == NULL) { + Status = QUIC_STATUS_INVALID_PARAMETER; + goto Exit; + } + } + + if (!CxPlatWorkersLazyStart(Config)) { + Status = QUIC_STATUS_OUT_OF_MEMORY; + goto Exit; + } - // TODO: moveup if ((WsaError = WSAStartup(MAKEWORD(2, 2), &WsaData)) != 0) { QuicTraceEvent( LibraryErrorStatus, @@ -782,6 +806,33 @@ DataPathInitialize( } WsaInitialized = TRUE; + if (Config && Config->ProcessorCount) { + PartitionCount = Config->ProcessorCount; + } + + DatapathLength = + sizeof(CXPLAT_DATAPATH) + + PartitionCount * sizeof(CXPLAT_DATAPATH_PARTITION); + + Datapath = (CXPLAT_DATAPATH*)CXPLAT_ALLOC_PAGED(DatapathLength, QUIC_POOL_DATAPATH); + if (Datapath == NULL) { + QuicTraceEvent( + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "CXPLAT_DATAPATH", + DatapathLength); + Status = QUIC_STATUS_OUT_OF_MEMORY; + goto Error; + } + + RtlZeroMemory(Datapath, DatapathLength); + if (UdpCallbacks) { + Datapath->UdpHandlers = *UdpCallbacks; + } + if (TcpCallbacks) { + Datapath->TcpHandlers = *TcpCallbacks; + } + Datapath->PartitionCount = (uint16_t)PartitionCount; CxPlatRefInitializeEx(&Datapath->RefCount, Datapath->PartitionCount); Datapath->UseRio = Config && !!(Config->Flags & QUIC_EXECUTION_CONFIG_FLAG_RIO); @@ -914,10 +965,11 @@ DataPathInitialize( } CXPLAT_FRE_ASSERT(CxPlatRundownAcquire(&CxPlatWorkerRundown)); + *NewDatapath = Datapath; Status = QUIC_STATUS_SUCCESS; Error: - // TODO: move up? + if (QUIC_FAILED(Status)) { if (Datapath != NULL) { CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); @@ -945,7 +997,6 @@ CxPlatDataPathRelease( CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); WSACleanup(); CxPlatRundownRelease(&CxPlatWorkerRundown); - } } diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 5e8271ceea..119bb89739 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -561,9 +561,11 @@ SocketDelete( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS DataPathInitialize( - _In_ uint32_t ClientRecvContextLength, + _In_ uint32_t ClientRecvDataLength, + _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, + _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, _In_opt_ QUIC_EXECUTION_CONFIG* Config, - _Out_ CXPLAT_DATAPATH* DataPath + _Out_ CXPLAT_DATAPATH** NewDatapath ); _IRQL_requires_max_(PASSIVE_LEVEL) @@ -731,7 +733,7 @@ RawDataPathInitialize( _In_ uint32_t ClientRecvContextLength, _In_opt_ QUIC_EXECUTION_CONFIG* Config, _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, - _Out_ CXPLAT_DATAPATH_RAW* DataPath + _Out_ CXPLAT_DATAPATH_RAW** DataPath ); _IRQL_requires_max_(PASSIVE_LEVEL) From c40d53bef7dc120b314782869e0626385f60d036 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Mon, 28 Aug 2023 23:16:42 -0700 Subject: [PATCH 51/87] use dummy raw datapath for uwp build --- src/platform/CMakeLists.txt | 15 ++++++--------- src/platform/datapath_raw.h | 10 ---------- src/platform/datapath_raw_socket.c | 10 ++++++++++ src/platform/datapath_win.c | 8 ++++---- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt index db46830da3..d8cc6fe100 100644 --- a/src/platform/CMakeLists.txt +++ b/src/platform/CMakeLists.txt @@ -12,15 +12,12 @@ endif() set(SOURCES crypt.c hashtable.c pcp.c platform_worker.c toeplitz.c) if("${CX_PLATFORM}" STREQUAL "windows") - set(SOURCES ${SOURCES} - platform_winuser.c - storage_winuser.c - datapath_win.c - datapath_winuser.c - datapath_raw_win.c - datapath_raw_socket.c - datapath_raw_socket_win.c - datapath_raw_xdp_win.c) + set(SOURCES ${SOURCES} platform_winuser.c storage_winuser.c datapath_win.c datapath_winuser.c) + if(QUIC_UWP_BUILD) + set(SOURCES ${SOURCES} datapath_raw_dummy.c) + else() + set(SOURCES ${SOURCES} datapath_raw_win.c datapath_raw_socket.c datapath_raw_socket_win.c datapath_raw_xdp_win.c) + endif() else() set(SOURCES ${SOURCES} inline.c platform_posix.c storage_posix.c cgroup.c) if(CX_PLATFORM STREQUAL "linux" AND NOT CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index c01b836163..66cb4131fb 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -260,16 +260,6 @@ typedef struct CXPLAT_SOCKET_RAW { CXPLAT_SOCKET; } CXPLAT_SOCKET_RAW; -inline CXPLAT_SOCKET* -CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket) { - return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); -} -// TODO: rename -inline CXPLAT_SOCKET_RAW* -CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { - return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); -} - BOOLEAN CxPlatSockPoolInitialize( _Inout_ CXPLAT_SOCKET_POOL* Pool diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index ec8456af9d..44a3706fe1 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -23,6 +23,16 @@ CxPlatGetRawSocketSize () { return sizeof(CXPLAT_SOCKET_RAW); } +CXPLAT_SOCKET* +CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket) { + return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); +} + +CXPLAT_SOCKET_RAW* +CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { + return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); +} + CXPLAT_SOCKET_RAW* CxPlatGetSocket( _In_ const CXPLAT_SOCKET_POOL* Pool, diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 9a7cec6331..8d3d557c86 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -22,9 +22,9 @@ #pragma warning(disable:6386) // buffer overrun #define IS_LOOPBACK(Address) ((Address.si_family == QUIC_ADDRESS_FAMILY_INET && \ - Address.Ipv4.sin_addr.S_un.S_addr == htonl(INADDR_LOOPBACK)) || \ - (Address.si_family == QUIC_ADDRESS_FAMILY_INET6 && \ - IN6_IS_ADDR_LOOPBACK(&Address.Ipv6.sin6_addr))) + Address.Ipv4.sin_addr.S_un.S_addr == htonl(INADDR_LOOPBACK)) || \ + (Address.si_family == QUIC_ADDRESS_FAMILY_INET6 && \ + IN6_IS_ADDR_LOOPBACK(&Address.Ipv6.sin6_addr))) #define RawDataPathAvailable(Datapath) ((Datapath)->RawDataPath != NULL) #define RawSocketAvailable(Socket) ((Socket)->Datapath && RawDataPathAvailable((Socket)->Datapath)) @@ -376,7 +376,7 @@ CxPlatSocketGetLocalMtu( CXPLAT_DBG_ASSERT(Socket != NULL); if (Socket->Datapath && Socket->Datapath->RawDataPath && !IS_LOOPBACK(Socket->RemoteAddress)) { - RawSocketGetLocalMtu(CxPlatSocketToRaw(Socket)); + return RawSocketGetLocalMtu(CxPlatSocketToRaw(Socket)); } return Socket->Mtu; } From 301372838366e3fb3c157b3299081612df2df168 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Mon, 28 Aug 2023 23:33:11 -0700 Subject: [PATCH 52/87] dummy raw --- src/platform/datapath_raw_dummy.c | 252 ++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 src/platform/datapath_raw_dummy.c diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c new file mode 100644 index 0000000000..16002cae23 --- /dev/null +++ b/src/platform/datapath_raw_dummy.c @@ -0,0 +1,252 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Datapath Implementation (User Mode) + +--*/ + +#include "datapath_raw.h" +#include "platform_internal.h" + +uint32_t +CxPlatGetRawSocketSize () { + return sizeof(CXPLAT_SOCKET_RAW); +} + +CXPLAT_SOCKET* +CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket) { + return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); +} + +CXPLAT_SOCKET_RAW* +CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { + return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +RawSocketCreateUdp( + _In_ CXPLAT_DATAPATH_RAW* DataPath, + _In_ const CXPLAT_UDP_CONFIG* Config, + _Out_ CXPLAT_SOCKET_RAW* NewSocket + ) +{ + UNREFERENCED_PARAMETER(DataPath); + UNREFERENCED_PARAMETER(Config); + UNREFERENCED_PARAMETER(NewSocket); + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +RawSocketDelete( + _In_ CXPLAT_SOCKET_RAW* Socket + ) +{ + UNREFERENCED_PARAMETER(Socket); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +RawDataPathInitialize( + _In_ uint32_t ClientRecvContextLength, + _In_opt_ QUIC_EXECUTION_CONFIG* Config, + _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, + _Out_ CXPLAT_DATAPATH_RAW** DataPath + ) +{ + UNREFERENCED_PARAMETER(ClientRecvContextLength); + UNREFERENCED_PARAMETER(Config); + UNREFERENCED_PARAMETER(ParentDataPath); + UNREFERENCED_PARAMETER(DataPath); + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +RawDataPathUninitialize( + _In_ CXPLAT_DATAPATH_RAW* Datapath + ) +{ + UNREFERENCED_PARAMETER(Datapath); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +RawDataPathUpdateConfig( + _In_ CXPLAT_DATAPATH_RAW* Datapath, + _In_ QUIC_EXECUTION_CONFIG* Config + ) +{ + UNREFERENCED_PARAMETER(Datapath); + UNREFERENCED_PARAMETER(Config); +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +uint32_t +RawDataPathGetSupportedFeatures( + _In_ CXPLAT_DATAPATH_RAW* Datapath + ) +{ + UNREFERENCED_PARAMETER(Datapath); + return 0; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +RawSocketUpdateQeo( + _In_ CXPLAT_SOCKET_RAW* Socket, + _In_reads_(OffloadCount) + const CXPLAT_QEO_CONNECTION* Offloads, + _In_ uint32_t OffloadCount + ) +{ + UNREFERENCED_PARAMETER(Socket); + UNREFERENCED_PARAMETER(Offloads); + UNREFERENCED_PARAMETER(OffloadCount); + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +UINT16 +RawSocketGetLocalMtu( + _In_ CXPLAT_SOCKET_RAW* Socket + ) +{ + UNREFERENCED_PARAMETER(Socket); + return 1500; +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +RawRecvDataReturn( + _In_opt_ CXPLAT_RECV_DATA* RecvDataChain + ) +{ + UNREFERENCED_PARAMETER(RecvDataChain); +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +CXPLAT_SEND_DATA* +RawSendDataAlloc( + _In_ CXPLAT_SOCKET_RAW* Socket, + _Inout_ CXPLAT_SEND_CONFIG* Config + ) +{ + UNREFERENCED_PARAMETER(Socket); + UNREFERENCED_PARAMETER(Config); + return NULL; +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +RawSendDataFree( + _In_ CXPLAT_SEND_DATA* SendData + ) +{ + UNREFERENCED_PARAMETER(SendData); +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +QUIC_BUFFER* +RawSendDataAllocBuffer( + _In_ CXPLAT_SEND_DATA* SendData, + _In_ uint16_t MaxBufferLength + ) +{ + UNREFERENCED_PARAMETER(SendData); + UNREFERENCED_PARAMETER(MaxBufferLength); + return NULL; +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +RawSendDataFreeBuffer( + _In_ CXPLAT_SEND_DATA* SendData, + _In_ QUIC_BUFFER* Buffer + ) +{ + UNREFERENCED_PARAMETER(SendData); + UNREFERENCED_PARAMETER(Buffer); +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +BOOLEAN +RawSendDataIsFull( + _In_ CXPLAT_SEND_DATA* SendData + ) +{ + UNREFERENCED_PARAMETER(SendData); + return FALSE; +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +QUIC_STATUS +RawSocketSend( + _In_ CXPLAT_SOCKET_RAW* Socket, + _In_ const CXPLAT_ROUTE* Route, + _In_ CXPLAT_SEND_DATA* SendData + ) +{ + UNREFERENCED_PARAMETER(Socket); + UNREFERENCED_PARAMETER(Route); + UNREFERENCED_PARAMETER(SendData); + return QUIC_STATUS_NOT_SUPPORTED; +} + +void +RawResolveRouteComplete( + _In_ void* Context, + _Inout_ CXPLAT_ROUTE* Route, + _In_reads_bytes_(6) const uint8_t* PhysicalAddress, + _In_ uint8_t PathId + ) +{ + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Route); + UNREFERENCED_PARAMETER(PhysicalAddress); + UNREFERENCED_PARAMETER(PathId); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +RawResolveRoute( + _In_ CXPLAT_SOCKET* Sock, + _Inout_ CXPLAT_ROUTE* Route, + _In_ uint8_t PathId, + _In_ void* Context, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback + ) +{ + UNREFERENCED_PARAMETER(Sock); + UNREFERENCED_PARAMETER(Route); + UNREFERENCED_PARAMETER(PathId); + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Callback); + return QUIC_STATUS_NOT_SUPPORTED; +} + +void +RawDataPathProcessCqe( + _In_ CXPLAT_CQE* Cqe + ) +{ + UNREFERENCED_PARAMETER(Cqe); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +RawUpdateRoute( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ + UNREFERENCED_PARAMETER(DstRoute); + UNREFERENCED_PARAMETER(SrcRoute); +} \ No newline at end of file From 08220d38279b54e2a38b2cd63e2c646fa9fe0ca2 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Mon, 28 Aug 2023 23:34:12 -0700 Subject: [PATCH 53/87] remove double free --- src/platform/datapath_win.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 8d3d557c86..a89da45df7 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -69,7 +69,6 @@ CxPlatDataPathInitialize( RawDatapathInitFail, "[ raw] Failed to initialize raw datapath, status:%d", Status); Status = QUIC_STATUS_SUCCESS; - CXPLAT_FREE((*NewDataPath)->RawDataPath, QUIC_POOL_DATAPATH); (*NewDataPath)->RawDataPath = NULL; } From ffacb712c2031fa98276b34dd0a367f951c5ad2c Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 29 Aug 2023 19:47:06 -0700 Subject: [PATCH 54/87] fix comments --- src/core/connection.c | 4 +-- src/core/packet_builder.c | 2 +- src/core/path.c | 2 +- src/core/path.h | 3 ++- src/inc/quic_datapath.h | 18 +++---------- src/platform/datapath_epoll.c | 12 ++++++--- src/platform/datapath_kqueue.c | 12 ++++++--- src/platform/datapath_raw_linux.c | 4 ++- src/platform/datapath_win.c | 44 +++++++++++++++++++------------ src/platform/datapath_winkernel.c | 12 ++++++--- src/platform/platform_internal.h | 29 ++++++++++---------- 11 files changed, 82 insertions(+), 60 deletions(-) diff --git a/src/core/connection.c b/src/core/connection.c index 790fa5fef8..7a56b129db 100644 --- a/src/core/connection.c +++ b/src/core/connection.c @@ -184,7 +184,7 @@ QuicConnAlloc( Connection->Stats.QuicVersion = Packet->Invariant->LONG_HDR.Version; QuicConnOnQuicVersionSet(Connection); - QuicCopyRouteInfo(&Path->Route, Datagram->Route); + QuicCopyRouteInfo(&Path->Route, Datagram->Route, Datagram->DatapathType); Connection->State.LocalAddressSet = TRUE; Connection->State.RemoteAddressSet = TRUE; @@ -5618,7 +5618,7 @@ QuicConnRecvDatagrams( goto Drop; } - CxPlatUpdateRoute(&DatagramPath->Route, Datagram->Route); + CxPlatUpdateRoute(&DatagramPath->Route, Datagram->Route, Datagram->DatapathType); if (DatagramPath != CurrentPath) { if (BatchCount != 0) { diff --git a/src/core/packet_builder.c b/src/core/packet_builder.c index 2c4d90acc5..113c08f956 100644 --- a/src/core/packet_builder.c +++ b/src/core/packet_builder.c @@ -720,7 +720,7 @@ QuicPacketBuilderFinalize( FinalQuicPacket = TRUE; - if (!FlushBatchedDatagrams && CxPlatDataPathIsPaddingPreferred(MsQuicLib.Datapath)) { + if (!FlushBatchedDatagrams && CxPlatDataPathIsPaddingPreferred(MsQuicLib.Datapath, Builder->SendData)) { // // When buffering multiple datagrams in a single contiguous buffer // (at the datapath layer), all but the last datagram needs to be diff --git a/src/core/path.c b/src/core/path.c index f585affb68..1a28000096 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -253,7 +253,7 @@ QuicConnGetPathForDatagram( Path->DestCid = Connection->Paths[0].DestCid; // TODO - Copy instead? } Path->Binding = Connection->Paths[0].Binding; - QuicCopyRouteInfo(&Path->Route, Datagram->Route); + QuicCopyRouteInfo(&Path->Route, Datagram->Route, Datagram->DatapathType); QuicPathValidate(Path); return Path; diff --git a/src/core/path.h b/src/core/path.h index d6dc6f07df..57a3bea7a2 100644 --- a/src/core/path.h +++ b/src/core/path.h @@ -306,7 +306,8 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void QuicCopyRouteInfo( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute + _In_ CXPLAT_ROUTE* SrcRoute, + _In_ uint16_t DatapathType ); // diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index b7d337d9cc..4a2528e215 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -456,7 +456,8 @@ CxPlatDataPathGetSupportedFeatures( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN CxPlatDataPathIsPaddingPreferred( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_SEND_DATA* SendData ); // @@ -600,18 +601,6 @@ CxPlatSocketDelete( ); -_IRQL_requires_max_(PASSIVE_LEVEL) -BOOLEAN -CxPlatRawDataPathAvailable( - _In_ CXPLAT_DATAPATH* Datapath - ); - -_IRQL_requires_max_(PASSIVE_LEVEL) -BOOLEAN -CxPlatRawSocketAvailable( - _In_ CXPLAT_SOCKET* Socket - ); - // // Plumbs new or removes existing QUIC encryption offload information. // @@ -785,7 +774,8 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute + _In_ CXPLAT_ROUTE* SrcRoute, + _In_ uint16_t DatapathType ); #if defined(__cplusplus) diff --git a/src/platform/datapath_epoll.c b/src/platform/datapath_epoll.c index 99ffdd9d06..0bb2dca8b1 100644 --- a/src/platform/datapath_epoll.c +++ b/src/platform/datapath_epoll.c @@ -736,9 +736,11 @@ CxPlatDataPathGetSupportedFeatures( BOOLEAN CxPlatDataPathIsPaddingPreferred( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_SEND_DATA* SendData ) { + UNREFERENCED_PARAMETER(SendData); return !!(Datapath->Features & CXPLAT_DATAPATH_FEATURE_SEND_SEGMENTATION); } @@ -2585,9 +2587,11 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void QuicCopyRouteInfo( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute + _In_ CXPLAT_ROUTE* SrcRoute, + _In_ uint16_t DatapathType ) { + UNREFERENCED_PARAMETER(DatapathType); *DstRoute = *SrcRoute; } @@ -2627,9 +2631,11 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute + _In_ CXPLAT_ROUTE* SrcRoute, + _In_ uint16_t DatapathType ) { UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); + UNREFERENCED_PARAMETER(DatapathType); } diff --git a/src/platform/datapath_kqueue.c b/src/platform/datapath_kqueue.c index 6a567bebf7..46585941e5 100644 --- a/src/platform/datapath_kqueue.c +++ b/src/platform/datapath_kqueue.c @@ -572,9 +572,11 @@ CxPlatDataPathGetSupportedFeatures( BOOLEAN CxPlatDataPathIsPaddingPreferred( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_SEND_DATA* SendData ) { + UNREFERENCED_PARAMETER(SendData); return !!(Datapath->Features & CXPLAT_DATAPATH_FEATURE_SEND_SEGMENTATION); } @@ -2276,9 +2278,11 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void QuicCopyRouteInfo( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute + _In_ CXPLAT_ROUTE* SrcRoute, + _In_ uint16_t DatapathType ) { + UNREFERENCED_PARAMETER(DatapathType); *DstRoute = *SrcRoute; } @@ -2318,9 +2322,11 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute + _In_ CXPLAT_ROUTE* SrcRoute, + _In_ uint16_t DatapathType ) { UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); + UNREFERENCED_PARAMETER(DatapathType); } diff --git a/src/platform/datapath_raw_linux.c b/src/platform/datapath_raw_linux.c index 5ab92ed393..41f45d4bce 100644 --- a/src/platform/datapath_raw_linux.c +++ b/src/platform/datapath_raw_linux.c @@ -250,10 +250,12 @@ CxPlatDataPathGetSupportedFeatures( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN CxPlatDataPathIsPaddingPreferred( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_SEND_DATA* SendData ) { UNREFERENCED_PARAMETER(Datapath); + UNREFERENCED_PARAMETER(SendData); return FALSE; } diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index a89da45df7..1b896532aa 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -26,8 +26,6 @@ (Address.si_family == QUIC_ADDRESS_FAMILY_INET6 && \ IN6_IS_ADDR_LOOPBACK(&Address.Ipv6.sin6_addr))) -#define RawDataPathAvailable(Datapath) ((Datapath)->RawDataPath != NULL) -#define RawSocketAvailable(Socket) ((Socket)->Datapath && RawDataPathAvailable((Socket)->Datapath)) #define DatapathType(SendData) ((CXPLAT_SEND_DATA_COMMON*)(SendData))->DatapathType _IRQL_requires_max_(PASSIVE_LEVEL) @@ -118,12 +116,18 @@ CxPlatDataPathGetSupportedFeatures( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN CxPlatDataPathIsPaddingPreferred( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_SEND_DATA* SendData ) { - // FIXME: Which flag should be taken? - // return DataPathIsPaddingPreferred(Datapath); - return 0; + if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { + return DataPathIsPaddingPreferred(Datapath); + } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { + return RawDataPathIsPaddingPreferred(Datapath); + } else { + CXPLAT_DBG_ASSERT(FALSE); + } + return FALSE; } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -292,6 +296,7 @@ CxPlatSocketCreateUdp( Datapath->RawDataPath, Config, CxPlatSocketToRaw(*NewSocket)); + (*NewSocket)->RawSocketAvailable = QUIC_SUCCEEDED(Status); if (QUIC_FAILED(Status)) { QuicTraceLogVerbose( RawSockCreateFail, @@ -344,7 +349,7 @@ CxPlatSocketDelete( _In_ CXPLAT_SOCKET* Socket ) { - if (Socket->Datapath && Socket->Datapath->RawDataPath) { + if (Socket->RawSocketAvailable) { RawSocketDelete(CxPlatSocketToRaw(Socket)); } SocketDelete(Socket); @@ -359,7 +364,7 @@ CxPlatSocketUpdateQeo( _In_ uint32_t OffloadCount ) { - if (Socket->Datapath && Socket->Datapath->RawDataPath && + if (Socket->RawSocketAvailable && !IS_LOOPBACK(Offloads[0].Address)) { return RawSocketUpdateQeo(CxPlatSocketToRaw(Socket), Offloads, OffloadCount); } @@ -373,7 +378,7 @@ CxPlatSocketGetLocalMtu( ) { CXPLAT_DBG_ASSERT(Socket != NULL); - if (Socket->Datapath && Socket->Datapath->RawDataPath && + if (Socket->RawSocketAvailable && !IS_LOOPBACK(Socket->RemoteAddress)) { return RawSocketGetLocalMtu(CxPlatSocketToRaw(Socket)); } @@ -429,7 +434,7 @@ CxPlatSendDataAlloc( CXPLAT_SEND_DATA* SendData = NULL; // TODO: fallback? if (Socket->Type == CXPLAT_SOCKET_UDP && - Socket->Datapath && Socket->Datapath->RawDataPath && + Socket->RawSocketAvailable && !IS_LOOPBACK(Config->Route->RemoteAddress)) { SendData = RawSendDataAlloc(CxPlatSocketToRaw(Socket), Config); if (SendData) { @@ -556,14 +561,17 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void QuicCopyRouteInfo( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute + _In_ CXPLAT_ROUTE* SrcRoute, + _In_ uint16_t DatapathType ) { - if (!IS_LOOPBACK(SrcRoute->RemoteAddress)) { + if (DatapathType == CXPLAT_DATAPATH_TYPE_XDP) { CxPlatCopyMemory(DstRoute, SrcRoute, (uint8_t*)&SrcRoute->State - (uint8_t*)SrcRoute); - CxPlatUpdateRoute(DstRoute, SrcRoute); - } else { + CxPlatUpdateRoute(DstRoute, SrcRoute, DatapathType); + } else if (DatapathType == CXPLAT_DATAPATH_TYPE_USER) { *DstRoute = *SrcRoute; + } else { + CXPLAT_DBG_ASSERT(FALSE); } } @@ -593,7 +601,7 @@ CxPlatResolveRoute( _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback ) { - if (Socket->Datapath && Socket->Datapath->RawDataPath && + if (Socket->RawSocketAvailable && !IS_LOOPBACK(Route->RemoteAddress)) { return RawResolveRoute(Socket, Route, PathId, Context, Callback); } @@ -605,10 +613,12 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute + _In_ CXPLAT_ROUTE* SrcRoute, + _In_ uint16_t DatapathType ) { - if (!IS_LOOPBACK(SrcRoute->RemoteAddress)) { + if (DatapathType == CXPLAT_DATAPATH_TYPE_XDP && + !IS_LOOPBACK(SrcRoute->RemoteAddress)) { RawUpdateRoute(DstRoute, SrcRoute); } } diff --git a/src/platform/datapath_winkernel.c b/src/platform/datapath_winkernel.c index b2e6085fc6..784b000b4f 100644 --- a/src/platform/datapath_winkernel.c +++ b/src/platform/datapath_winkernel.c @@ -1051,9 +1051,11 @@ CxPlatDataPathGetSupportedFeatures( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN CxPlatDataPathIsPaddingPreferred( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_SEND_DATA* SendData ) { + UNREFERENCED_PARAMETER(SendData); return !!(Datapath->Features & CXPLAT_DATAPATH_FEATURE_SEND_SEGMENTATION); } @@ -3116,9 +3118,11 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void QuicCopyRouteInfo( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute + _In_ CXPLAT_ROUTE* SrcRoute, + _In_ uint16_t DatapathType ) { + UNREFERENCED_PARAMETER(DatapathType); *DstRoute = *SrcRoute; } @@ -3158,9 +3162,11 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute + _In_ CXPLAT_ROUTE* SrcRoute, + _In_ uint16_t DatapathType ) { UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); + UNREFERENCED_PARAMETER(DatapathType); } diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 119bb89739..597a20e5e1 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -39,19 +39,6 @@ #endif - -#ifdef _KERNEL_MODE -// #include "datapath_winkernel.h" -#elif _WIN32 -// #include "datapath_winuser.h" -#elif CX_PLATFORM_LINUX -// #include "datapath_posix.h" -#elif CX_PLATFORM_DARWIN -// #include "datapath_posix.h" -#else -#error "Unsupported Platform" -#endif - // TODO: create header files for each datapath typedef struct DATAPATH_SQE { @@ -511,10 +498,12 @@ typedef struct CXPLAT_SOCKET { // // Debug flags. - // TODO: Move to base + // uint8_t Uninitialized : 1; uint8_t Freed : 1; + uint8_t RawSocketAvailable : 1; + // // Per-processor socket contexts. // @@ -587,6 +576,12 @@ DataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH* Datapath ); +_IRQL_requires_max_(DISPATCH_LEVEL) +BOOLEAN +DataPathIsPaddingPreferred( + _In_ CXPLAT_DATAPATH* Datapath + ); + _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS @@ -755,6 +750,12 @@ RawDataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH_RAW* Datapath ); +_IRQL_requires_max_(DISPATCH_LEVEL) +BOOLEAN +RawDataPathIsPaddingPreferred( + _In_ CXPLAT_DATAPATH* Datapath + ); + _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS RawSocketUpdateQeo( From a11cd764df8634af279f6029b02c7542b5fc62e6 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 29 Aug 2023 20:05:59 -0700 Subject: [PATCH 55/87] update dummy func --- src/platform/datapath_raw_dummy.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index 16002cae23..5a955f3e83 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -96,6 +96,16 @@ RawDataPathGetSupportedFeatures( return 0; } +_IRQL_requires_max_(DISPATCH_LEVEL) +BOOLEAN +RawDataPathIsPaddingPreferred( + _In_ CXPLAT_DATAPATH* Datapath + ) +{ + UNREFERENCED_PARAMETER(Datapath); + return FALSE; +} + _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS RawSocketUpdateQeo( From a0a9e7f438ebf2021fa691b6a760d7f8ce223417 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 30 Aug 2023 01:28:19 -0700 Subject: [PATCH 56/87] fix perf run for TCP --- src/platform/datapath_win.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 1b896532aa..1c18d699dd 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -413,7 +413,10 @@ CxPlatRecvDataReturn( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ) { - CXPLAT_DBG_ASSERT(RecvDataChain != NULL); + if (RecvDataChain == NULL) { + return; + } + if (RecvDataChain->DatapathType == CXPLAT_DATAPATH_TYPE_USER) { RecvDataReturn(RecvDataChain); } else if (RecvDataChain->DatapathType == CXPLAT_DATAPATH_TYPE_XDP) { From e1fa3be72a2dab5c7265f32f834c131d858b88c6 Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Thu, 31 Aug 2023 01:13:25 -0700 Subject: [PATCH 57/87] partially fix comments --- src/core/connection.c | 4 +- src/core/path.c | 2 +- src/core/path.h | 3 +- src/inc/quic_datapath.h | 16 ++--- src/platform/datapath_epoll.c | 8 +-- src/platform/datapath_kqueue.c | 8 +-- src/platform/datapath_raw_dummy.c | 52 +++++++------- src/platform/datapath_raw_socket.c | 14 ++-- src/platform/datapath_raw_socket_win.c | 5 +- src/platform/datapath_raw_win.c | 74 +++++++++----------- src/platform/datapath_raw_xdp_win.c | 15 ++-- src/platform/datapath_win.c | 94 +++++++++++++------------- src/platform/datapath_winkernel.c | 8 +-- src/platform/datapath_winuser.c | 4 +- src/platform/platform_internal.h | 71 ++++++++++--------- 15 files changed, 176 insertions(+), 202 deletions(-) diff --git a/src/core/connection.c b/src/core/connection.c index 5349a4b9b4..656204258e 100644 --- a/src/core/connection.c +++ b/src/core/connection.c @@ -182,7 +182,7 @@ QuicConnAlloc( Connection->Stats.QuicVersion = Packet->Invariant->LONG_HDR.Version; QuicConnOnQuicVersionSet(Connection); - QuicCopyRouteInfo(&Path->Route, Packet->Route, ((CXPLAT_RECV_DATA*)Packet)->DatapathType); + QuicCopyRouteInfo(&Path->Route, Packet->Route); Connection->State.LocalAddressSet = TRUE; Connection->State.RemoteAddressSet = TRUE; @@ -5615,7 +5615,7 @@ QuicConnRecvDatagrams( goto Drop; } - CxPlatUpdateRoute(&DatagramPath->Route, Packet->Route, ((CXPLAT_RECV_DATA*)Packet)->DatapathType); + CxPlatUpdateRoute(&DatagramPath->Route, Packet->Route); if (DatagramPath != CurrentPath) { if (BatchCount != 0) { diff --git a/src/core/path.c b/src/core/path.c index 1101268a3e..91a545c54e 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -253,7 +253,7 @@ QuicConnGetPathForPacket( Path->DestCid = Connection->Paths[0].DestCid; // TODO - Copy instead? } Path->Binding = Connection->Paths[0].Binding; - QuicCopyRouteInfo(&Path->Route, Packet->Route, ((CXPLAT_RECV_DATA*)Packet)->DatapathType); + QuicCopyRouteInfo(&Path->Route, Packet->Route); QuicPathValidate(Path); return Path; diff --git a/src/core/path.h b/src/core/path.h index b4cbf46d9d..2c60008ee6 100644 --- a/src/core/path.h +++ b/src/core/path.h @@ -306,8 +306,7 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void QuicCopyRouteInfo( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute, - _In_ uint16_t DatapathType + _In_ CXPLAT_ROUTE* SrcRoute ); // diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 4a2528e215..607556158f 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -132,14 +132,6 @@ typedef struct CXPLAT_DATAPATH_RAW CXPLAT_DATAPATH_RAW; // typedef struct CXPLAT_SOCKET CXPLAT_SOCKET; -typedef enum CXPLAT_DATAPATH_TYPE { - CXPLAT_DATAPATH_TYPE_UNKNOWN = 0, - CXPLAT_DATAPATH_TYPE_USER, - CXPLAT_DATAPATH_TYPE_KERNEL, - CXPLAT_DATAPATH_TYPE_XDP, - // DPDK? -} CXPLAT_DATAPATH_TYPE; - // // Structure that maintains the 'per send' context. // @@ -189,6 +181,8 @@ typedef struct CXPLAT_ROUTE { CXPLAT_ROUTE_STATE State; CXPLAT_RAW_TCP_STATE TcpState; + uint16_t DatapathType; // CXPLAT_DATAPATH_TYPE + } CXPLAT_ROUTE; // @@ -233,7 +227,7 @@ typedef struct CXPLAT_RECV_DATA { // uint16_t Allocated : 1; // Used for debugging. Set to FALSE on free. uint16_t QueuedOnConnection : 1; // Used for debugging. - uint16_t DatapathType : 2; + uint16_t DatapathType : 2; // CXPLAT_DATAPATH_TYPE uint16_t Reserved : 4; uint16_t ReservedEx : 8; @@ -600,7 +594,6 @@ CxPlatSocketDelete( _In_ CXPLAT_SOCKET* Socket ); - // // Plumbs new or removes existing QUIC encryption offload information. // @@ -774,8 +767,7 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute, - _In_ uint16_t DatapathType + _In_ CXPLAT_ROUTE* SrcRoute ); #if defined(__cplusplus) diff --git a/src/platform/datapath_epoll.c b/src/platform/datapath_epoll.c index 0bb2dca8b1..88181d7c60 100644 --- a/src/platform/datapath_epoll.c +++ b/src/platform/datapath_epoll.c @@ -2587,11 +2587,9 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void QuicCopyRouteInfo( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute, - _In_ uint16_t DatapathType + _In_ CXPLAT_ROUTE* SrcRoute ) { - UNREFERENCED_PARAMETER(DatapathType); *DstRoute = *SrcRoute; } @@ -2631,11 +2629,9 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute, - _In_ uint16_t DatapathType + _In_ CXPLAT_ROUTE* SrcRoute ) { UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); - UNREFERENCED_PARAMETER(DatapathType); } diff --git a/src/platform/datapath_kqueue.c b/src/platform/datapath_kqueue.c index 46585941e5..aed170037e 100644 --- a/src/platform/datapath_kqueue.c +++ b/src/platform/datapath_kqueue.c @@ -2278,11 +2278,9 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void QuicCopyRouteInfo( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute, - _In_ uint16_t DatapathType + _In_ CXPLAT_ROUTE* SrcRoute ) { - UNREFERENCED_PARAMETER(DatapathType); *DstRoute = *SrcRoute; } @@ -2322,11 +2320,9 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute, - _In_ uint16_t DatapathType + _In_ CXPLAT_ROUTE* SrcRoute ) { UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); - UNREFERENCED_PARAMETER(DatapathType); } diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index 5a955f3e83..5946b1be67 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -29,10 +29,10 @@ CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawSocketCreateUdp( - _In_ CXPLAT_DATAPATH_RAW* DataPath, +CxPlatRawSocketCreateUdp( + _In_ CXPLAT_DATAPATH_RAW* DataPath, _In_ const CXPLAT_UDP_CONFIG* Config, - _Out_ CXPLAT_SOCKET_RAW* NewSocket + _Inout_ CXPLAT_SOCKET_RAW* NewSocket ) { UNREFERENCED_PARAMETER(DataPath); @@ -43,7 +43,7 @@ RawSocketCreateUdp( _IRQL_requires_max_(PASSIVE_LEVEL) void -RawSocketDelete( +CxPlatRawSocketDelete( _In_ CXPLAT_SOCKET_RAW* Socket ) { @@ -52,7 +52,7 @@ RawSocketDelete( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawDataPathInitialize( +CxPlatRawDataPathInitialize( _In_ uint32_t ClientRecvContextLength, _In_opt_ QUIC_EXECUTION_CONFIG* Config, _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, @@ -68,8 +68,8 @@ RawDataPathInitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void -RawDataPathUninitialize( - _In_ CXPLAT_DATAPATH_RAW* Datapath +CxPlatRawDataPathUninitialize( + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { UNREFERENCED_PARAMETER(Datapath); @@ -77,8 +77,8 @@ RawDataPathUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void -RawDataPathUpdateConfig( - _In_ CXPLAT_DATAPATH_RAW* Datapath, +CxPlatRawDataPathUpdateConfig( + _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ) { @@ -88,8 +88,8 @@ RawDataPathUpdateConfig( _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t -RawDataPathGetSupportedFeatures( - _In_ CXPLAT_DATAPATH_RAW* Datapath +CxPlatRawDataPathGetSupportedFeatures( + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { UNREFERENCED_PARAMETER(Datapath); @@ -98,8 +98,8 @@ RawDataPathGetSupportedFeatures( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -RawDataPathIsPaddingPreferred( - _In_ CXPLAT_DATAPATH* Datapath +CxPlatRawDataPathIsPaddingPreferred( + _In_ CXPLAT_DATAPATH* Datapath ) { UNREFERENCED_PARAMETER(Datapath); @@ -108,7 +108,7 @@ RawDataPathIsPaddingPreferred( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawSocketUpdateQeo( +CxPlatRawSocketUpdateQeo( _In_ CXPLAT_SOCKET_RAW* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, @@ -123,7 +123,7 @@ RawSocketUpdateQeo( _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 -RawSocketGetLocalMtu( +CxPlatRawSocketGetLocalMtu( _In_ CXPLAT_SOCKET_RAW* Socket ) { @@ -133,7 +133,7 @@ RawSocketGetLocalMtu( _IRQL_requires_max_(DISPATCH_LEVEL) void -RawRecvDataReturn( +CxPlatRawRecvDataReturn( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ) { @@ -143,7 +143,7 @@ RawRecvDataReturn( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* -RawSendDataAlloc( +CxPlatRawSendDataAlloc( _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) @@ -155,7 +155,7 @@ RawSendDataAlloc( _IRQL_requires_max_(DISPATCH_LEVEL) void -RawSendDataFree( +CxPlatRawSendDataFree( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -165,7 +165,7 @@ RawSendDataFree( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* -RawSendDataAllocBuffer( +CxPlatRawSendDataAllocBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ) @@ -177,7 +177,7 @@ RawSendDataAllocBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) void -RawSendDataFreeBuffer( +CxPlatRawSendDataFreeBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ) @@ -188,7 +188,7 @@ RawSendDataFreeBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -RawSendDataIsFull( +CxPlatRawSendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -198,7 +198,7 @@ RawSendDataIsFull( _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS -RawSocketSend( +CxPlatRawSocketSend( _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA* SendData @@ -211,7 +211,7 @@ RawSocketSend( } void -RawResolveRouteComplete( +CxPlatRawResolveRouteComplete( _In_ void* Context, _Inout_ CXPLAT_ROUTE* Route, _In_reads_bytes_(6) const uint8_t* PhysicalAddress, @@ -226,7 +226,7 @@ RawResolveRouteComplete( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawResolveRoute( +CxPlatRawResolveRoute( _In_ CXPLAT_SOCKET* Sock, _Inout_ CXPLAT_ROUTE* Route, _In_ uint8_t PathId, @@ -243,7 +243,7 @@ RawResolveRoute( } void -RawDataPathProcessCqe( +CxPlatRawDataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ) { @@ -252,7 +252,7 @@ RawDataPathProcessCqe( _IRQL_requires_max_(PASSIVE_LEVEL) void -RawUpdateRoute( +CxPlatRawUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, _In_ CXPLAT_ROUTE* SrcRoute ) diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index 44a3706fe1..505cac60db 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -60,7 +60,7 @@ CxPlatGetSocket( } void -RawResolveRouteComplete( +CxPlatRawResolveRouteComplete( _In_ void* Context, _Inout_ CXPLAT_ROUTE* Route, _In_reads_bytes_(6) const uint8_t* PhysicalAddress, @@ -85,7 +85,7 @@ RawResolveRouteComplete( _IRQL_requires_max_(PASSIVE_LEVEL) void -RawUpdateRoute( +CxPlatRawUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, _In_ CXPLAT_ROUTE* SrcRoute ) @@ -151,7 +151,6 @@ CxPlatDpRawParseUdp( Packet->Buffer = (uint8_t*)Udp->Data; Packet->BufferLength = QuicNetByteSwapShort(Udp->Length) - sizeof(UDP_HEADER); - Packet->DatapathType = CXPLAT_DATAPATH_TYPE_XDP; } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -221,7 +220,6 @@ CxPlatDpRawParseTcp( Packet->Buffer = (uint8_t*)(Tcp) + HeaderLength; Packet->BufferLength = Length; - Packet->DatapathType = CXPLAT_DATAPATH_TYPE_XDP; Packet->ReservedEx = HeaderLength; } @@ -507,7 +505,7 @@ CxPlatDpRawSocketAckFin( CXPLAT_ROUTE* Route = Packet->Route; CXPLAT_SEND_CONFIG SendConfig = { Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA *SendData = (CXPLAT_SEND_DATA*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); + CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(CxPlatRawToSocket(Socket), &SendConfig); if (SendData == NULL) { return; } @@ -546,7 +544,7 @@ CxPlatDpRawSocketAckSyn( CXPLAT_ROUTE* Route = Packet->Route; CXPLAT_SEND_CONFIG SendConfig = { Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA *SendData = (CXPLAT_SEND_DATA*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); + CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(CxPlatRawToSocket(Socket), &SendConfig); if (SendData == NULL) { return; } @@ -595,7 +593,7 @@ CxPlatDpRawSocketAckSyn( TH_ACK); CxPlatDpRawTxEnqueue(SendData); - SendData = (CXPLAT_SEND_DATA*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); + SendData = CxPlatSendDataAlloc(CxPlatRawToSocket(Socket), &SendConfig); if (SendData == NULL) { return; } @@ -630,7 +628,7 @@ CxPlatDpRawSocketSyn( CXPLAT_DBG_ASSERT(Socket->UseTcp); CXPLAT_SEND_CONFIG SendConfig = { (CXPLAT_ROUTE*)Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA *SendData = (CXPLAT_SEND_DATA*)CxPlatSendDataAlloc((CXPLAT_SOCKET*)Socket, &SendConfig); + CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(CxPlatRawToSocket(Socket), &SendConfig); if (SendData == NULL) { return; } diff --git a/src/platform/datapath_raw_socket_win.c b/src/platform/datapath_raw_socket_win.c index 78adf4b9d1..0b71a28e22 100644 --- a/src/platform/datapath_raw_socket_win.c +++ b/src/platform/datapath_raw_socket_win.c @@ -77,15 +77,14 @@ CxPlatRemoveSocket( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawResolveRoute( - _In_ CXPLAT_SOCKET* Sock, +CxPlatRawResolveRoute( + _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_ROUTE* Route, _In_ uint8_t PathId, _In_ void* Context, _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback ) { - CXPLAT_SOCKET_RAW* Socket = CxPlatSocketToRaw(Sock); NETIO_STATUS Status = ERROR_SUCCESS; MIB_IPFORWARD_ROW2 IpforwardRow = {0}; CXPLAT_ROUTE_STATE State = Route->State; diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index 2dac8ae434..95ef101df6 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -45,7 +45,7 @@ CxPlatDataPathRouteWorkerUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatDataPathRouteWorkerInitialize( - _Inout_ CXPLAT_DATAPATH_RAW* DataPath + _Inout_ CXPLAT_DATAPATH_RAW* DataPath ) { QUIC_STATUS Status; @@ -102,14 +102,13 @@ CxPlatDataPathRouteWorkerInitialize( return Status; } - _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawDataPathInitialize( +CxPlatRawDataPathInitialize( _In_ uint32_t ClientRecvContextLength, _In_opt_ QUIC_EXECUTION_CONFIG* Config, _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, - _Out_ CXPLAT_DATAPATH_RAW** NewDataPath + _Out_ CXPLAT_DATAPATH_RAW** NewDataPath ) { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; @@ -180,8 +179,8 @@ RawDataPathInitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void -RawDataPathUninitialize( - _In_ CXPLAT_DATAPATH_RAW* Datapath +CxPlatRawDataPathUninitialize( + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { if (Datapath != NULL) { @@ -198,7 +197,7 @@ RawDataPathUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDataPathUninitializeComplete( - _In_ CXPLAT_DATAPATH_RAW* Datapath + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { #if DEBUG @@ -213,8 +212,8 @@ CxPlatDataPathUninitializeComplete( _IRQL_requires_max_(PASSIVE_LEVEL) void -RawDataPathUpdateConfig( - _In_ CXPLAT_DATAPATH_RAW* Datapath, +CxPlatRawDataPathUpdateConfig( + _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ) { @@ -223,8 +222,8 @@ RawDataPathUpdateConfig( _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t -RawDataPathGetSupportedFeatures( - _In_ CXPLAT_DATAPATH_RAW* Datapath +CxPlatRawDataPathGetSupportedFeatures( + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { return CXPLAT_DATAPATH_FEATURE_RAW; @@ -232,8 +231,8 @@ RawDataPathGetSupportedFeatures( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -RawDataPathIsPaddingPreferred( - _In_ CXPLAT_DATAPATH* Datapath +CxPlatRawDataPathIsPaddingPreferred( + _In_ CXPLAT_DATAPATH* Datapath ) { return FALSE; @@ -242,8 +241,8 @@ RawDataPathIsPaddingPreferred( _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS -RawDataPathGetLocalAddresses( - _In_ CXPLAT_DATAPATH* Datapath, +CxPlatRawDataPathGetLocalAddresses( + _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) CXPLAT_ADAPTER_ADDRESS** Addresses, _Out_ uint32_t* AddressesCount @@ -255,8 +254,8 @@ RawDataPathGetLocalAddresses( _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS -RawDataPathGetGatewayAddresses( - _In_ CXPLAT_DATAPATH* Datapath, +CxPlatRawDataPathGetGatewayAddresses( + _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) QUIC_ADDR** GatewayAddresses, _Out_ uint32_t* GatewayAddressesCount @@ -267,14 +266,12 @@ RawDataPathGetGatewayAddresses( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawSocketCreateUdp( - _In_ CXPLAT_DATAPATH_RAW* Raw, +CxPlatRawSocketCreateUdp( + _In_ CXPLAT_DATAPATH_RAW* Raw, _In_ const CXPLAT_UDP_CONFIG* Config, - _Out_ CXPLAT_SOCKET_RAW* Socket + _Inout_ CXPLAT_SOCKET_RAW* Socket ) { - #pragma warning(push) - #pragma warning(disable:6001) // Using uninitialized memory CXPLAT_DBG_ASSERT(Socket != NULL); QUIC_STATUS Status = QUIC_STATUS_SUCCESS; @@ -330,15 +327,14 @@ RawSocketCreateUdp( Socket = NULL; } } - #pragma warning(pop) return Status; } _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawCxPlatSocketCreateTcp( - _In_ CXPLAT_DATAPATH* Datapath, +CxPlatRawCxPlatSocketCreateTcp( + _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, _In_opt_ void* CallbackContext, @@ -350,8 +346,8 @@ RawCxPlatSocketCreateTcp( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawSocketCreateTcpListener( - _In_ CXPLAT_DATAPATH* Datapath, +CxPlatRawSocketCreateTcpListener( + _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ void* RecvCallbackContext, _Out_ CXPLAT_SOCKET_RAW** NewSocket @@ -362,14 +358,10 @@ RawSocketCreateTcpListener( _IRQL_requires_max_(PASSIVE_LEVEL) void -RawSocketDelete( +CxPlatRawSocketDelete( _In_ CXPLAT_SOCKET_RAW* Socket ) { - if (!Socket->RawDatapath) { - // Raw socket was not initialized. - return; - } CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); CxPlatRemoveSocket(&Socket->RawDatapath->SocketPool, Socket); CxPlatRundownReleaseAndWait(&Socket->Rundown); @@ -385,7 +377,7 @@ RawSocketDelete( _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 -RawSocketGetLocalMtu( +CxPlatRawSocketGetLocalMtu( _In_ CXPLAT_SOCKET_RAW* Socket ) { @@ -399,7 +391,7 @@ RawSocketGetLocalMtu( _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawRxEthernet( - _In_ const CXPLAT_DATAPATH_RAW* Datapath, + _In_ const CXPLAT_DATAPATH_RAW* Datapath, _In_reads_(PacketCount) CXPLAT_RECV_DATA** Packets, _In_ uint16_t PacketCount @@ -464,7 +456,7 @@ CxPlatDpRawRxEthernet( _IRQL_requires_max_(DISPATCH_LEVEL) void -RawRecvDataReturn( +CxPlatRawRecvDataReturn( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ) { @@ -474,7 +466,7 @@ RawRecvDataReturn( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* -RawSendDataAlloc( +CxPlatRawSendDataAlloc( _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) @@ -485,7 +477,7 @@ RawSendDataAlloc( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* -RawSendDataAllocBuffer( +CxPlatRawSendDataAllocBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ) @@ -496,7 +488,7 @@ RawSendDataAllocBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) void -RawSendDataFree( +CxPlatRawSendDataFree( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -505,7 +497,7 @@ RawSendDataFree( _IRQL_requires_max_(DISPATCH_LEVEL) void -RawSendDataFreeBuffer( +CxPlatRawSendDataFreeBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ) @@ -515,7 +507,7 @@ RawSendDataFreeBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -RawSendDataIsFull( +CxPlatRawSendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -526,7 +518,7 @@ RawSendDataIsFull( _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS -RawSocketSend( +CxPlatRawSocketSend( _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA* SendData diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 9ed199a7e9..2acef35267 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -993,7 +993,6 @@ CxPlatDpRawGetDatapathSize( return sizeof(XDP_DATAPATH) + (PartitionCount * sizeof(XDP_PARTITION)); } - _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatDpRawInitialize( @@ -1004,6 +1003,7 @@ CxPlatDpRawInitialize( { XDP_DATAPATH* Xdp = (XDP_DATAPATH*)Datapath; QUIC_STATUS Status; + CxPlatListInitializeHead(&Xdp->Interfaces); if (QUIC_FAILED(XdpLoadApi(XDP_API_VERSION_1, &Xdp->XdpApiLoadContext, &Xdp->XdpApi))) { Status = QUIC_STATUS_NOT_SUPPORTED; @@ -1284,17 +1284,13 @@ CxPlatDpRawUpdateConfig( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawSocketUpdateQeo( +CxPlatRawSocketUpdateQeo( _In_ CXPLAT_SOCKET_RAW* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, _In_ uint32_t OffloadCount ) { - if (!Socket->RawDatapath) { - // Raw socket was not created. - return QUIC_STATUS_INVALID_STATE; - } XDP_DATAPATH* Xdp = (XDP_DATAPATH*)Socket->RawDatapath; XDP_QUIC_CONNECTION Connections[2]; @@ -1383,11 +1379,10 @@ CxPlatDpRawClearPortBit( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDpRawPlumbRulesOnSocket( - _In_ CXPLAT_SOCKET_RAW* Sock, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ BOOLEAN IsCreated ) { - CXPLAT_SOCKET_RAW* Socket = (CXPLAT_SOCKET_RAW*)Sock; XDP_DATAPATH* Xdp = (XDP_DATAPATH*)Socket->RawDatapath; if (Socket->Wildcard) { XDP_RULE Rules[3] = {0}; @@ -1567,6 +1562,7 @@ CxPlatXdpRx( Packet->Queue = Queue; Packet->RouteStorage.Queue = Queue; Packet->RecvData.Route = &Packet->RouteStorage; + Packet->RecvData.Route->DatapathType = Packet->RecvData.DatapathType = CXPLAT_DATAPATH_TYPE_XDP; Packet->RecvData.PartitionIndex = PartitionIndex; CxPlatDpRawParseEthernet( @@ -1584,7 +1580,6 @@ CxPlatXdpRx( if (Packet->RecvData.Buffer) { Packet->RecvData.Allocated = TRUE; - Packet->RecvData.DatapathType = CXPLAT_DATAPATH_TYPE_XDP; Buffers[PacketCount++] = &Packet->RecvData; } else { CxPlatListPushEntry(&Queue->PartitionRxPool, (CXPLAT_SLIST_ENTRY*)Packet); @@ -1905,7 +1900,7 @@ CxPlatXdpExecute( } void -RawDataPathProcessCqe( +CxPlatRawDataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ) { diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 1c18d699dd..0d7fbf7f68 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -44,12 +44,13 @@ CxPlatDataPathInitialize( goto Error; } - Status = DataPathInitialize( - ClientRecvContextLength, - UdpCallbacks, - TcpCallbacks, - Config, - NewDataPath); + Status = + DataPathInitialize( + ClientRecvContextLength, + UdpCallbacks, + TcpCallbacks, + Config, + NewDataPath); if (QUIC_FAILED(Status)) { QuicTraceLogVerbose( DatapathInitFail, @@ -57,11 +58,12 @@ CxPlatDataPathInitialize( goto Error; } - Status = RawDataPathInitialize( - ClientRecvContextLength, - Config, - (*NewDataPath), - &((*NewDataPath)->RawDataPath)); + Status = + CxPlatRawDataPathInitialize( + ClientRecvContextLength, + Config, + (*NewDataPath), + &((*NewDataPath)->RawDataPath)); if (QUIC_FAILED(Status)) { QuicTraceLogVerbose( RawDatapathInitFail, @@ -82,7 +84,7 @@ CxPlatDataPathUninitialize( ) { if (Datapath->RawDataPath) { - RawDataPathUninitialize(Datapath->RawDataPath); + CxPlatRawDataPathUninitialize(Datapath->RawDataPath); } DataPathUninitialize(Datapath); } @@ -96,7 +98,7 @@ CxPlatDataPathUpdateConfig( { DataPathUpdateConfig(Datapath, Config); if (Datapath->RawDataPath) { - RawDataPathUpdateConfig(Datapath->RawDataPath, Config); + CxPlatRawDataPathUpdateConfig(Datapath->RawDataPath, Config); } } @@ -108,7 +110,7 @@ CxPlatDataPathGetSupportedFeatures( { if (Datapath->RawDataPath) { return DataPathGetSupportedFeatures(Datapath) | - RawDataPathGetSupportedFeatures(Datapath->RawDataPath); + CxPlatRawDataPathGetSupportedFeatures(Datapath->RawDataPath); } return DataPathGetSupportedFeatures(Datapath); } @@ -123,7 +125,7 @@ CxPlatDataPathIsPaddingPreferred( if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { return DataPathIsPaddingPreferred(Datapath); } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - return RawDataPathIsPaddingPreferred(Datapath); + return CxPlatRawDataPathIsPaddingPreferred(Datapath); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -280,10 +282,11 @@ CxPlatSocketCreateUdp( { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - Status = SocketCreateUdp( - Datapath, - Config, - NewSocket); + Status = + SocketCreateUdp( + Datapath, + Config, + NewSocket); if (QUIC_FAILED(Status)) { QuicTraceLogVerbose( SockCreateFail, @@ -292,10 +295,11 @@ CxPlatSocketCreateUdp( } if (Datapath->RawDataPath) { - Status = RawSocketCreateUdp( - Datapath->RawDataPath, - Config, - CxPlatSocketToRaw(*NewSocket)); + Status = + CxPlatRawSocketCreateUdp( + Datapath->RawDataPath, + Config, + CxPlatSocketToRaw(*NewSocket)); (*NewSocket)->RawSocketAvailable = QUIC_SUCCEEDED(Status); if (QUIC_FAILED(Status)) { QuicTraceLogVerbose( @@ -350,7 +354,7 @@ CxPlatSocketDelete( ) { if (Socket->RawSocketAvailable) { - RawSocketDelete(CxPlatSocketToRaw(Socket)); + CxPlatRawSocketDelete(CxPlatSocketToRaw(Socket)); } SocketDelete(Socket); } @@ -366,7 +370,7 @@ CxPlatSocketUpdateQeo( { if (Socket->RawSocketAvailable && !IS_LOOPBACK(Offloads[0].Address)) { - return RawSocketUpdateQeo(CxPlatSocketToRaw(Socket), Offloads, OffloadCount); + return CxPlatRawSocketUpdateQeo(CxPlatSocketToRaw(Socket), Offloads, OffloadCount); } return QUIC_STATUS_NOT_SUPPORTED; } @@ -380,7 +384,7 @@ CxPlatSocketGetLocalMtu( CXPLAT_DBG_ASSERT(Socket != NULL); if (Socket->RawSocketAvailable && !IS_LOOPBACK(Socket->RemoteAddress)) { - return RawSocketGetLocalMtu(CxPlatSocketToRaw(Socket)); + return CxPlatRawSocketGetLocalMtu(CxPlatSocketToRaw(Socket)); } return Socket->Mtu; } @@ -420,7 +424,7 @@ CxPlatRecvDataReturn( if (RecvDataChain->DatapathType == CXPLAT_DATAPATH_TYPE_USER) { RecvDataReturn(RecvDataChain); } else if (RecvDataChain->DatapathType == CXPLAT_DATAPATH_TYPE_XDP) { - RawRecvDataReturn(RecvDataChain); + CxPlatRawRecvDataReturn(RecvDataChain); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -439,7 +443,7 @@ CxPlatSendDataAlloc( if (Socket->Type == CXPLAT_SOCKET_UDP && Socket->RawSocketAvailable && !IS_LOOPBACK(Config->Route->RemoteAddress)) { - SendData = RawSendDataAlloc(CxPlatSocketToRaw(Socket), Config); + SendData = CxPlatRawSendDataAlloc(CxPlatSocketToRaw(Socket), Config); if (SendData) { DatapathType(SendData) = CXPLAT_DATAPATH_TYPE_XDP; } @@ -461,7 +465,7 @@ CxPlatSendDataFree( if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { SendDataFree(SendData); } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - RawSendDataFree(SendData); + CxPlatRawSendDataFree(SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -478,7 +482,7 @@ CxPlatSendDataAllocBuffer( if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { return SendDataAllocBuffer(SendData, MaxBufferLength); } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - return RawSendDataAllocBuffer(SendData, MaxBufferLength); + return CxPlatRawSendDataAllocBuffer(SendData, MaxBufferLength); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -495,7 +499,7 @@ CxPlatSendDataFreeBuffer( if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { SendDataFreeBuffer(SendData, Buffer); } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - RawSendDataFreeBuffer(SendData, Buffer); + CxPlatRawSendDataFreeBuffer(SendData, Buffer); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -510,7 +514,7 @@ CxPlatSendDataIsFull( if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { return SendDataIsFull(SendData); } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - return RawSendDataIsFull(SendData); + return CxPlatRawSendDataIsFull(SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -528,7 +532,7 @@ CxPlatSocketSend( if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { return SocketSend(Socket, Route, SendData); } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - return RawSocketSend(CxPlatSocketToRaw(Socket), Route, SendData); + return CxPlatRawSocketSend(CxPlatSocketToRaw(Socket), Route, SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -546,14 +550,14 @@ CxPlatDataPathProcessCqe( DATAPATH_IO_SQE* Sqe = CONTAINING_RECORD(CxPlatCqeUserData(Cqe), DATAPATH_IO_SQE, DatapathSqe); if (Sqe->IoType == DATAPATH_XDP_IO_RECV || Sqe->IoType == DATAPATH_XDP_IO_SEND) { - RawDataPathProcessCqe(Cqe); + CxPlatRawDataPathProcessCqe(Cqe); } else { DataPathProcessCqe(Cqe); } break; } case CXPLAT_CQE_TYPE_SOCKET_SHUTDOWN: { - RawDataPathProcessCqe(Cqe); + CxPlatRawDataPathProcessCqe(Cqe); break; } default: CXPLAT_DBG_ASSERT(FALSE); break; @@ -564,14 +568,13 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void QuicCopyRouteInfo( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute, - _In_ uint16_t DatapathType + _In_ CXPLAT_ROUTE* SrcRoute ) { - if (DatapathType == CXPLAT_DATAPATH_TYPE_XDP) { + if (SrcRoute->DatapathType == CXPLAT_DATAPATH_TYPE_XDP) { CxPlatCopyMemory(DstRoute, SrcRoute, (uint8_t*)&SrcRoute->State - (uint8_t*)SrcRoute); - CxPlatUpdateRoute(DstRoute, SrcRoute, DatapathType); - } else if (DatapathType == CXPLAT_DATAPATH_TYPE_USER) { + CxPlatUpdateRoute(DstRoute, SrcRoute); + } else if (SrcRoute->DatapathType == CXPLAT_DATAPATH_TYPE_USER) { *DstRoute = *SrcRoute; } else { CXPLAT_DBG_ASSERT(FALSE); @@ -587,7 +590,7 @@ CxPlatResolveRouteComplete( ) { if (Route->State != RouteResolved) { - RawResolveRouteComplete(Connection, Route, PhysicalAddress, PathId); + CxPlatRawResolveRouteComplete(Connection, Route, PhysicalAddress, PathId); } } @@ -606,7 +609,7 @@ CxPlatResolveRoute( { if (Socket->RawSocketAvailable && !IS_LOOPBACK(Route->RemoteAddress)) { - return RawResolveRoute(Socket, Route, PathId, Context, Callback); + return CxPlatRawResolveRoute(CxPlatSocketToRaw(Socket), Route, PathId, Context, Callback); } Route->State = RouteResolved; return QUIC_STATUS_SUCCESS; @@ -616,13 +619,12 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute, - _In_ uint16_t DatapathType + _In_ CXPLAT_ROUTE* SrcRoute ) { - if (DatapathType == CXPLAT_DATAPATH_TYPE_XDP && + if (SrcRoute->DatapathType == CXPLAT_DATAPATH_TYPE_XDP && !IS_LOOPBACK(SrcRoute->RemoteAddress)) { - RawUpdateRoute(DstRoute, SrcRoute); + CxPlatRawUpdateRoute(DstRoute, SrcRoute); } } diff --git a/src/platform/datapath_winkernel.c b/src/platform/datapath_winkernel.c index 784b000b4f..ceabc04be9 100644 --- a/src/platform/datapath_winkernel.c +++ b/src/platform/datapath_winkernel.c @@ -3118,11 +3118,9 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void QuicCopyRouteInfo( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute, - _In_ uint16_t DatapathType + _In_ CXPLAT_ROUTE* SrcRoute ) { - UNREFERENCED_PARAMETER(DatapathType); *DstRoute = *SrcRoute; } @@ -3162,11 +3160,9 @@ _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute, - _In_ uint16_t DatapathType + _In_ CXPLAT_ROUTE* SrcRoute ) { UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); - UNREFERENCED_PARAMETER(DatapathType); } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index f81226b89a..933f2f76c6 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -3419,7 +3419,7 @@ CxPlatDataPathUdpRecvComplete( SocketProc->DatapathProc->PartitionIndex % SocketProc->DatapathProc->Datapath->PartitionCount; Datagram->TypeOfService = (uint8_t)ECN; Datagram->Allocated = TRUE; - Datagram->DatapathType = CXPLAT_DATAPATH_TYPE_USER; + Datagram->Route->DatapathType = Datagram->DatapathType = CXPLAT_DATAPATH_TYPE_USER; Datagram->QueuedOnConnection = FALSE; RecvPayload += MessageLength; @@ -3677,7 +3677,7 @@ CxPlatDataPathTcpRecvComplete( Data->PartitionIndex = SocketProc->DatapathProc->PartitionIndex; Data->TypeOfService = 0; Data->Allocated = TRUE; - Data->DatapathType = CXPLAT_DATAPATH_TYPE_USER; + Data->Route->DatapathType = Data->DatapathType = CXPLAT_DATAPATH_TYPE_USER; Data->QueuedOnConnection = FALSE; IoBlock->ReferenceCount++; IoBlock = NULL; diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 597a20e5e1..80b6c11e91 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -82,6 +82,15 @@ typedef struct CXPLAT_SEND_DATA_COMMON { uint8_t ECN; // CXPLAT_ECN_TYPE } CXPLAT_SEND_DATA_COMMON; + +typedef enum CXPLAT_DATAPATH_TYPE { + CXPLAT_DATAPATH_TYPE_UNKNOWN = 0, + CXPLAT_DATAPATH_TYPE_USER, + CXPLAT_DATAPATH_TYPE_KERNEL, + CXPLAT_DATAPATH_TYPE_XDP, + // DPDK? +} CXPLAT_DATAPATH_TYPE; + #ifdef _KERNEL_MODE #define CXPLAT_BASE_REG_PATH L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\MsQuic\\Parameters\\" @@ -517,7 +526,7 @@ typedef struct CXPLAT_SOCKET_RAW CXPLAT_SOCKET_RAW; _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS SocketCreateUdp( - _In_ CXPLAT_DATAPATH* DataPath, + _In_ CXPLAT_DATAPATH* DataPath, _In_ const CXPLAT_UDP_CONFIG* Config, _Out_ CXPLAT_SOCKET** NewSocket ); @@ -560,20 +569,20 @@ DataPathInitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void DataPathUninitialize( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH* Datapath ); _IRQL_requires_max_(PASSIVE_LEVEL) void DataPathUpdateConfig( - _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_DATAPATH* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ); _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t DataPathGetSupportedFeatures( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH* Datapath ); _IRQL_requires_max_(DISPATCH_LEVEL) @@ -710,21 +719,21 @@ CxPlatGetRawSocketSize (); _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawSocketCreateUdp( - _In_ CXPLAT_DATAPATH_RAW* DataPath, +CxPlatRawSocketCreateUdp( + _In_ CXPLAT_DATAPATH_RAW* DataPath, _In_ const CXPLAT_UDP_CONFIG* Config, - _Out_ CXPLAT_SOCKET_RAW* NewSocket + _Inout_ CXPLAT_SOCKET_RAW* NewSocket ); _IRQL_requires_max_(PASSIVE_LEVEL) void -RawSocketDelete( +CxPlatRawSocketDelete( _In_ CXPLAT_SOCKET_RAW* Socket ); _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawDataPathInitialize( +CxPlatRawDataPathInitialize( _In_ uint32_t ClientRecvContextLength, _In_opt_ QUIC_EXECUTION_CONFIG* Config, _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, @@ -733,32 +742,32 @@ RawDataPathInitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void -RawDataPathUninitialize( - _In_ CXPLAT_DATAPATH_RAW* Datapath +CxPlatRawDataPathUninitialize( + _In_ CXPLAT_DATAPATH_RAW* Datapath ); _IRQL_requires_max_(PASSIVE_LEVEL) void -RawDataPathUpdateConfig( - _In_ CXPLAT_DATAPATH_RAW* Datapath, +CxPlatRawDataPathUpdateConfig( + _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ); _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t -RawDataPathGetSupportedFeatures( - _In_ CXPLAT_DATAPATH_RAW* Datapath +CxPlatRawDataPathGetSupportedFeatures( + _In_ CXPLAT_DATAPATH_RAW* Datapath ); _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -RawDataPathIsPaddingPreferred( - _In_ CXPLAT_DATAPATH* Datapath +CxPlatRawDataPathIsPaddingPreferred( + _In_ CXPLAT_DATAPATH* Datapath ); _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawSocketUpdateQeo( +CxPlatRawSocketUpdateQeo( _In_ CXPLAT_SOCKET_RAW* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, @@ -767,61 +776,61 @@ RawSocketUpdateQeo( _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 -RawSocketGetLocalMtu( +CxPlatRawSocketGetLocalMtu( _In_ CXPLAT_SOCKET_RAW* Socket ); _IRQL_requires_max_(DISPATCH_LEVEL) void -RawRecvDataReturn( +CxPlatRawRecvDataReturn( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ); _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* -RawSendDataAlloc( +CxPlatRawSendDataAlloc( _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ); _IRQL_requires_max_(DISPATCH_LEVEL) void -RawSendDataFree( +CxPlatRawSendDataFree( _In_ CXPLAT_SEND_DATA* SendData ); _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* -RawSendDataAllocBuffer( +CxPlatRawSendDataAllocBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ); _IRQL_requires_max_(DISPATCH_LEVEL) void -RawSendDataFreeBuffer( +CxPlatRawSendDataFreeBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ); _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -RawSendDataIsFull( +CxPlatRawSendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ); _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS -RawSocketSend( +CxPlatRawSocketSend( _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA* SendData ); void -RawResolveRouteComplete( +CxPlatRawResolveRouteComplete( _In_ void* Context, _Inout_ CXPLAT_ROUTE* Route, _In_reads_bytes_(6) const uint8_t* PhysicalAddress, @@ -830,8 +839,8 @@ RawResolveRouteComplete( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -RawResolveRoute( - _In_ CXPLAT_SOCKET* Sock, +CxPlatRawResolveRoute( + _In_ CXPLAT_SOCKET_RAW* Sock, _Inout_ CXPLAT_ROUTE* Route, _In_ uint8_t PathId, _In_ void* Context, @@ -839,13 +848,13 @@ RawResolveRoute( ); void -RawDataPathProcessCqe( +CxPlatRawDataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ); _IRQL_requires_max_(PASSIVE_LEVEL) void -RawUpdateRoute( +CxPlatRawUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, _In_ CXPLAT_ROUTE* SrcRoute ); From 00bcd08cd602f9ac7a74a8d88c2cfd4fc25048de Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Thu, 31 Aug 2023 02:21:26 -0700 Subject: [PATCH 58/87] fix build error for uwp and remove duplicate variable in raw socket --- src/platform/datapath_raw.h | 1 - src/platform/datapath_raw_dummy.c | 2 +- src/platform/datapath_raw_win.c | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index 66cb4131fb..fd3df16871 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -244,7 +244,6 @@ typedef struct CXPLAT_SOCKET_RAW { CXPLAT_RUNDOWN_REF Rundown; CXPLAT_DATAPATH_RAW* RawDatapath; // SOCKET AuxSocket; - void* CallbackContext; BOOLEAN Wildcard; // Using a wildcard local address. Optimization // to avoid always reading LocalAddress. BOOLEAN Connected; // Bound to a remote address diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index 5946b1be67..63c1a162aa 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -227,7 +227,7 @@ CxPlatRawResolveRouteComplete( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatRawResolveRoute( - _In_ CXPLAT_SOCKET* Sock, + _In_ CXPLAT_SOCKET_RAW* Sock, _Inout_ CXPLAT_ROUTE* Route, _In_ uint8_t PathId, _In_ void* Context, diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index 95ef101df6..6e78ffda41 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -277,7 +277,6 @@ CxPlatRawSocketCreateUdp( CxPlatRundownInitialize(&Socket->Rundown); Socket->RawDatapath = Raw; - Socket->CallbackContext = Config->CallbackContext; Socket->CibirIdLength = Config->CibirIdLength; Socket->CibirIdOffsetSrc = Config->CibirIdOffsetSrc; Socket->CibirIdOffsetDst = Config->CibirIdOffsetDst; @@ -436,7 +435,7 @@ CxPlatDpRawRxEthernet( CXPLAT_DBG_ASSERT(Packets[i+1]->Next == NULL); i++; } - Datapath->ParentDataPath->UdpHandlers.Receive(CxPlatRawToSocket(Socket), Socket->CallbackContext, (CXPLAT_RECV_DATA*)PacketChain); + Datapath->ParentDataPath->UdpHandlers.Receive(CxPlatRawToSocket(Socket), Socket->ClientContext, (CXPLAT_RECV_DATA*)PacketChain); } else if (PacketChain->Reserved == L4_TYPE_TCP_SYN || PacketChain->Reserved == L4_TYPE_TCP_SYNACK) { CxPlatDpRawSocketAckSyn(Socket, PacketChain); CxPlatDpRawRxFree(PacketChain); From 76daf503c1786d779215dbabdbd9895896a9110d Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Thu, 31 Aug 2023 20:18:25 -0700 Subject: [PATCH 59/87] set socket before start receiving --- src/platform/datapath_winuser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 933f2f76c6..e6b1aa1b88 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1932,13 +1932,13 @@ SocketCreateUdp( // Must set output pointer before starting receive path, as the receive path // will try to use the output. // + *NewSocket = Socket; for (uint16_t i = 0; i < SocketCount; i++) { CxPlatDataPathStartReceiveAsync(&Socket->PerProcSockets[i]); Socket->PerProcSockets[i].IoStarted = TRUE; } - *NewSocket = Socket; Socket = NULL; RawSocket = NULL; Status = QUIC_STATUS_SUCCESS; From fbc601ee9804f1a54372af9d3cff2abcc699693f Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Thu, 31 Aug 2023 20:20:37 -0700 Subject: [PATCH 60/87] add dummy to clog --- scripts/clog.inputs | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/clog.inputs b/scripts/clog.inputs index b22e83f406..0f8c533436 100644 --- a/scripts/clog.inputs +++ b/scripts/clog.inputs @@ -14,6 +14,7 @@ ../src/platform/hashtable.c ../src/platform/datapath_winuser.c ../src/platform/datapath_raw_dpdk.c +../src/platform/datapath_raw_dummy.c ../src/platform/datapath_raw_socket.c ../src/platform/datapath_raw_socket_win.c ../src/platform/datapath_raw_socket_linux.c From 1319b224189f5e74f365977f877fca3f0cce2b52 Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Thu, 31 Aug 2023 21:04:11 -0700 Subject: [PATCH 61/87] add clog files for dummy --- src/generated/linux/datapath_raw_dummy.c.clog.h | 12 ++++++++++++ .../linux/quic.clog_datapath_raw_dummy.c.clog.h.c | 1 + 2 files changed, 13 insertions(+) create mode 100644 src/generated/linux/datapath_raw_dummy.c.clog.h create mode 100644 src/generated/linux/quic.clog_datapath_raw_dummy.c.clog.h.c diff --git a/src/generated/linux/datapath_raw_dummy.c.clog.h b/src/generated/linux/datapath_raw_dummy.c.clog.h new file mode 100644 index 0000000000..46da66e527 --- /dev/null +++ b/src/generated/linux/datapath_raw_dummy.c.clog.h @@ -0,0 +1,12 @@ +#ifndef CLOG_DO_NOT_INCLUDE_HEADER +#include +#endif +#ifdef __cplusplus +extern "C" { +#endif +#ifdef __cplusplus +} +#endif +#ifdef CLOG_INLINE_IMPLEMENTATION +#include "quic.clog_datapath_raw_dummy.c.clog.h.c" +#endif diff --git a/src/generated/linux/quic.clog_datapath_raw_dummy.c.clog.h.c b/src/generated/linux/quic.clog_datapath_raw_dummy.c.clog.h.c new file mode 100644 index 0000000000..60649ebb50 --- /dev/null +++ b/src/generated/linux/quic.clog_datapath_raw_dummy.c.clog.h.c @@ -0,0 +1 @@ +#include From 246b2886c6bff95f2b2531934e536e15a9083049 Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Thu, 31 Aug 2023 22:07:08 -0700 Subject: [PATCH 62/87] fix dependency for cargo on windows --- .github/workflows/cargo.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index f6e64d724c..19abadd627 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -26,7 +26,11 @@ jobs: egress-policy: audit - name: Checkout repository uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - - name: Prepare Machine + - name: Prepare Machine for Windows + if: runner.os == 'Windows' + run: scripts/prepare-machine.ps1 -Tls openssl -ForBuild -UseXdp + - name: Prepare Machine for Others + if: runner.os != 'Windows' run: scripts/prepare-machine.ps1 -Tls openssl -ForBuild shell: pwsh - name: Install Perl From d2759c388533e6d43e20c2d4517c4017ee58996a Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Thu, 31 Aug 2023 22:07:27 -0700 Subject: [PATCH 63/87] remove dummy clog files --- scripts/clog.inputs | 1 - src/generated/linux/datapath_raw_dummy.c.clog.h | 12 ------------ .../linux/quic.clog_datapath_raw_dummy.c.clog.h.c | 1 - 3 files changed, 14 deletions(-) delete mode 100644 src/generated/linux/datapath_raw_dummy.c.clog.h delete mode 100644 src/generated/linux/quic.clog_datapath_raw_dummy.c.clog.h.c diff --git a/scripts/clog.inputs b/scripts/clog.inputs index 0f8c533436..b22e83f406 100644 --- a/scripts/clog.inputs +++ b/scripts/clog.inputs @@ -14,7 +14,6 @@ ../src/platform/hashtable.c ../src/platform/datapath_winuser.c ../src/platform/datapath_raw_dpdk.c -../src/platform/datapath_raw_dummy.c ../src/platform/datapath_raw_socket.c ../src/platform/datapath_raw_socket_win.c ../src/platform/datapath_raw_socket_linux.c diff --git a/src/generated/linux/datapath_raw_dummy.c.clog.h b/src/generated/linux/datapath_raw_dummy.c.clog.h deleted file mode 100644 index 46da66e527..0000000000 --- a/src/generated/linux/datapath_raw_dummy.c.clog.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef CLOG_DO_NOT_INCLUDE_HEADER -#include -#endif -#ifdef __cplusplus -extern "C" { -#endif -#ifdef __cplusplus -} -#endif -#ifdef CLOG_INLINE_IMPLEMENTATION -#include "quic.clog_datapath_raw_dummy.c.clog.h.c" -#endif diff --git a/src/generated/linux/quic.clog_datapath_raw_dummy.c.clog.h.c b/src/generated/linux/quic.clog_datapath_raw_dummy.c.clog.h.c deleted file mode 100644 index 60649ebb50..0000000000 --- a/src/generated/linux/quic.clog_datapath_raw_dummy.c.clog.h.c +++ /dev/null @@ -1 +0,0 @@ -#include From 0ec6dcad6a927e7a6b4d8b1fa8deaa301b3c3fdb Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Thu, 31 Aug 2023 23:18:16 -0700 Subject: [PATCH 64/87] remove AuxSocket --- src/platform/datapath_raw.h | 3 +-- src/platform/datapath_raw_dummy.c | 2 +- src/platform/datapath_raw_socket_win.c | 11 ----------- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index fd3df16871..1d541cb40b 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -242,8 +242,7 @@ typedef struct CXPLAT_SOCKET_RAW { CXPLAT_HASHTABLE_ENTRY Entry; CXPLAT_RUNDOWN_REF Rundown; - CXPLAT_DATAPATH_RAW* RawDatapath; // - SOCKET AuxSocket; + CXPLAT_DATAPATH_RAW* RawDatapath; BOOLEAN Wildcard; // Using a wildcard local address. Optimization // to avoid always reading LocalAddress. BOOLEAN Connected; // Bound to a remote address diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index 63c1a162aa..b2ced083af 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -259,4 +259,4 @@ CxPlatRawUpdateRoute( { UNREFERENCED_PARAMETER(DstRoute); UNREFERENCED_PARAMETER(SrcRoute); -} \ No newline at end of file +} diff --git a/src/platform/datapath_raw_socket_win.c b/src/platform/datapath_raw_socket_win.c index 0b71a28e22..17da95fe96 100644 --- a/src/platform/datapath_raw_socket_win.c +++ b/src/platform/datapath_raw_socket_win.c @@ -61,17 +61,6 @@ CxPlatRemoveSocket( { CxPlatRwLockAcquireExclusive(&Pool->Lock); CxPlatHashtableRemove(&Pool->Sockets, &Socket->Entry, NULL); - - if (closesocket(Socket->AuxSocket) == SOCKET_ERROR) { - int Error = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - Error, - "closesocket"); - } - CxPlatRwLockReleaseExclusive(&Pool->Lock); } From 599ddaa4dcf0c4a12456d14a354e452fb45139b2 Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Thu, 31 Aug 2023 23:34:23 -0700 Subject: [PATCH 65/87] add pwsh for cargo setup --- .github/workflows/cargo.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index 19abadd627..348185f834 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -29,6 +29,7 @@ jobs: - name: Prepare Machine for Windows if: runner.os == 'Windows' run: scripts/prepare-machine.ps1 -Tls openssl -ForBuild -UseXdp + shell: pwsh - name: Prepare Machine for Others if: runner.os != 'Windows' run: scripts/prepare-machine.ps1 -Tls openssl -ForBuild From 5e5cdffee855c3c9c7d08c82e86dc0d8dbb0bd03 Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Thu, 31 Aug 2023 23:35:58 -0700 Subject: [PATCH 66/87] clog fix --- .../linux/datapath_raw_socket_win.c.clog.h | 44 +++++++-------- .../datapath_raw_socket_win.c.clog.h.lttng.h | 54 +++++++++---------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/generated/linux/datapath_raw_socket_win.c.clog.h b/src/generated/linux/datapath_raw_socket_win.c.clog.h index a3558c6d72..fa42adfcb8 100644 --- a/src/generated/linux/datapath_raw_socket_win.c.clog.h +++ b/src/generated/linux/datapath_raw_socket_win.c.clog.h @@ -67,28 +67,6 @@ tracepoint(CLOG_DATAPATH_RAW_SOCKET_WIN_C, LibraryErrorStatus , arg2, arg3);\ -/*---------------------------------------------------------- -// Decoder Ring for DatapathErrorStatus -// [data][%p] ERROR, %u, %s. -// QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - Error, - "closesocket"); -// arg2 = arg2 = Socket = arg2 -// arg3 = arg3 = Error = arg3 -// arg4 = arg4 = "closesocket" = arg4 -----------------------------------------------------------*/ -#ifndef _clog_5_ARGS_TRACE_DatapathErrorStatus -#define _clog_5_ARGS_TRACE_DatapathErrorStatus(uniqueId, encoded_arg_string, arg2, arg3, arg4)\ -tracepoint(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathErrorStatus , arg2, arg3, arg4);\ - -#endif - - - - /*---------------------------------------------------------- // Decoder Ring for DatapathGetRouteStart // [data][%p] Querying route, local=%!ADDR!, remote=%!ADDR! @@ -111,6 +89,28 @@ tracepoint(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathGetRouteStart , arg2, arg3_le +/*---------------------------------------------------------- +// Decoder Ring for DatapathErrorStatus +// [data][%p] ERROR, %u, %s. +// QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + Status, + "GetBestRoute2"); +// arg2 = arg2 = Socket = arg2 +// arg3 = arg3 = Status = arg3 +// arg4 = arg4 = "GetBestRoute2" = arg4 +----------------------------------------------------------*/ +#ifndef _clog_5_ARGS_TRACE_DatapathErrorStatus +#define _clog_5_ARGS_TRACE_DatapathErrorStatus(uniqueId, encoded_arg_string, arg2, arg3, arg4)\ +tracepoint(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathErrorStatus , arg2, arg3, arg4);\ + +#endif + + + + /*---------------------------------------------------------- // Decoder Ring for DatapathGetRouteComplete // [data][%p] Query route result: %!ADDR! diff --git a/src/generated/linux/datapath_raw_socket_win.c.clog.h.lttng.h b/src/generated/linux/datapath_raw_socket_win.c.clog.h.lttng.h index e32b9a37ad..343928bc40 100644 --- a/src/generated/linux/datapath_raw_socket_win.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_raw_socket_win.c.clog.h.lttng.h @@ -51,33 +51,6 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_SOCKET_WIN_C, LibraryErrorStatus, -/*---------------------------------------------------------- -// Decoder Ring for DatapathErrorStatus -// [data][%p] ERROR, %u, %s. -// QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - Error, - "closesocket"); -// arg2 = arg2 = Socket = arg2 -// arg3 = arg3 = Error = arg3 -// arg4 = arg4 = "closesocket" = arg4 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathErrorStatus, - TP_ARGS( - const void *, arg2, - unsigned int, arg3, - const char *, arg4), - TP_FIELDS( - ctf_integer_hex(uint64_t, arg2, arg2) - ctf_integer(unsigned int, arg3, arg3) - ctf_string(arg4, arg4) - ) -) - - - /*---------------------------------------------------------- // Decoder Ring for DatapathGetRouteStart // [data][%p] Querying route, local=%!ADDR!, remote=%!ADDR! @@ -109,6 +82,33 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathGetRouteStart, +/*---------------------------------------------------------- +// Decoder Ring for DatapathErrorStatus +// [data][%p] ERROR, %u, %s. +// QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + Status, + "GetBestRoute2"); +// arg2 = arg2 = Socket = arg2 +// arg3 = arg3 = Status = arg3 +// arg4 = arg4 = "GetBestRoute2" = arg4 +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathErrorStatus, + TP_ARGS( + const void *, arg2, + unsigned int, arg3, + const char *, arg4), + TP_FIELDS( + ctf_integer_hex(uint64_t, arg2, arg2) + ctf_integer(unsigned int, arg3, arg3) + ctf_string(arg4, arg4) + ) +) + + + /*---------------------------------------------------------- // Decoder Ring for DatapathGetRouteComplete // [data][%p] Query route result: %!ADDR! From a75ee4bd071b9aa488c9936d866a4650629f4697 Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Mon, 4 Sep 2023 17:17:53 -0700 Subject: [PATCH 67/87] add include dir for cargo --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 2a88bb31fd..6fb3b5f0e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ include = [ "/src/generated", "/src/manifest", "/src/platform", + "/artifacts/xdp/include", "/THIRD-PARTY-NOTICES", ] From 470ac4f9aaed68701a5a730956bcf255f54e9be7 Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Wed, 6 Sep 2023 18:12:26 -0700 Subject: [PATCH 68/87] [WIP] fix cargo and qtip --- .github/workflows/cargo.yml | 2 +- src/platform/datapath_raw.h | 2 +- src/platform/datapath_raw_socket_win.c | 276 +++++++++++++++++++++++++ src/platform/datapath_raw_win.c | 10 +- src/platform/datapath_win.c | 8 +- src/platform/datapath_winuser.c | 34 ++- src/platform/platform_internal.h | 4 + 7 files changed, 328 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index 348185f834..5c3b07a8d6 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -50,4 +50,4 @@ jobs: - name: Cargo test run: cargo test --all - name: Cargo Publish (dry run) - run: cargo publish --dry-run + run: cargo publish --dry-run --allow-dirty diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index 1d541cb40b..67d9866eba 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -243,6 +243,7 @@ typedef struct CXPLAT_SOCKET_RAW { CXPLAT_HASHTABLE_ENTRY Entry; CXPLAT_RUNDOWN_REF Rundown; CXPLAT_DATAPATH_RAW* RawDatapath; + SOCKET AuxSocket; BOOLEAN Wildcard; // Using a wildcard local address. Optimization // to avoid always reading LocalAddress. BOOLEAN Connected; // Bound to a remote address @@ -250,7 +251,6 @@ typedef struct CXPLAT_SOCKET_RAW { uint8_t CibirIdOffsetSrc; // CIBIR ID offset in source CID uint8_t CibirIdOffsetDst; // CIBIR ID offset in destination CID uint8_t CibirId[6]; // CIBIR ID data - BOOLEAN UseTcp; // Quic over TCP CXPLAT_SEND_DATA* PausedTcpSend; // Paused TCP send data *before* framing CXPLAT_SEND_DATA* CachedRstSend; // Cached TCP RST send data *after* framing diff --git a/src/platform/datapath_raw_socket_win.c b/src/platform/datapath_raw_socket_win.c index 17da95fe96..9a805d4f5d 100644 --- a/src/platform/datapath_raw_socket_win.c +++ b/src/platform/datapath_raw_socket_win.c @@ -241,9 +241,275 @@ CxPlatTryAddSocket( QUIC_STATUS Status = QUIC_STATUS_SUCCESS; CXPLAT_HASHTABLE_LOOKUP_CONTEXT Context; CXPLAT_HASHTABLE_ENTRY* Entry; + int Result; + int Option; + QUIC_ADDR MappedAddress = {0}; + SOCKET TempUdpSocket = INVALID_SOCKET; + int AssignedLocalAddressLength; + + // + // Get (and reserve) a transport layer port from the OS networking stack by + // binding an auxiliary (dual stack) socket. + // + + Socket->AuxSocket = + socket( + AF_INET6, + Socket->UseTcp ? SOCK_STREAM : SOCK_DGRAM, + Socket->UseTcp ? IPPROTO_TCP : IPPROTO_UDP); + if (Socket->AuxSocket == INVALID_SOCKET) { + int WsaError = SocketError(); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + WsaError, + "socket"); + Status = HRESULT_FROM_WIN32(WsaError); + goto Error; + } + + if (Socket->UseTcp) + { + Option = FALSE; + Result = + setsockopt( + Socket->AuxSocket, + IPPROTO_IPV6, + IPV6_V6ONLY, + (char*)&Option, + sizeof(Option)); + if (Result == SOCKET_ERROR) { + int WsaError = SocketError(); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + WsaError, + "Set IPV6_V6ONLY"); + Status = HRESULT_FROM_WIN32(WsaError); + goto Error; + } + + if (Socket->CibirIdLength) { + Option = TRUE; + Result = + setsockopt( + Socket->AuxSocket, + SOL_SOCKET, + SO_REUSEADDR, + (char*)&Option, + sizeof(Option)); + if (Result == SOCKET_ERROR) { + int WsaError = SocketError(); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + WsaError, + "Set SO_REUSEADDR"); + Status = HRESULT_FROM_WIN32(WsaError); + goto Error; + } + } + + CxPlatConvertToMappedV6(&Socket->LocalAddress, &MappedAddress); +#if QUIC_ADDRESS_FAMILY_INET6 != AF_INET6 + if (MappedAddress.Ipv6.sin6_family == QUIC_ADDRESS_FAMILY_INET6) { + MappedAddress.Ipv6.sin6_family = AF_INET6; + } +#endif + } CxPlatRwLockAcquireExclusive(&Pool->Lock); + if (Socket->UseTcp) + { + QUIC_ADDR_STR LocalAddressString = {0}; + QuicAddrToString(&MappedAddress, &LocalAddressString); + QuicTraceLogVerbose( + DatapathTcpAuxBinding, + "[data][%p] Binding TCP socket to %s", + Socket, + LocalAddressString.Address); + Result = + bind( + Socket->AuxSocket, + (struct sockaddr*)&MappedAddress, + sizeof(MappedAddress)); + if (Result == SOCKET_ERROR) { + fprintf(stderr, "bind failed: %d\n", WSAGetLastError()); + int WsaError = SocketError(); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + WsaError, + "bind"); + CxPlatRwLockReleaseExclusive(&Pool->Lock); + Status = HRESULT_FROM_WIN32(WsaError); + goto Error; + } + fprintf(stderr, "bind succeeded\n"); + + if (Socket->Connected) + { + CxPlatZeroMemory(&MappedAddress, sizeof(MappedAddress)); + CxPlatConvertToMappedV6(&Socket->RemoteAddress, &MappedAddress); + + #if QUIC_ADDRESS_FAMILY_INET6 != AF_INET6 + if (MappedAddress.Ipv6.sin6_family == QUIC_ADDRESS_FAMILY_INET6) { + MappedAddress.Ipv6.sin6_family = AF_INET6; + } + #endif + // + // Create a temporary UDP socket bound to a wildcard port + // and connect this socket to the remote address. + // By doing this, the OS will select a local address for us. + // + uint16_t LocalPortChosen = 0; + QUIC_ADDR TempLocalAddress = {0}; + AssignedLocalAddressLength = sizeof(TempLocalAddress); + Result = + getsockname( + Socket->AuxSocket, + (struct sockaddr*)&TempLocalAddress, + &AssignedLocalAddressLength); + if (Result == SOCKET_ERROR) { + int WsaError = SocketError(); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + WsaError, + "getsockname"); + CxPlatRwLockReleaseExclusive(&Pool->Lock); + Status = HRESULT_FROM_WIN32(WsaError); + goto Error; + } + LocalPortChosen = TempLocalAddress.Ipv4.sin_port; + fprintf(stderr, "LocalPortChosen %d\n", (int)LocalPortChosen); + TempUdpSocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (TempUdpSocket == INVALID_SOCKET) { + int WsaError = SocketError(); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + WsaError, + "temp udp socket"); + CxPlatRwLockReleaseExclusive(&Pool->Lock); + Status = HRESULT_FROM_WIN32(WsaError); + goto Error; + } + + Option = FALSE; + Result = + setsockopt( + TempUdpSocket, + IPPROTO_IPV6, + IPV6_V6ONLY, + (char*)&Option, + sizeof(Option)); + if (Result == SOCKET_ERROR) { + int WsaError = SocketError(); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + WsaError, + "Set IPV6_V6ONLY (temp udp socket)"); + CxPlatRwLockReleaseExclusive(&Pool->Lock); + Status = HRESULT_FROM_WIN32(WsaError); + goto Error; + } + + CxPlatZeroMemory(&TempLocalAddress, sizeof(TempLocalAddress)); + CxPlatConvertToMappedV6(&Socket->LocalAddress, &TempLocalAddress); + TempLocalAddress.Ipv4.sin_port = 0; + Result = + bind( + TempUdpSocket, + (struct sockaddr*)&TempLocalAddress, + sizeof(TempLocalAddress)); + if (Result == SOCKET_ERROR) { + int WsaError = SocketError(); + fprintf(stderr, "bind failed: %d\n", WsaError); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + WsaError, + "bind (temp udp socket)"); + CxPlatRwLockReleaseExclusive(&Pool->Lock); + Status = HRESULT_FROM_WIN32(WsaError); + goto Error; + } + + Result = + connect( + TempUdpSocket, + (struct sockaddr*)&MappedAddress, + sizeof(MappedAddress)); + if (Result == SOCKET_ERROR) { + int WsaError = SocketError(); + fprintf(stderr, "connect failed (temp udp socket): %d\n", WsaError); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + WsaError, + "connect failed (temp udp socket)"); + CxPlatRwLockReleaseExclusive(&Pool->Lock); + Status = HRESULT_FROM_WIN32(WsaError); + goto Error; + } + + AssignedLocalAddressLength = sizeof(Socket->LocalAddress); + Result = + getsockname( + TempUdpSocket, + (struct sockaddr*)&Socket->LocalAddress, + &AssignedLocalAddressLength); + if (Result == SOCKET_ERROR) { + int WsaError = SocketError(); + fprintf(stderr, "getsockname failed: %d\n", WsaError); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + WsaError, + "getsockname (temp udp socket)"); + CxPlatRwLockReleaseExclusive(&Pool->Lock); + Status = HRESULT_FROM_WIN32(WsaError); + goto Error; + } + CxPlatConvertFromMappedV6(&Socket->LocalAddress, &Socket->LocalAddress); + Socket->LocalAddress.Ipv4.sin_port = LocalPortChosen; + CXPLAT_FRE_ASSERT(Socket->LocalAddress.Ipv4.sin_port != 0); + } else { + AssignedLocalAddressLength = sizeof(Socket->LocalAddress); + Result = + getsockname( + Socket->AuxSocket, + (struct sockaddr*)&Socket->LocalAddress, + &AssignedLocalAddressLength); + if (Result == SOCKET_ERROR) { + int WsaError = SocketError(); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + WsaError, + "getsockname"); + CxPlatRwLockReleaseExclusive(&Pool->Lock); + Status = HRESULT_FROM_WIN32(WsaError); + goto Error; + } + CxPlatConvertFromMappedV6(&Socket->LocalAddress, &Socket->LocalAddress); + } + } + Entry = CxPlatHashtableLookup(&Pool->Sockets, Socket->LocalAddress.Ipv4.sin_port, &Context); while (Entry != NULL) { CXPLAT_SOCKET_RAW* Temp = CXPLAT_CONTAINING_RECORD(Entry, CXPLAT_SOCKET_RAW, Entry); @@ -259,5 +525,15 @@ CxPlatTryAddSocket( CxPlatRwLockReleaseExclusive(&Pool->Lock); +Error: + + if (QUIC_FAILED(Status) && Socket->AuxSocket != INVALID_SOCKET) { + closesocket(Socket->AuxSocket); + } + + if (TempUdpSocket != INVALID_SOCKET) { + closesocket(TempUdpSocket); + } + return Status; } diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index 6e78ffda41..7f29836bda 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -280,7 +280,6 @@ CxPlatRawSocketCreateUdp( Socket->CibirIdLength = Config->CibirIdLength; Socket->CibirIdOffsetSrc = Config->CibirIdOffsetSrc; Socket->CibirIdOffsetDst = Config->CibirIdOffsetDst; - Socket->UseTcp = Raw->UseTcp; if (Config->CibirIdLength) { memcpy(Socket->CibirId, Config->CibirId, Config->CibirIdLength); } @@ -288,10 +287,16 @@ CxPlatRawSocketCreateUdp( // TODO: remove Socket address settings if (Config->RemoteAddress) { CXPLAT_FRE_ASSERT(!QuicAddrIsWildCard(Config->RemoteAddress)); // No wildcard remote addresses allowed. + if (Socket->UseTcp) { + Socket->RemoteAddress = *Config->RemoteAddress; + } Socket->Connected = TRUE; } if (Config->LocalAddress) { + if (Socket->UseTcp) { + Socket->LocalAddress = *Config->LocalAddress; + } if (QuicAddrIsWildCard(Config->LocalAddress)) { if (!Socket->Connected) { Socket->Wildcard = TRUE; @@ -302,6 +307,9 @@ CxPlatRawSocketCreateUdp( goto Error; } } else { + if (Socket->UseTcp) { + QuicAddrSetFamily(&Socket->LocalAddress, QUIC_ADDRESS_FAMILY_INET6); + } if (!Socket->Connected) { Socket->Wildcard = TRUE; } diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 0d7fbf7f68..f6164d7fbe 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -305,6 +305,10 @@ CxPlatSocketCreateUdp( QuicTraceLogVerbose( RawSockCreateFail, "[sock] Failed to create raw socket, status:%d", Status); + if (Datapath->UseTcp) { + CxPlatSocketDelete(*NewSocket); + goto Error; + } Status = QUIC_STATUS_SUCCESS; } } @@ -440,9 +444,9 @@ CxPlatSendDataAlloc( { CXPLAT_SEND_DATA* SendData = NULL; // TODO: fallback? - if (Socket->Type == CXPLAT_SOCKET_UDP && + if (Socket->UseTcp || (Socket->Type == CXPLAT_SOCKET_UDP && Socket->RawSocketAvailable && - !IS_LOOPBACK(Config->Route->RemoteAddress)) { + !IS_LOOPBACK(Config->Route->RemoteAddress))) { SendData = CxPlatRawSendDataAlloc(CxPlatSocketToRaw(Socket), Config); if (SendData) { DatapathType(SendData) = CXPLAT_DATAPATH_TYPE_XDP; diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index e6b1aa1b88..c84e630eb6 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -832,6 +832,11 @@ DataPathInitialize( if (TcpCallbacks) { Datapath->TcpHandlers = *TcpCallbacks; } + + if (Config && (Config->Flags & QUIC_EXECUTION_CONFIG_FLAG_QTIP)) { + Datapath->UseTcp = TRUE; + } + Datapath->PartitionCount = (uint16_t)PartitionCount; CxPlatRefInitializeEx(&Datapath->RefCount, Datapath->PartitionCount); Datapath->UseRio = Config && !!(Config->Flags & QUIC_EXECUTION_CONFIG_FLAG_RIO); @@ -1388,6 +1393,7 @@ SocketCreateUdp( Socket->HasFixedRemoteAddress = (Config->RemoteAddress != NULL); Socket->Type = CXPLAT_SOCKET_UDP; Socket->UseRio = Datapath->UseRio; + Socket->UseTcp = Datapath->UseTcp; if (Config->LocalAddress) { CxPlatConvertToMappedV6(Config->LocalAddress, &Socket->LocalAddress); } else { @@ -1399,6 +1405,13 @@ SocketCreateUdp( } CxPlatRefInitializeEx(&Socket->RefCount, SocketCount); + if (Datapath->UseTcp) { + // + // Skip normal socket settings to use AuxSocket in raw socket + // + goto Skip; + } + Socket->RecvBufLen = (Datapath->Features & CXPLAT_DATAPATH_FEATURE_RECV_COALESCING) ? MAX_URO_PAYLOAD_LENGTH : @@ -1922,6 +1935,8 @@ SocketCreateUdp( CxPlatConvertFromMappedV6(&Socket->LocalAddress, &Socket->LocalAddress); +Skip: + if (Config->RemoteAddress != NULL) { Socket->RemoteAddress = *Config->RemoteAddress; } else { @@ -1934,9 +1949,11 @@ SocketCreateUdp( // *NewSocket = Socket; - for (uint16_t i = 0; i < SocketCount; i++) { - CxPlatDataPathStartReceiveAsync(&Socket->PerProcSockets[i]); - Socket->PerProcSockets[i].IoStarted = TRUE; + if (!Socket->UseTcp) { + for (uint16_t i = 0; i < SocketCount; i++) { + CxPlatDataPathStartReceiveAsync(&Socket->PerProcSockets[i]); + Socket->PerProcSockets[i].IoStarted = TRUE; + } } Socket = NULL; @@ -2453,6 +2470,12 @@ SocketCreateTcpListener( return Status; } +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatSocketRelease( + _In_ CXPLAT_SOCKET* Socket + ); + _IRQL_requires_max_(PASSIVE_LEVEL) void SocketDelete( @@ -2468,6 +2491,11 @@ SocketDelete( CXPLAT_DBG_ASSERT(!Socket->Uninitialized); Socket->Uninitialized = TRUE; + if (Socket->UseTcp) { + CxPlatSocketRelease(Socket); + return; + } + const uint16_t SocketCount = Socket->NumPerProcessorSockets ? (uint16_t)CxPlatProcMaxCount() : 1; for (uint16_t i = 0; i < SocketCount; ++i) { diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 80b6c11e91..63930d6c5e 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -433,6 +433,8 @@ typedef struct CXPLAT_DATAPATH { uint8_t Uninitialized : 1; uint8_t Freed : 1; + uint8_t UseTcp : 1; + CXPLAT_DATAPATH_RAW* RawDataPath; // @@ -511,6 +513,8 @@ typedef struct CXPLAT_SOCKET { uint8_t Uninitialized : 1; uint8_t Freed : 1; + uint8_t UseTcp : 1; // Quic over TCP + uint8_t RawSocketAvailable : 1; // From 5dc33e935f6aa2e3b3502ebde4551c3a98cb3ca0 Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Wed, 6 Sep 2023 19:28:02 -0700 Subject: [PATCH 69/87] fix clog, qtip, rename private raw functions and cleanup --- .../linux/datapath_raw_socket_win.c.clog.h | 68 ++++++++++------ .../datapath_raw_socket_win.c.clog.h.lttng.h | 77 ++++++++++++------- src/manifest/clog.sidecar | 21 +++++ src/platform/datapath_raw_dummy.c | 44 +++++------ src/platform/datapath_raw_socket.c | 17 ++-- src/platform/datapath_raw_socket_win.c | 56 ++++++++------ src/platform/datapath_raw_win.c | 40 +++++----- src/platform/datapath_raw_xdp_win.c | 4 +- src/platform/datapath_win.c | 44 +++++------ src/platform/datapath_winuser.c | 14 ++-- src/platform/platform_internal.h | 44 +++++------ 11 files changed, 251 insertions(+), 178 deletions(-) diff --git a/src/generated/linux/datapath_raw_socket_win.c.clog.h b/src/generated/linux/datapath_raw_socket_win.c.clog.h index fa42adfcb8..a0a23ed2f6 100644 --- a/src/generated/linux/datapath_raw_socket_win.c.clog.h +++ b/src/generated/linux/datapath_raw_socket_win.c.clog.h @@ -14,6 +14,10 @@ #include "datapath_raw_socket_win.c.clog.h.lttng.h" #endif #include +#ifndef _clog_MACRO_QuicTraceLogVerbose +#define _clog_MACRO_QuicTraceLogVerbose 1 +#define QuicTraceLogVerbose(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) +#endif #ifndef _clog_MACRO_QuicTraceLogConnInfo #define _clog_MACRO_QuicTraceLogConnInfo 1 #define QuicTraceLogConnInfo(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) @@ -25,6 +29,26 @@ #ifdef __cplusplus extern "C" { #endif +/*---------------------------------------------------------- +// Decoder Ring for DatapathTcpAuxBinding +// [data][%p] Binding TCP socket to %s +// QuicTraceLogVerbose( + DatapathTcpAuxBinding, + "[data][%p] Binding TCP socket to %s", + Socket, + LocalAddressString.Address); +// arg2 = arg2 = Socket = arg2 +// arg3 = arg3 = LocalAddressString.Address = arg3 +----------------------------------------------------------*/ +#ifndef _clog_4_ARGS_TRACE_DatapathTcpAuxBinding +#define _clog_4_ARGS_TRACE_DatapathTcpAuxBinding(uniqueId, encoded_arg_string, arg2, arg3)\ +tracepoint(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathTcpAuxBinding , arg2, arg3);\ + +#endif + + + + /*---------------------------------------------------------- // Decoder Ring for RouteResolutionStart // [conn][%p] Starting to look up neighbor on Path[%hhu] with status %u @@ -67,6 +91,28 @@ tracepoint(CLOG_DATAPATH_RAW_SOCKET_WIN_C, LibraryErrorStatus , arg2, arg3);\ +/*---------------------------------------------------------- +// Decoder Ring for DatapathErrorStatus +// [data][%p] ERROR, %u, %s. +// QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + Error, + "closesocket"); +// arg2 = arg2 = Socket = arg2 +// arg3 = arg3 = Error = arg3 +// arg4 = arg4 = "closesocket" = arg4 +----------------------------------------------------------*/ +#ifndef _clog_5_ARGS_TRACE_DatapathErrorStatus +#define _clog_5_ARGS_TRACE_DatapathErrorStatus(uniqueId, encoded_arg_string, arg2, arg3, arg4)\ +tracepoint(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathErrorStatus , arg2, arg3, arg4);\ + +#endif + + + + /*---------------------------------------------------------- // Decoder Ring for DatapathGetRouteStart // [data][%p] Querying route, local=%!ADDR!, remote=%!ADDR! @@ -89,28 +135,6 @@ tracepoint(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathGetRouteStart , arg2, arg3_le -/*---------------------------------------------------------- -// Decoder Ring for DatapathErrorStatus -// [data][%p] ERROR, %u, %s. -// QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - Status, - "GetBestRoute2"); -// arg2 = arg2 = Socket = arg2 -// arg3 = arg3 = Status = arg3 -// arg4 = arg4 = "GetBestRoute2" = arg4 -----------------------------------------------------------*/ -#ifndef _clog_5_ARGS_TRACE_DatapathErrorStatus -#define _clog_5_ARGS_TRACE_DatapathErrorStatus(uniqueId, encoded_arg_string, arg2, arg3, arg4)\ -tracepoint(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathErrorStatus , arg2, arg3, arg4);\ - -#endif - - - - /*---------------------------------------------------------- // Decoder Ring for DatapathGetRouteComplete // [data][%p] Query route result: %!ADDR! diff --git a/src/generated/linux/datapath_raw_socket_win.c.clog.h.lttng.h b/src/generated/linux/datapath_raw_socket_win.c.clog.h.lttng.h index 343928bc40..d4abe65057 100644 --- a/src/generated/linux/datapath_raw_socket_win.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_raw_socket_win.c.clog.h.lttng.h @@ -1,6 +1,29 @@ +/*---------------------------------------------------------- +// Decoder Ring for DatapathTcpAuxBinding +// [data][%p] Binding TCP socket to %s +// QuicTraceLogVerbose( + DatapathTcpAuxBinding, + "[data][%p] Binding TCP socket to %s", + Socket, + LocalAddressString.Address); +// arg2 = arg2 = Socket = arg2 +// arg3 = arg3 = LocalAddressString.Address = arg3 +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathTcpAuxBinding, + TP_ARGS( + const void *, arg2, + const char *, arg3), + TP_FIELDS( + ctf_integer_hex(uint64_t, arg2, arg2) + ctf_string(arg3, arg3) + ) +) + + + /*---------------------------------------------------------- // Decoder Ring for RouteResolutionStart // [conn][%p] Starting to look up neighbor on Path[%hhu] with status %u @@ -51,6 +74,33 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_SOCKET_WIN_C, LibraryErrorStatus, +/*---------------------------------------------------------- +// Decoder Ring for DatapathErrorStatus +// [data][%p] ERROR, %u, %s. +// QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + Error, + "closesocket"); +// arg2 = arg2 = Socket = arg2 +// arg3 = arg3 = Error = arg3 +// arg4 = arg4 = "closesocket" = arg4 +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathErrorStatus, + TP_ARGS( + const void *, arg2, + unsigned int, arg3, + const char *, arg4), + TP_FIELDS( + ctf_integer_hex(uint64_t, arg2, arg2) + ctf_integer(unsigned int, arg3, arg3) + ctf_string(arg4, arg4) + ) +) + + + /*---------------------------------------------------------- // Decoder Ring for DatapathGetRouteStart // [data][%p] Querying route, local=%!ADDR!, remote=%!ADDR! @@ -82,33 +132,6 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathGetRouteStart, -/*---------------------------------------------------------- -// Decoder Ring for DatapathErrorStatus -// [data][%p] ERROR, %u, %s. -// QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - Status, - "GetBestRoute2"); -// arg2 = arg2 = Socket = arg2 -// arg3 = arg3 = Status = arg3 -// arg4 = arg4 = "GetBestRoute2" = arg4 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_SOCKET_WIN_C, DatapathErrorStatus, - TP_ARGS( - const void *, arg2, - unsigned int, arg3, - const char *, arg4), - TP_FIELDS( - ctf_integer_hex(uint64_t, arg2, arg2) - ctf_integer(unsigned int, arg3, arg3) - ctf_string(arg4, arg4) - ) -) - - - /*---------------------------------------------------------- // Decoder Ring for DatapathGetRouteComplete // [data][%p] Query route result: %!ADDR! diff --git a/src/manifest/clog.sidecar b/src/manifest/clog.sidecar index cfb1c27860..a52aa75114 100644 --- a/src/manifest/clog.sidecar +++ b/src/manifest/clog.sidecar @@ -3087,6 +3087,22 @@ ], "macroName": "QuicTraceLogVerbose" }, + "DatapathTcpAuxBinding": { + "ModuleProperites": {}, + "TraceString": "[data][%p] Binding TCP socket to %s", + "UniqueId": "DatapathTcpAuxBinding", + "splitArgs": [ + { + "DefinationEncoding": "p", + "MacroVariableName": "arg2" + }, + { + "DefinationEncoding": "s", + "MacroVariableName": "arg3" + } + ], + "macroName": "QuicTraceLogVerbose" + }, "DatapathTooLarge": { "ModuleProperites": {}, "TraceString": "[data][%p] Received larger than expected datagram from %!ADDR!", @@ -13631,6 +13647,11 @@ "TraceID": "DatapathSocketUninitializeComplete", "EncodingString": "[data][%p] Socket uninitialize complete" }, + { + "UniquenessHash": "3a4da7bb-462a-a423-bd9e-bcc366e59258", + "TraceID": "DatapathTcpAuxBinding", + "EncodingString": "[data][%p] Binding TCP socket to %s" + }, { "UniquenessHash": "a07c9538-e6d7-3c41-1367-ce58f2df4d9e", "TraceID": "DatapathTooLarge", diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index b2ced083af..812c3aacbf 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -18,18 +18,18 @@ CxPlatGetRawSocketSize () { } CXPLAT_SOCKET* -CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket) { +RawToSocket(CXPLAT_SOCKET_RAW* Socket) { return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); } CXPLAT_SOCKET_RAW* -CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { +SocketToRaw(CXPLAT_SOCKET* Socket) { return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); } _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawSocketCreateUdp( +RawSocketCreateUdp( _In_ CXPLAT_DATAPATH_RAW* DataPath, _In_ const CXPLAT_UDP_CONFIG* Config, _Inout_ CXPLAT_SOCKET_RAW* NewSocket @@ -43,7 +43,7 @@ CxPlatRawSocketCreateUdp( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatRawSocketDelete( +RawSocketDelete( _In_ CXPLAT_SOCKET_RAW* Socket ) { @@ -52,7 +52,7 @@ CxPlatRawSocketDelete( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawDataPathInitialize( +RawDataPathInitialize( _In_ uint32_t ClientRecvContextLength, _In_opt_ QUIC_EXECUTION_CONFIG* Config, _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, @@ -68,7 +68,7 @@ CxPlatRawDataPathInitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatRawDataPathUninitialize( +RawDataPathUninitialize( _In_ CXPLAT_DATAPATH_RAW* Datapath ) { @@ -77,7 +77,7 @@ CxPlatRawDataPathUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatRawDataPathUpdateConfig( +RawDataPathUpdateConfig( _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ) @@ -88,7 +88,7 @@ CxPlatRawDataPathUpdateConfig( _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t -CxPlatRawDataPathGetSupportedFeatures( +RawDataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH_RAW* Datapath ) { @@ -98,7 +98,7 @@ CxPlatRawDataPathGetSupportedFeatures( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -CxPlatRawDataPathIsPaddingPreferred( +RawDataPathIsPaddingPreferred( _In_ CXPLAT_DATAPATH* Datapath ) { @@ -108,7 +108,7 @@ CxPlatRawDataPathIsPaddingPreferred( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawSocketUpdateQeo( +RawSocketUpdateQeo( _In_ CXPLAT_SOCKET_RAW* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, @@ -123,7 +123,7 @@ CxPlatRawSocketUpdateQeo( _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 -CxPlatRawSocketGetLocalMtu( +RawSocketGetLocalMtu( _In_ CXPLAT_SOCKET_RAW* Socket ) { @@ -133,7 +133,7 @@ CxPlatRawSocketGetLocalMtu( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatRawRecvDataReturn( +RawRecvDataReturn( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ) { @@ -143,7 +143,7 @@ CxPlatRawRecvDataReturn( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* -CxPlatRawSendDataAlloc( +RawSendDataAlloc( _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) @@ -155,7 +155,7 @@ CxPlatRawSendDataAlloc( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatRawSendDataFree( +RawSendDataFree( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -165,7 +165,7 @@ CxPlatRawSendDataFree( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* -CxPlatRawSendDataAllocBuffer( +RawSendDataAllocBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ) @@ -177,7 +177,7 @@ CxPlatRawSendDataAllocBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatRawSendDataFreeBuffer( +RawSendDataFreeBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ) @@ -188,7 +188,7 @@ CxPlatRawSendDataFreeBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -CxPlatRawSendDataIsFull( +RawSendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -198,7 +198,7 @@ CxPlatRawSendDataIsFull( _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS -CxPlatRawSocketSend( +RawSocketSend( _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA* SendData @@ -211,7 +211,7 @@ CxPlatRawSocketSend( } void -CxPlatRawResolveRouteComplete( +RawResolveRouteComplete( _In_ void* Context, _Inout_ CXPLAT_ROUTE* Route, _In_reads_bytes_(6) const uint8_t* PhysicalAddress, @@ -226,7 +226,7 @@ CxPlatRawResolveRouteComplete( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawResolveRoute( +RawResolveRoute( _In_ CXPLAT_SOCKET_RAW* Sock, _Inout_ CXPLAT_ROUTE* Route, _In_ uint8_t PathId, @@ -243,7 +243,7 @@ CxPlatRawResolveRoute( } void -CxPlatRawDataPathProcessCqe( +RawDataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ) { @@ -252,7 +252,7 @@ CxPlatRawDataPathProcessCqe( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatRawUpdateRoute( +RawUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, _In_ CXPLAT_ROUTE* SrcRoute ) diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index 505cac60db..fa5fd40f85 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -24,12 +24,12 @@ CxPlatGetRawSocketSize () { } CXPLAT_SOCKET* -CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket) { +RawToSocket(CXPLAT_SOCKET_RAW* Socket) { return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); } CXPLAT_SOCKET_RAW* -CxPlatSocketToRaw(CXPLAT_SOCKET* Socket) { +SocketToRaw(CXPLAT_SOCKET* Socket) { return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); } @@ -60,7 +60,7 @@ CxPlatGetSocket( } void -CxPlatRawResolveRouteComplete( +RawResolveRouteComplete( _In_ void* Context, _Inout_ CXPLAT_ROUTE* Route, _In_reads_bytes_(6) const uint8_t* PhysicalAddress, @@ -85,7 +85,7 @@ CxPlatRawResolveRouteComplete( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatRawUpdateRoute( +RawUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, _In_ CXPLAT_ROUTE* SrcRoute ) @@ -505,7 +505,7 @@ CxPlatDpRawSocketAckFin( CXPLAT_ROUTE* Route = Packet->Route; CXPLAT_SEND_CONFIG SendConfig = { Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(CxPlatRawToSocket(Socket), &SendConfig); + CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(RawToSocket(Socket), &SendConfig); if (SendData == NULL) { return; } @@ -544,7 +544,7 @@ CxPlatDpRawSocketAckSyn( CXPLAT_ROUTE* Route = Packet->Route; CXPLAT_SEND_CONFIG SendConfig = { Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(CxPlatRawToSocket(Socket), &SendConfig); + CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(RawToSocket(Socket), &SendConfig); if (SendData == NULL) { return; } @@ -593,7 +593,7 @@ CxPlatDpRawSocketAckSyn( TH_ACK); CxPlatDpRawTxEnqueue(SendData); - SendData = CxPlatSendDataAlloc(CxPlatRawToSocket(Socket), &SendConfig); + SendData = CxPlatSendDataAlloc(RawToSocket(Socket), &SendConfig); if (SendData == NULL) { return; } @@ -626,9 +626,8 @@ CxPlatDpRawSocketSyn( ) { CXPLAT_DBG_ASSERT(Socket->UseTcp); - CXPLAT_SEND_CONFIG SendConfig = { (CXPLAT_ROUTE*)Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(CxPlatRawToSocket(Socket), &SendConfig); + CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(RawToSocket(Socket), &SendConfig); if (SendData == NULL) { return; } diff --git a/src/platform/datapath_raw_socket_win.c b/src/platform/datapath_raw_socket_win.c index 9a805d4f5d..e2a7c18602 100644 --- a/src/platform/datapath_raw_socket_win.c +++ b/src/platform/datapath_raw_socket_win.c @@ -61,12 +61,24 @@ CxPlatRemoveSocket( { CxPlatRwLockAcquireExclusive(&Pool->Lock); CxPlatHashtableRemove(&Pool->Sockets, &Socket->Entry, NULL); + + if (Socket->AuxSocket != INVALID_SOCKET && + closesocket(Socket->AuxSocket) == SOCKET_ERROR) { + int Error = SocketError(); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + Error, + "closesocket"); + } + CxPlatRwLockReleaseExclusive(&Pool->Lock); } _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawResolveRoute( +RawResolveRoute( _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_ROUTE* Route, _In_ uint8_t PathId, @@ -239,9 +251,9 @@ CxPlatTryAddSocket( ) { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; + int Result; CXPLAT_HASHTABLE_LOOKUP_CONTEXT Context; CXPLAT_HASHTABLE_ENTRY* Entry; - int Result; int Option; QUIC_ADDR MappedAddress = {0}; SOCKET TempUdpSocket = INVALID_SOCKET; @@ -252,25 +264,25 @@ CxPlatTryAddSocket( // binding an auxiliary (dual stack) socket. // - Socket->AuxSocket = - socket( - AF_INET6, - Socket->UseTcp ? SOCK_STREAM : SOCK_DGRAM, - Socket->UseTcp ? IPPROTO_TCP : IPPROTO_UDP); - if (Socket->AuxSocket == INVALID_SOCKET) { - int WsaError = SocketError(); - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Socket, - WsaError, - "socket"); - Status = HRESULT_FROM_WIN32(WsaError); - goto Error; - } - if (Socket->UseTcp) { + Socket->AuxSocket = + socket( + AF_INET6, + SOCK_STREAM, + IPPROTO_TCP); + if (Socket->AuxSocket == INVALID_SOCKET) { + int WsaError = SocketError(); + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + WsaError, + "socket"); + Status = HRESULT_FROM_WIN32(WsaError); + goto Error; + } + Option = FALSE; Result = setsockopt( @@ -338,7 +350,6 @@ CxPlatTryAddSocket( (struct sockaddr*)&MappedAddress, sizeof(MappedAddress)); if (Result == SOCKET_ERROR) { - fprintf(stderr, "bind failed: %d\n", WSAGetLastError()); int WsaError = SocketError(); QuicTraceEvent( DatapathErrorStatus, @@ -350,7 +361,6 @@ CxPlatTryAddSocket( Status = HRESULT_FROM_WIN32(WsaError); goto Error; } - fprintf(stderr, "bind succeeded\n"); if (Socket->Connected) { @@ -388,7 +398,6 @@ CxPlatTryAddSocket( goto Error; } LocalPortChosen = TempLocalAddress.Ipv4.sin_port; - fprintf(stderr, "LocalPortChosen %d\n", (int)LocalPortChosen); TempUdpSocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (TempUdpSocket == INVALID_SOCKET) { int WsaError = SocketError(); @@ -434,7 +443,6 @@ CxPlatTryAddSocket( sizeof(TempLocalAddress)); if (Result == SOCKET_ERROR) { int WsaError = SocketError(); - fprintf(stderr, "bind failed: %d\n", WsaError); QuicTraceEvent( DatapathErrorStatus, "[data][%p] ERROR, %u, %s.", @@ -453,7 +461,6 @@ CxPlatTryAddSocket( sizeof(MappedAddress)); if (Result == SOCKET_ERROR) { int WsaError = SocketError(); - fprintf(stderr, "connect failed (temp udp socket): %d\n", WsaError); QuicTraceEvent( DatapathErrorStatus, "[data][%p] ERROR, %u, %s.", @@ -473,7 +480,6 @@ CxPlatTryAddSocket( &AssignedLocalAddressLength); if (Result == SOCKET_ERROR) { int WsaError = SocketError(); - fprintf(stderr, "getsockname failed: %d\n", WsaError); QuicTraceEvent( DatapathErrorStatus, "[data][%p] ERROR, %u, %s.", diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index 7f29836bda..ad6a473c1b 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -104,7 +104,7 @@ CxPlatDataPathRouteWorkerInitialize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawDataPathInitialize( +RawDataPathInitialize( _In_ uint32_t ClientRecvContextLength, _In_opt_ QUIC_EXECUTION_CONFIG* Config, _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, @@ -179,7 +179,7 @@ CxPlatRawDataPathInitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatRawDataPathUninitialize( +RawDataPathUninitialize( _In_ CXPLAT_DATAPATH_RAW* Datapath ) { @@ -212,7 +212,7 @@ CxPlatDataPathUninitializeComplete( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatRawDataPathUpdateConfig( +RawDataPathUpdateConfig( _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ) @@ -222,7 +222,7 @@ CxPlatRawDataPathUpdateConfig( _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t -CxPlatRawDataPathGetSupportedFeatures( +RawDataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH_RAW* Datapath ) { @@ -231,7 +231,7 @@ CxPlatRawDataPathGetSupportedFeatures( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -CxPlatRawDataPathIsPaddingPreferred( +RawDataPathIsPaddingPreferred( _In_ CXPLAT_DATAPATH* Datapath ) { @@ -241,7 +241,7 @@ CxPlatRawDataPathIsPaddingPreferred( _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS -CxPlatRawDataPathGetLocalAddresses( +RawDataPathGetLocalAddresses( _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) CXPLAT_ADAPTER_ADDRESS** Addresses, @@ -254,7 +254,7 @@ CxPlatRawDataPathGetLocalAddresses( _IRQL_requires_max_(PASSIVE_LEVEL) _Success_(QUIC_SUCCEEDED(return)) QUIC_STATUS -CxPlatRawDataPathGetGatewayAddresses( +RawDataPathGetGatewayAddresses( _In_ CXPLAT_DATAPATH* Datapath, _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) QUIC_ADDR** GatewayAddresses, @@ -266,7 +266,7 @@ CxPlatRawDataPathGetGatewayAddresses( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawSocketCreateUdp( +RawSocketCreateUdp( _In_ CXPLAT_DATAPATH_RAW* Raw, _In_ const CXPLAT_UDP_CONFIG* Config, _Inout_ CXPLAT_SOCKET_RAW* Socket @@ -340,7 +340,7 @@ CxPlatRawSocketCreateUdp( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawCxPlatSocketCreateTcp( +RawCxPlatSocketCreateTcp( _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, @@ -353,7 +353,7 @@ CxPlatRawCxPlatSocketCreateTcp( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawSocketCreateTcpListener( +RawSocketCreateTcpListener( _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ void* RecvCallbackContext, @@ -365,7 +365,7 @@ CxPlatRawSocketCreateTcpListener( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatRawSocketDelete( +RawSocketDelete( _In_ CXPLAT_SOCKET_RAW* Socket ) { @@ -384,7 +384,7 @@ CxPlatRawSocketDelete( _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 -CxPlatRawSocketGetLocalMtu( +RawSocketGetLocalMtu( _In_ CXPLAT_SOCKET_RAW* Socket ) { @@ -443,7 +443,7 @@ CxPlatDpRawRxEthernet( CXPLAT_DBG_ASSERT(Packets[i+1]->Next == NULL); i++; } - Datapath->ParentDataPath->UdpHandlers.Receive(CxPlatRawToSocket(Socket), Socket->ClientContext, (CXPLAT_RECV_DATA*)PacketChain); + Datapath->ParentDataPath->UdpHandlers.Receive(RawToSocket(Socket), Socket->ClientContext, (CXPLAT_RECV_DATA*)PacketChain); } else if (PacketChain->Reserved == L4_TYPE_TCP_SYN || PacketChain->Reserved == L4_TYPE_TCP_SYNACK) { CxPlatDpRawSocketAckSyn(Socket, PacketChain); CxPlatDpRawRxFree(PacketChain); @@ -463,7 +463,7 @@ CxPlatDpRawRxEthernet( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatRawRecvDataReturn( +RawRecvDataReturn( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ) { @@ -473,7 +473,7 @@ CxPlatRawRecvDataReturn( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* -CxPlatRawSendDataAlloc( +RawSendDataAlloc( _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) @@ -484,7 +484,7 @@ CxPlatRawSendDataAlloc( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* -CxPlatRawSendDataAllocBuffer( +RawSendDataAllocBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ) @@ -495,7 +495,7 @@ CxPlatRawSendDataAllocBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatRawSendDataFree( +RawSendDataFree( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -504,7 +504,7 @@ CxPlatRawSendDataFree( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatRawSendDataFreeBuffer( +RawSendDataFreeBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ) @@ -514,7 +514,7 @@ CxPlatRawSendDataFreeBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -CxPlatRawSendDataIsFull( +RawSendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -525,7 +525,7 @@ CxPlatRawSendDataIsFull( _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS -CxPlatRawSocketSend( +RawSocketSend( _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA* SendData diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 2acef35267..1364ee22be 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -1284,7 +1284,7 @@ CxPlatDpRawUpdateConfig( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawSocketUpdateQeo( +RawSocketUpdateQeo( _In_ CXPLAT_SOCKET_RAW* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, @@ -1900,7 +1900,7 @@ CxPlatXdpExecute( } void -CxPlatRawDataPathProcessCqe( +RawDataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ) { diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index f6164d7fbe..24720a5124 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -59,7 +59,7 @@ CxPlatDataPathInitialize( } Status = - CxPlatRawDataPathInitialize( + RawDataPathInitialize( ClientRecvContextLength, Config, (*NewDataPath), @@ -84,7 +84,7 @@ CxPlatDataPathUninitialize( ) { if (Datapath->RawDataPath) { - CxPlatRawDataPathUninitialize(Datapath->RawDataPath); + RawDataPathUninitialize(Datapath->RawDataPath); } DataPathUninitialize(Datapath); } @@ -98,7 +98,7 @@ CxPlatDataPathUpdateConfig( { DataPathUpdateConfig(Datapath, Config); if (Datapath->RawDataPath) { - CxPlatRawDataPathUpdateConfig(Datapath->RawDataPath, Config); + RawDataPathUpdateConfig(Datapath->RawDataPath, Config); } } @@ -110,7 +110,7 @@ CxPlatDataPathGetSupportedFeatures( { if (Datapath->RawDataPath) { return DataPathGetSupportedFeatures(Datapath) | - CxPlatRawDataPathGetSupportedFeatures(Datapath->RawDataPath); + RawDataPathGetSupportedFeatures(Datapath->RawDataPath); } return DataPathGetSupportedFeatures(Datapath); } @@ -125,7 +125,7 @@ CxPlatDataPathIsPaddingPreferred( if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { return DataPathIsPaddingPreferred(Datapath); } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - return CxPlatRawDataPathIsPaddingPreferred(Datapath); + return RawDataPathIsPaddingPreferred(Datapath); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -296,10 +296,10 @@ CxPlatSocketCreateUdp( if (Datapath->RawDataPath) { Status = - CxPlatRawSocketCreateUdp( + RawSocketCreateUdp( Datapath->RawDataPath, Config, - CxPlatSocketToRaw(*NewSocket)); + SocketToRaw(*NewSocket)); (*NewSocket)->RawSocketAvailable = QUIC_SUCCEEDED(Status); if (QUIC_FAILED(Status)) { QuicTraceLogVerbose( @@ -358,7 +358,7 @@ CxPlatSocketDelete( ) { if (Socket->RawSocketAvailable) { - CxPlatRawSocketDelete(CxPlatSocketToRaw(Socket)); + RawSocketDelete(SocketToRaw(Socket)); } SocketDelete(Socket); } @@ -374,7 +374,7 @@ CxPlatSocketUpdateQeo( { if (Socket->RawSocketAvailable && !IS_LOOPBACK(Offloads[0].Address)) { - return CxPlatRawSocketUpdateQeo(CxPlatSocketToRaw(Socket), Offloads, OffloadCount); + return RawSocketUpdateQeo(SocketToRaw(Socket), Offloads, OffloadCount); } return QUIC_STATUS_NOT_SUPPORTED; } @@ -388,7 +388,7 @@ CxPlatSocketGetLocalMtu( CXPLAT_DBG_ASSERT(Socket != NULL); if (Socket->RawSocketAvailable && !IS_LOOPBACK(Socket->RemoteAddress)) { - return CxPlatRawSocketGetLocalMtu(CxPlatSocketToRaw(Socket)); + return RawSocketGetLocalMtu(SocketToRaw(Socket)); } return Socket->Mtu; } @@ -428,7 +428,7 @@ CxPlatRecvDataReturn( if (RecvDataChain->DatapathType == CXPLAT_DATAPATH_TYPE_USER) { RecvDataReturn(RecvDataChain); } else if (RecvDataChain->DatapathType == CXPLAT_DATAPATH_TYPE_XDP) { - CxPlatRawRecvDataReturn(RecvDataChain); + RawRecvDataReturn(RecvDataChain); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -447,7 +447,7 @@ CxPlatSendDataAlloc( if (Socket->UseTcp || (Socket->Type == CXPLAT_SOCKET_UDP && Socket->RawSocketAvailable && !IS_LOOPBACK(Config->Route->RemoteAddress))) { - SendData = CxPlatRawSendDataAlloc(CxPlatSocketToRaw(Socket), Config); + SendData = RawSendDataAlloc(SocketToRaw(Socket), Config); if (SendData) { DatapathType(SendData) = CXPLAT_DATAPATH_TYPE_XDP; } @@ -469,7 +469,7 @@ CxPlatSendDataFree( if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { SendDataFree(SendData); } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - CxPlatRawSendDataFree(SendData); + RawSendDataFree(SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -486,7 +486,7 @@ CxPlatSendDataAllocBuffer( if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { return SendDataAllocBuffer(SendData, MaxBufferLength); } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - return CxPlatRawSendDataAllocBuffer(SendData, MaxBufferLength); + return RawSendDataAllocBuffer(SendData, MaxBufferLength); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -503,7 +503,7 @@ CxPlatSendDataFreeBuffer( if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { SendDataFreeBuffer(SendData, Buffer); } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - CxPlatRawSendDataFreeBuffer(SendData, Buffer); + RawSendDataFreeBuffer(SendData, Buffer); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -518,7 +518,7 @@ CxPlatSendDataIsFull( if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { return SendDataIsFull(SendData); } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - return CxPlatRawSendDataIsFull(SendData); + return RawSendDataIsFull(SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -536,7 +536,7 @@ CxPlatSocketSend( if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { return SocketSend(Socket, Route, SendData); } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - return CxPlatRawSocketSend(CxPlatSocketToRaw(Socket), Route, SendData); + return RawSocketSend(SocketToRaw(Socket), Route, SendData); } else { CXPLAT_DBG_ASSERT(FALSE); } @@ -554,14 +554,14 @@ CxPlatDataPathProcessCqe( DATAPATH_IO_SQE* Sqe = CONTAINING_RECORD(CxPlatCqeUserData(Cqe), DATAPATH_IO_SQE, DatapathSqe); if (Sqe->IoType == DATAPATH_XDP_IO_RECV || Sqe->IoType == DATAPATH_XDP_IO_SEND) { - CxPlatRawDataPathProcessCqe(Cqe); + RawDataPathProcessCqe(Cqe); } else { DataPathProcessCqe(Cqe); } break; } case CXPLAT_CQE_TYPE_SOCKET_SHUTDOWN: { - CxPlatRawDataPathProcessCqe(Cqe); + RawDataPathProcessCqe(Cqe); break; } default: CXPLAT_DBG_ASSERT(FALSE); break; @@ -594,7 +594,7 @@ CxPlatResolveRouteComplete( ) { if (Route->State != RouteResolved) { - CxPlatRawResolveRouteComplete(Connection, Route, PhysicalAddress, PathId); + RawResolveRouteComplete(Connection, Route, PhysicalAddress, PathId); } } @@ -613,7 +613,7 @@ CxPlatResolveRoute( { if (Socket->RawSocketAvailable && !IS_LOOPBACK(Route->RemoteAddress)) { - return CxPlatRawResolveRoute(CxPlatSocketToRaw(Socket), Route, PathId, Context, Callback); + return RawResolveRoute(SocketToRaw(Socket), Route, PathId, Context, Callback); } Route->State = RouteResolved; return QUIC_STATUS_SUCCESS; @@ -628,7 +628,7 @@ CxPlatUpdateRoute( { if (SrcRoute->DatapathType == CXPLAT_DATAPATH_TYPE_XDP && !IS_LOOPBACK(SrcRoute->RemoteAddress)) { - CxPlatRawUpdateRoute(DstRoute, SrcRoute); + RawUpdateRoute(DstRoute, SrcRoute); } } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index c84e630eb6..c202c9d887 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1377,7 +1377,7 @@ SocketCreateUdp( Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; } - CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); + CXPLAT_SOCKET* Socket = RawToSocket(RawSocket); QuicTraceEvent( DatapathCreated, @@ -1963,7 +1963,7 @@ SocketCreateUdp( Error: if (RawSocket != NULL) { - SocketDelete(CxPlatRawToSocket(RawSocket)); + SocketDelete(RawToSocket(RawSocket)); } return Status; @@ -2000,7 +2000,7 @@ CxPlatSocketCreateTcpInternal( Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; } - CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); + CXPLAT_SOCKET* Socket = RawToSocket(RawSocket); QuicTraceEvent( DatapathCreated, @@ -2222,7 +2222,7 @@ CxPlatSocketCreateTcpInternal( Error: if (RawSocket != NULL) { - SocketDelete(CxPlatRawToSocket(RawSocket)); + SocketDelete(RawToSocket(RawSocket)); } return Status; @@ -2275,7 +2275,7 @@ SocketCreateTcpListener( Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; } - CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); + CXPLAT_SOCKET* Socket = RawToSocket(RawSocket); QuicTraceEvent( DatapathCreated, @@ -2464,7 +2464,7 @@ SocketCreateTcpListener( Error: if (RawSocket != NULL) { - SocketDelete(CxPlatRawToSocket(RawSocket)); + SocketDelete(RawToSocket(RawSocket)); } return Status; @@ -2517,7 +2517,7 @@ CxPlatSocketRelease( CXPLAT_DBG_ASSERT(!Socket->Freed); CXPLAT_DBG_ASSERT(Socket->Uninitialized); Socket->Freed = TRUE; - CXPLAT_FREE(CxPlatSocketToRaw(Socket), QUIC_POOL_SOCKET); + CXPLAT_FREE(SocketToRaw(Socket), QUIC_POOL_SOCKET); } } diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 63930d6c5e..e543a191fa 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -712,18 +712,18 @@ UpdateRoute( // TODO: rename CXPLAT_SOCKET* -CxPlatRawToSocket(CXPLAT_SOCKET_RAW* Socket); +RawToSocket(CXPLAT_SOCKET_RAW* Socket); // TODO: rename CXPLAT_SOCKET_RAW* -CxPlatSocketToRaw(CXPLAT_SOCKET* Socket); +SocketToRaw(CXPLAT_SOCKET* Socket); uint32_t CxPlatGetRawSocketSize (); _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawSocketCreateUdp( +RawSocketCreateUdp( _In_ CXPLAT_DATAPATH_RAW* DataPath, _In_ const CXPLAT_UDP_CONFIG* Config, _Inout_ CXPLAT_SOCKET_RAW* NewSocket @@ -731,13 +731,13 @@ CxPlatRawSocketCreateUdp( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatRawSocketDelete( +RawSocketDelete( _In_ CXPLAT_SOCKET_RAW* Socket ); _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawDataPathInitialize( +RawDataPathInitialize( _In_ uint32_t ClientRecvContextLength, _In_opt_ QUIC_EXECUTION_CONFIG* Config, _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, @@ -746,32 +746,32 @@ CxPlatRawDataPathInitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatRawDataPathUninitialize( +RawDataPathUninitialize( _In_ CXPLAT_DATAPATH_RAW* Datapath ); _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatRawDataPathUpdateConfig( +RawDataPathUpdateConfig( _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ); _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t -CxPlatRawDataPathGetSupportedFeatures( +RawDataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH_RAW* Datapath ); _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -CxPlatRawDataPathIsPaddingPreferred( +RawDataPathIsPaddingPreferred( _In_ CXPLAT_DATAPATH* Datapath ); _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawSocketUpdateQeo( +RawSocketUpdateQeo( _In_ CXPLAT_SOCKET_RAW* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, @@ -780,61 +780,61 @@ CxPlatRawSocketUpdateQeo( _IRQL_requires_max_(DISPATCH_LEVEL) UINT16 -CxPlatRawSocketGetLocalMtu( +RawSocketGetLocalMtu( _In_ CXPLAT_SOCKET_RAW* Socket ); _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatRawRecvDataReturn( +RawRecvDataReturn( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ); _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* -CxPlatRawSendDataAlloc( +RawSendDataAlloc( _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ); _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatRawSendDataFree( +RawSendDataFree( _In_ CXPLAT_SEND_DATA* SendData ); _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* -CxPlatRawSendDataAllocBuffer( +RawSendDataAllocBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ); _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatRawSendDataFreeBuffer( +RawSendDataFreeBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ); _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -CxPlatRawSendDataIsFull( +RawSendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ); _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS -CxPlatRawSocketSend( +RawSocketSend( _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA* SendData ); void -CxPlatRawResolveRouteComplete( +RawResolveRouteComplete( _In_ void* Context, _Inout_ CXPLAT_ROUTE* Route, _In_reads_bytes_(6) const uint8_t* PhysicalAddress, @@ -843,7 +843,7 @@ CxPlatRawResolveRouteComplete( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatRawResolveRoute( +RawResolveRoute( _In_ CXPLAT_SOCKET_RAW* Sock, _Inout_ CXPLAT_ROUTE* Route, _In_ uint8_t PathId, @@ -852,13 +852,13 @@ CxPlatRawResolveRoute( ); void -CxPlatRawDataPathProcessCqe( +RawDataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ); _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatRawUpdateRoute( +RawUpdateRoute( _Inout_ CXPLAT_ROUTE* DstRoute, _In_ CXPLAT_ROUTE* SrcRoute ); From 972051335a830c96b33d10984dcf299a132c4b16 Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Wed, 6 Sep 2023 21:29:51 -0700 Subject: [PATCH 70/87] experiment to avoid write overflow --- src/platform/platform_internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index e543a191fa..cbc6751fbb 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -74,7 +74,7 @@ typedef struct CXPLAT_SOCKET_COMMON { } CXPLAT_SOCKET_COMMON; typedef struct CXPLAT_SEND_DATA_COMMON { - uint16_t DatapathType : 2; + uint16_t DatapathType; // CXPLAT_DATAPATH_TYPE // // The type of ECN markings needed for send. From 02b39de09da72591233d2d1b912386c2ead53c20 Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Thu, 7 Sep 2023 01:02:01 -0700 Subject: [PATCH 71/87] use Config->Route->DatapathType for data allocation --- src/platform/datapath_win.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 24720a5124..7e820bdd59 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -444,9 +444,9 @@ CxPlatSendDataAlloc( { CXPLAT_SEND_DATA* SendData = NULL; // TODO: fallback? - if (Socket->UseTcp || (Socket->Type == CXPLAT_SOCKET_UDP && - Socket->RawSocketAvailable && - !IS_LOOPBACK(Config->Route->RemoteAddress))) { + if (Socket->UseTcp || Config->Route->DatapathType == CXPLAT_DATAPATH_TYPE_XDP || + (Config->Route->DatapathType == CXPLAT_DATAPATH_TYPE_UNKNOWN && + Socket->RawSocketAvailable && !IS_LOOPBACK(Config->Route->RemoteAddress))) { SendData = RawSendDataAlloc(SocketToRaw(Socket), Config); if (SendData) { DatapathType(SendData) = CXPLAT_DATAPATH_TYPE_XDP; From da791e7bfc22ef98b6b5302c9d8145e11cf7a232 Mon Sep 17 00:00:00 2001 From: Daiki Aminaka Date: Thu, 7 Sep 2023 08:29:32 -0700 Subject: [PATCH 72/87] more strict if conditions --- src/platform/datapath_raw_xdp_win.c | 3 ++- src/platform/datapath_win.c | 22 ++++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 1364ee22be..968b4495bd 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -24,8 +24,9 @@ #endif typedef struct XDP_DATAPATH { - CXPLAT_DATAPATH_RAW; DECLSPEC_CACHEALIGN + CXPLAT_DATAPATH_RAW; + // // Currently, all XDP interfaces share the same config. // diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 7e820bdd59..27c137e245 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -372,8 +372,8 @@ CxPlatSocketUpdateQeo( _In_ uint32_t OffloadCount ) { - if (Socket->RawSocketAvailable && - !IS_LOOPBACK(Offloads[0].Address)) { + if (Socket->UseTcp || (Socket->RawSocketAvailable && + !IS_LOOPBACK(Offloads[0].Address))) { return RawSocketUpdateQeo(SocketToRaw(Socket), Offloads, OffloadCount); } return QUIC_STATUS_NOT_SUPPORTED; @@ -386,8 +386,8 @@ CxPlatSocketGetLocalMtu( ) { CXPLAT_DBG_ASSERT(Socket != NULL); - if (Socket->RawSocketAvailable && - !IS_LOOPBACK(Socket->RemoteAddress)) { + if (Socket->UseTcp || (Socket->RawSocketAvailable && + !IS_LOOPBACK(Socket->RemoteAddress))) { return RawSocketGetLocalMtu(SocketToRaw(Socket)); } return Socket->Mtu; @@ -449,12 +449,12 @@ CxPlatSendDataAlloc( Socket->RawSocketAvailable && !IS_LOOPBACK(Config->Route->RemoteAddress))) { SendData = RawSendDataAlloc(SocketToRaw(Socket), Config); if (SendData) { - DatapathType(SendData) = CXPLAT_DATAPATH_TYPE_XDP; + DatapathType(SendData) = Config->Route->DatapathType = CXPLAT_DATAPATH_TYPE_XDP; } } else { SendData = SendDataAlloc(Socket, Config); if (SendData) { - DatapathType(SendData) = CXPLAT_DATAPATH_TYPE_USER; + DatapathType(SendData) = Config->Route->DatapathType = CXPLAT_DATAPATH_TYPE_USER; } } return SendData; @@ -611,8 +611,9 @@ CxPlatResolveRoute( _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback ) { - if (Socket->RawSocketAvailable && - !IS_LOOPBACK(Route->RemoteAddress)) { + if (Socket->UseTcp || Route->DatapathType == CXPLAT_DATAPATH_TYPE_XDP || + (Route->DatapathType == CXPLAT_DATAPATH_TYPE_UNKNOWN && + Socket->RawSocketAvailable && !IS_LOOPBACK(Route->RemoteAddress))) { return RawResolveRoute(SocketToRaw(Socket), Route, PathId, Context, Callback); } Route->State = RouteResolved; @@ -626,8 +627,9 @@ CxPlatUpdateRoute( _In_ CXPLAT_ROUTE* SrcRoute ) { - if (SrcRoute->DatapathType == CXPLAT_DATAPATH_TYPE_XDP && - !IS_LOOPBACK(SrcRoute->RemoteAddress)) { + if (SrcRoute->DatapathType == CXPLAT_DATAPATH_TYPE_XDP || + (SrcRoute->DatapathType == CXPLAT_DATAPATH_TYPE_UNKNOWN && + !IS_LOOPBACK(SrcRoute->RemoteAddress))) { RawUpdateRoute(DstRoute, SrcRoute); } } From 5d37f8511cf035cd5a68131f9534c1fb3a98c1f0 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 19 Sep 2023 12:19:43 -0700 Subject: [PATCH 73/87] fix comments --- .azure/OneBranch.Official.yml | 17 -- .azure/OneBranch.PullRequest.yml | 17 -- .azure/azure-pipelines.perf.yml | 23 +- .azure/azure-pipelines.periodic.yml | 19 +- .azure/obtemplates/build-winuser-xdp.yml | 36 --- .azure/obtemplates/build-winuser.yml | 7 + .azure/templates/build-config-user.yml | 2 +- .github/workflows/build-reuse-win.yml | 8 +- .github/workflows/build.yml | 10 +- .github/workflows/code-coverage.yml | 6 +- CMakeLists.txt | 5 - scripts/make-pgo-pr.ps1 | 1 - src/inc/quic_datapath.h | 4 +- src/platform/datapath_raw_dummy.c | 6 +- src/platform/datapath_raw_socket.c | 14 +- src/platform/datapath_raw_socket_win.c | 9 +- src/platform/datapath_raw_win.c | 31 +- src/platform/datapath_raw_xdp_win.c | 5 +- src/platform/datapath_win.c | 326 +++++++++++++++----- src/platform/datapath_winuser.c | 364 ++--------------------- src/platform/platform_internal.h | 86 +----- 21 files changed, 315 insertions(+), 681 deletions(-) delete mode 100644 .azure/obtemplates/build-winuser-xdp.yml diff --git a/.azure/OneBranch.Official.yml b/.azure/OneBranch.Official.yml index c0a86cddb8..d2242f5fe8 100644 --- a/.azure/OneBranch.Official.yml +++ b/.azure/OneBranch.Official.yml @@ -86,22 +86,6 @@ extends: parameters: config: Debug - - stage: build_windows_xdp - displayName: Build Windows XDP - dependsOn: [] - jobs: - - template: .azure/obtemplates/build-winuser-xdp.yml@self - parameters: - config: Release - tls: schannel - - template: .azure/obtemplates/build-winuser-xdp.yml@self - parameters: - config: Release - tls: openssl - - template: .azure/obtemplates/build-winuser-xdp.yml@self - parameters: - config: Debug - - stage: build_windows displayName: Build Windows dependsOn: [] @@ -204,7 +188,6 @@ extends: dependsOn: - build_windows - build_uwp - - build_windows_xdp jobs: - template: .azure/obtemplates/build-nuget.yml@self ${{ if startsWith(variables['Build.SourceBranch'], 'refs/tags/') }}: diff --git a/.azure/OneBranch.PullRequest.yml b/.azure/OneBranch.PullRequest.yml index 97bd4704b7..795b954f78 100644 --- a/.azure/OneBranch.PullRequest.yml +++ b/.azure/OneBranch.PullRequest.yml @@ -82,22 +82,6 @@ extends: parameters: config: Debug - - stage: build_windows_xdp - displayName: Build Windows XDP - dependsOn: [] - jobs: - - template: .azure/obtemplates/build-winuser-xdp.yml@self - parameters: - config: Release - tls: schannel - - template: .azure/obtemplates/build-winuser-xdp.yml@self - parameters: - config: Release - tls: openssl - - template: .azure/obtemplates/build-winuser-xdp.yml@self - parameters: - config: Debug - - stage: build_windows displayName: Build Windows dependsOn: [] @@ -198,6 +182,5 @@ extends: dependsOn: - build_windows - build_uwp - - build_windows_xdp jobs: - template: .azure/obtemplates/build-nuget.yml@self diff --git a/.azure/azure-pipelines.perf.yml b/.azure/azure-pipelines.perf.yml index 33ac95fa4f..868d7e33e7 100644 --- a/.azure/azure-pipelines.perf.yml +++ b/.azure/azure-pipelines.perf.yml @@ -177,27 +177,6 @@ stages: ${{ if eq(parameters.pgo_mode, true) }}: extraBuildArgs: -DisableTest -DisableTools -PGO -- ${{ if eq(parameters.winuser_xdp, true) }}: - - stage: build_winuser_xdp - displayName: Build Windows (XDP) - dependsOn: [] - variables: - runCodesignValidationInjection: false - jobs: - - template: ./templates/build-config-user.yml - parameters: - image: windows-latest - platform: windows - arch: ${{ parameters.arch }} - tls: schannel - config: Release - extraName: 'xdp' - extraPrepareArgs: -DisableTest -InstallXdpSdk - ${{ if eq(parameters.pgo_mode, false) }}: - extraBuildArgs: -DisableTest -DisableTools -UseXdp -ExtraArtifactDir Xdp - ${{ if eq(parameters.pgo_mode, true) }}: - extraBuildArgs: -DisableTest -DisableTools -UseXdp -ExtraArtifactDir Xdp -PGO - - ${{ if eq(parameters.winuser_openssl, true) }}: - stage: build_winuser_openssl displayName: Build Windows (OpenSSL) @@ -331,7 +310,7 @@ stages: - stage: perf_winuser_xdp displayName: Performance Testing Windows (XDP) dependsOn: - - build_winuser_xdp + - build_winuser_schannel jobs: - template: ./templates/run-performance.yml parameters: diff --git a/.azure/azure-pipelines.periodic.yml b/.azure/azure-pipelines.periodic.yml index b7586d7f9d..24fe06e79e 100644 --- a/.azure/azure-pipelines.periodic.yml +++ b/.azure/azure-pipelines.periodic.yml @@ -69,23 +69,6 @@ stages: extraPrepareArgs: -DisableTest extraBuildArgs: -DisableTest -DisableTools -PGO -- stage: build_winuser_xdp - displayName: Build Windows (XDP) - dependsOn: [] - variables: - runCodesignValidationInjection: false - jobs: - - template: ./templates/build-config-user.yml - parameters: - image: windows-latest - platform: windows - arch: x64 - tls: schannel - config: Release - extraName: 'xdp' - extraPrepareArgs: -DisableTest -InstallXdpSdk - extraBuildArgs: -DisableTest -DisableTools -UseXdp -ExtraArtifactDir Xdp -PGO - # # Performance Tests # @@ -141,7 +124,7 @@ stages: - stage: perf_winuser_xdp displayName: Performance Testing Windows (XDP) dependsOn: - - build_winuser_xdp + - build_winuser_schannel jobs: - template: ./templates/run-performance.yml parameters: diff --git a/.azure/obtemplates/build-winuser-xdp.yml b/.azure/obtemplates/build-winuser-xdp.yml deleted file mode 100644 index ee57c77fb7..0000000000 --- a/.azure/obtemplates/build-winuser-xdp.yml +++ /dev/null @@ -1,36 +0,0 @@ -parameters: - config: '' - tls: 'schannel' - platform: 'windows' - -jobs: -- job: build_${{ parameters.platform }}_${{ parameters.tls }}_${{ parameters.config }}_xdp - displayName: ${{ parameters.platform }} ${{ parameters.tls }} ${{ parameters.config }} XDP - pool: - type: windows - variables: - ob_outputDirectory: $(Build.SourcesDirectory)\artifacts\bin\${{ parameters.platform }} - ob_sdl_binskim_break: true # https://aka.ms/obpipelines/sdl - ob_sdl_codeSignValidation_excludes: -|**\*.exe # Disable signing requirements for test executables - steps: - - task: PowerShell@2 - displayName: Prepare Build Machine - target: windows_build_container2 - inputs: - pwsh: true - filePath: scripts/prepare-machine.ps1 - arguments: -InstallXdpSdk -ForContainerBuild - - task: PowerShell@2 - displayName: x64 - target: windows_build_container2 - inputs: - pwsh: true - filePath: scripts/build.ps1 - arguments: -Tls ${{ parameters.tls }} -Config ${{ parameters.config }} -Platform ${{ parameters.platform }} -Arch x64 -CI -UseXdp -ExtraArtifactDir xdp -OfficialRelease - - task: onebranch.pipeline.signing@1 - target: windows_build_container2 - inputs: - command: 'sign' - signing_profile: 'external_distribution' - files_to_sign: '**/*.dll' - search_root: '$(ob_outputDirectory)' diff --git a/.azure/obtemplates/build-winuser.yml b/.azure/obtemplates/build-winuser.yml index dcb709129d..17b99b3642 100644 --- a/.azure/obtemplates/build-winuser.yml +++ b/.azure/obtemplates/build-winuser.yml @@ -13,6 +13,13 @@ jobs: ob_sdl_binskim_break: true # https://aka.ms/obpipelines/sdl ob_sdl_codeSignValidation_excludes: -|**\*.exe # Disable signing requirements for test executables steps: + - task: PowerShell@2 + displayName: Prepare Build Machine + target: windows_build_container2 + inputs: + pwsh: true + filePath: scripts/prepare-machine.ps1 + arguments: -InstallXdpSdk -ForContainerBuild - task: PowerShell@2 displayName: x64 target: windows_build_container2 diff --git a/.azure/templates/build-config-user.yml b/.azure/templates/build-config-user.yml index 9639dbfd8f..6e46977cab 100644 --- a/.azure/templates/build-config-user.yml +++ b/.azure/templates/build-config-user.yml @@ -43,7 +43,7 @@ jobs: inputs: pwsh: true filePath: scripts/prepare-machine.ps1 - arguments: -Tls ${{ parameters.tls }} -ForBuild ${{ parameters.extraPrepareArgs }} + arguments: -Tls ${{ parameters.tls }} -ForBuild ${{ parameters.extraPrepareArgs }} -UseXdp - task: PowerShell@2 displayName: Build Source Code (Debug) diff --git a/.github/workflows/build-reuse-win.yml b/.github/workflows/build-reuse-win.yml index afa7b1b304..2881856941 100644 --- a/.github/workflows/build-reuse-win.yml +++ b/.github/workflows/build-reuse-win.yml @@ -85,15 +85,15 @@ jobs: uses: ilammy/setup-nasm@321e6ed62a1fc77024a3bd853deb33645e8b22c4 - name: Prepare Machine shell: pwsh - run: scripts/prepare-machine.ps1 -ForBuild -Tls ${{ inputs.tls }} + run: scripts/prepare-machine.ps1 -ForBuild -Tls ${{ inputs.tls }} ${{ inputs.xdp }} - name: Build For Test if: inputs.test == '-Test' shell: pwsh - run: scripts/build.ps1 -Config ${{ inputs.config }} -Platform ${{ inputs.plat }} -Arch ${{ inputs.arch }} -Tls ${{ inputs.tls }} -DisablePerf -DynamicCRT ${{ inputs.xdp }} ${{ inputs.sanitize }} + run: scripts/build.ps1 -Config ${{ inputs.config }} -Platform ${{ inputs.plat }} -Arch ${{ inputs.arch }} -Tls ${{ inputs.tls }} -DisablePerf -DynamicCRT ${{ inputs.sanitize }} - name: Build if: inputs.test == '' shell: pwsh - run: scripts/build.ps1 -Config ${{ inputs.config }} -Platform ${{ inputs.plat }} -Arch ${{ inputs.arch }} -Tls ${{ inputs.tls }} ${{ inputs.xdp }} ${{ inputs.sanitize }} ${{ inputs.static }} + run: scripts/build.ps1 -Config ${{ inputs.config }} -Platform ${{ inputs.plat }} -Arch ${{ inputs.arch }} -Tls ${{ inputs.tls }} ${{ inputs.sanitize }} ${{ inputs.static }} - name: Filter Build Artifacts shell: pwsh run: | @@ -103,5 +103,5 @@ jobs: - name: Upload build artifacts uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 with: - name: ${{ inputs.config }}-${{ inputs.plat }}-${{ inputs.os }}-${{ inputs.arch }}-${{ inputs.tls }}${{ inputs.xdp }}${{ inputs.sanitize }}${{ inputs.static }}${{ inputs.test }} + name: ${{ inputs.config }}-${{ inputs.plat }}-${{ inputs.os }}-${{ inputs.arch }}-${{ inputs.tls }}${{ inputs.sanitize }}${{ inputs.static }}${{ inputs.test }} path: artifacts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d8d118c8d9..397a504b90 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -201,8 +201,6 @@ jobs: { plat: "uwp", tls: "openssl", arg: "-UWP" }, { plat: "windows", tls: "openssl" }, { plat: "windows", tls: "schannel" }, - { plat: "windows", tls: "openssl", xdp: "-UseXdp", arg: "-XDP" }, - { plat: "windows", tls: "schannel", xdp: "-UseXdp", arg: "-XDP" }, ] runs-on: windows-2022 steps: @@ -211,19 +209,19 @@ jobs: - name: Download Build Artifacts uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 with: - name: Release-${{ matrix.vec.plat }}-windows-2022-x86-${{ matrix.vec.tls }}${{ matrix.vec.xdp }} + name: Release-${{ matrix.vec.plat }}-windows-2022-x86-${{ matrix.vec.tls }} path: artifacts if_no_artifact_found: ignore - name: Download Build Artifacts uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 with: - name: Release-${{ matrix.vec.plat }}-windows-2022-x64-${{ matrix.vec.tls }}${{ matrix.vec.xdp }} + name: Release-${{ matrix.vec.plat }}-windows-2022-x64-${{ matrix.vec.tls }} path: artifacts if_no_artifact_found: ignore - name: Download Build Artifacts uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 with: - name: Release-${{ matrix.vec.plat }}-windows-2022-arm64-${{ matrix.vec.tls }}${{ matrix.vec.xdp }} + name: Release-${{ matrix.vec.plat }}-windows-2022-arm64-${{ matrix.vec.tls }} path: artifacts if_no_artifact_found: ignore - name: Build Package @@ -232,5 +230,5 @@ jobs: - name: Upload build artifacts uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 with: - name: Nuget-Release-${{ matrix.vec.plat }}-windows-2022-arm64-${{ matrix.vec.tls }}${{ matrix.vec.xdp }} + name: Nuget-Release-${{ matrix.vec.plat }}-windows-2022-arm64-${{ matrix.vec.tls }} path: artifacts/dist/*.nupkg diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index cbc1f74724..86b8c87533 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -59,7 +59,7 @@ jobs: - name: Download Build Artifacts uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a with: - name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.xdp }}${{ matrix.vec.sanitize }}${{ matrix.vec.test }} + name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.sanitize }}${{ matrix.vec.test }} path: artifacts - name: Prepare Machine run: scripts/prepare-machine.ps1 -Tls ${{ matrix.vec.tls }} -ForTest ${{ matrix.vec.xdp }} -InstallCodeCoverage @@ -79,7 +79,7 @@ jobs: - name: Upload Results uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 with: - name: BVT-${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.xdp }}${{ matrix.vec.qtip }}${{ matrix.vec.systemcrypto }}${{ matrix.vec.sanitize }} + name: BVT-${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.qtip }}${{ matrix.vec.systemcrypto }}${{ matrix.vec.sanitize }} path: artifacts/coverage/*.cov stress-winlatest: @@ -109,7 +109,7 @@ jobs: fetch-depth: 0 - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a with: - name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.xdp }}${{ matrix.vec.sanitize }}${{ matrix.vec.test }} + name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.sanitize }}${{ matrix.vec.test }} path: artifacts - name: Prepare Machine run: scripts/prepare-machine.ps1 -Tls ${{ matrix.vec.tls }} -ForTest ${{ matrix.vec.xdp }} -InstallCodeCoverage diff --git a/CMakeLists.txt b/CMakeLists.txt index 2de5843e07..684055f708 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -555,11 +555,6 @@ if(WIN32) set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG /IGNORE:4075 /DEBUG /OPT:REF /OPT:ICF") # Find the right PGO file. - if(QUIC_USE_XDP) - set(QUIC_PGO_NAME "msquic.${QUIC_TLS}.xdp.pgd") - set(QUIC_PGO_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/bin/winuser/pgo_${SYSTEM_PROCESSOR}/${QUIC_PGO_NAME}") - endif() - if(NOT EXISTS "${QUIC_PGO_FILE}") set(QUIC_PGO_NAME "msquic.${QUIC_TLS}.pgd") set(QUIC_PGO_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/bin/winuser/pgo_${SYSTEM_PROCESSOR}/${QUIC_PGO_NAME}") diff --git a/scripts/make-pgo-pr.ps1 b/scripts/make-pgo-pr.ps1 index e6fbbc553e..141fab1d96 100644 --- a/scripts/make-pgo-pr.ps1 +++ b/scripts/make-pgo-pr.ps1 @@ -43,7 +43,6 @@ git checkout -b $BranchName Copy-Item -Path artifacts/PerfDataResults/performance/windows/$($Arch)_$($Config)_schannel/msquic.pgd src/bin/winuser/pgo_$($Arch)/msquic.schannel.pgd -Force Copy-Item -Path artifacts/PerfDataResults/performance/windows/$($Arch)_$($Config)_openssl/msquic.pgd src/bin/winuser/pgo_$($Arch)/msquic.openssl.pgd -Force Copy-Item -Path artifacts/PerfDataResults/performance/windows/$($Arch)_$($Config)_openssl3/msquic.pgd src/bin/winuser/pgo_$($Arch)/msquic.openssl3.pgd -Force -Copy-Item -Path artifacts/PerfDataResults/performance/windows/$($Arch)_$($Config)_schannel_Xdp/msquic.pgd src/bin/winuser/pgo_$($Arch)/msquic.schannel.xdp.pgd -Force # Commit the new PGD files. git commit -am "Update PGO data" diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 607556158f..726ccbe4c7 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -228,8 +228,8 @@ typedef struct CXPLAT_RECV_DATA { uint16_t Allocated : 1; // Used for debugging. Set to FALSE on free. uint16_t QueuedOnConnection : 1; // Used for debugging. uint16_t DatapathType : 2; // CXPLAT_DATAPATH_TYPE - uint16_t Reserved : 4; - uint16_t ReservedEx : 8; + uint16_t Reserved : 4; // PACKET_TYPE (at least 3 bits) + uint16_t ReservedEx : 8; // Header length // // Variable length data (of size `ClientRecvContextLength` passed into diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index 812c3aacbf..e32d690635 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -13,17 +13,17 @@ #include "platform_internal.h" uint32_t -CxPlatGetRawSocketSize () { +CxPlatGetRawSocketSize(void) { return sizeof(CXPLAT_SOCKET_RAW); } CXPLAT_SOCKET* -RawToSocket(CXPLAT_SOCKET_RAW* Socket) { +CxPlatRawToSocket(_In_ CXPLAT_SOCKET_RAW* Socket) { return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); } CXPLAT_SOCKET_RAW* -SocketToRaw(CXPLAT_SOCKET* Socket) { +CxPlatSocketToRaw(_In_ CXPLAT_SOCKET* Socket) { return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); } diff --git a/src/platform/datapath_raw_socket.c b/src/platform/datapath_raw_socket.c index fa5fd40f85..3f6e21bc88 100644 --- a/src/platform/datapath_raw_socket.c +++ b/src/platform/datapath_raw_socket.c @@ -19,17 +19,17 @@ uint32_t -CxPlatGetRawSocketSize () { +CxPlatGetRawSocketSize(void) { return sizeof(CXPLAT_SOCKET_RAW); } CXPLAT_SOCKET* -RawToSocket(CXPLAT_SOCKET_RAW* Socket) { +CxPlatRawToSocket(_In_ CXPLAT_SOCKET_RAW* Socket) { return (CXPLAT_SOCKET*)((unsigned char*)Socket + sizeof(CXPLAT_SOCKET_RAW) - sizeof(CXPLAT_SOCKET)); } CXPLAT_SOCKET_RAW* -SocketToRaw(CXPLAT_SOCKET* Socket) { +CxPlatSocketToRaw(_In_ CXPLAT_SOCKET* Socket) { return (CXPLAT_SOCKET_RAW*)((unsigned char*)Socket - sizeof(CXPLAT_SOCKET_RAW) + sizeof(CXPLAT_SOCKET)); } @@ -505,7 +505,7 @@ CxPlatDpRawSocketAckFin( CXPLAT_ROUTE* Route = Packet->Route; CXPLAT_SEND_CONFIG SendConfig = { Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(RawToSocket(Socket), &SendConfig); + CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(CxPlatRawToSocket(Socket), &SendConfig); if (SendData == NULL) { return; } @@ -544,7 +544,7 @@ CxPlatDpRawSocketAckSyn( CXPLAT_ROUTE* Route = Packet->Route; CXPLAT_SEND_CONFIG SendConfig = { Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(RawToSocket(Socket), &SendConfig); + CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(CxPlatRawToSocket(Socket), &SendConfig); if (SendData == NULL) { return; } @@ -593,7 +593,7 @@ CxPlatDpRawSocketAckSyn( TH_ACK); CxPlatDpRawTxEnqueue(SendData); - SendData = CxPlatSendDataAlloc(RawToSocket(Socket), &SendConfig); + SendData = CxPlatSendDataAlloc(CxPlatRawToSocket(Socket), &SendConfig); if (SendData == NULL) { return; } @@ -627,7 +627,7 @@ CxPlatDpRawSocketSyn( { CXPLAT_DBG_ASSERT(Socket->UseTcp); CXPLAT_SEND_CONFIG SendConfig = { (CXPLAT_ROUTE*)Route, 0, CXPLAT_ECN_NON_ECT, 0 }; - CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(RawToSocket(Socket), &SendConfig); + CXPLAT_SEND_DATA *SendData = CxPlatSendDataAlloc(CxPlatRawToSocket(Socket), &SendConfig); if (SendData == NULL) { return; } diff --git a/src/platform/datapath_raw_socket_win.c b/src/platform/datapath_raw_socket_win.c index e2a7c18602..1f387e608a 100644 --- a/src/platform/datapath_raw_socket_win.c +++ b/src/platform/datapath_raw_socket_win.c @@ -264,8 +264,7 @@ CxPlatTryAddSocket( // binding an auxiliary (dual stack) socket. // - if (Socket->UseTcp) - { + if (Socket->UseTcp) { Socket->AuxSocket = socket( AF_INET6, @@ -335,8 +334,7 @@ CxPlatTryAddSocket( CxPlatRwLockAcquireExclusive(&Pool->Lock); - if (Socket->UseTcp) - { + if (Socket->UseTcp) { QUIC_ADDR_STR LocalAddressString = {0}; QuicAddrToString(&MappedAddress, &LocalAddressString); QuicTraceLogVerbose( @@ -362,8 +360,7 @@ CxPlatTryAddSocket( goto Error; } - if (Socket->Connected) - { + if (Socket->Connected) { CxPlatZeroMemory(&MappedAddress, sizeof(MappedAddress)); CxPlatConvertToMappedV6(&Socket->RemoteAddress, &MappedAddress); diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index ad6a473c1b..54254109d0 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -238,32 +238,6 @@ RawDataPathIsPaddingPreferred( return FALSE; } -_IRQL_requires_max_(PASSIVE_LEVEL) -_Success_(QUIC_SUCCEEDED(return)) -QUIC_STATUS -RawDataPathGetLocalAddresses( - _In_ CXPLAT_DATAPATH* Datapath, - _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) - CXPLAT_ADAPTER_ADDRESS** Addresses, - _Out_ uint32_t* AddressesCount - ) -{ - return QUIC_STATUS_NOT_SUPPORTED; -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -_Success_(QUIC_SUCCEEDED(return)) -QUIC_STATUS -RawDataPathGetGatewayAddresses( - _In_ CXPLAT_DATAPATH* Datapath, - _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) - QUIC_ADDR** GatewayAddresses, - _Out_ uint32_t* GatewayAddressesCount - ) -{ - return QUIC_STATUS_NOT_SUPPORTED; -} - _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS RawSocketCreateUdp( @@ -284,7 +258,6 @@ RawSocketCreateUdp( memcpy(Socket->CibirId, Config->CibirId, Config->CibirIdLength); } - // TODO: remove Socket address settings if (Config->RemoteAddress) { CXPLAT_FRE_ASSERT(!QuicAddrIsWildCard(Config->RemoteAddress)); // No wildcard remote addresses allowed. if (Socket->UseTcp) { @@ -443,7 +416,7 @@ CxPlatDpRawRxEthernet( CXPLAT_DBG_ASSERT(Packets[i+1]->Next == NULL); i++; } - Datapath->ParentDataPath->UdpHandlers.Receive(RawToSocket(Socket), Socket->ClientContext, (CXPLAT_RECV_DATA*)PacketChain); + Datapath->ParentDataPath->UdpHandlers.Receive(CxPlatRawToSocket(Socket), Socket->ClientContext, (CXPLAT_RECV_DATA*)PacketChain); } else if (PacketChain->Reserved == L4_TYPE_TCP_SYN || PacketChain->Reserved == L4_TYPE_TCP_SYNACK) { CxPlatDpRawSocketAckSyn(Socket, PacketChain); CxPlatDpRawRxFree(PacketChain); @@ -464,7 +437,7 @@ CxPlatDpRawRxEthernet( _IRQL_requires_max_(DISPATCH_LEVEL) void RawRecvDataReturn( - _In_opt_ CXPLAT_RECV_DATA* RecvDataChain + _In_ CXPLAT_RECV_DATA* RecvDataChain ) { CxPlatDpRawRxFree((const CXPLAT_RECV_DATA*)RecvDataChain); diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 968b4495bd..8644f554a9 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -24,8 +24,8 @@ #endif typedef struct XDP_DATAPATH { - DECLSPEC_CACHEALIGN CXPLAT_DATAPATH_RAW; + DECLSPEC_CACHEALIGN // // Currently, all XDP interfaces share the same config. @@ -1563,7 +1563,7 @@ CxPlatXdpRx( Packet->Queue = Queue; Packet->RouteStorage.Queue = Queue; Packet->RecvData.Route = &Packet->RouteStorage; - Packet->RecvData.Route->DatapathType = Packet->RecvData.DatapathType = CXPLAT_DATAPATH_TYPE_XDP; + Packet->RecvData.Route->DatapathType = Packet->RecvData.DatapathType = CXPLAT_DATAPATH_TYPE_RAW; Packet->RecvData.PartitionIndex = PartitionIndex; CxPlatDpRawParseEthernet( @@ -1688,6 +1688,7 @@ CxPlatDpRawTxAlloc( Packet->Buffer.Length = Config->MaxPacketSize; Packet->Buffer.Buffer = &Packet->FrameBuffer[HeaderBackfill.AllLayer]; Packet->ECN = Config->ECN; + Packet->DatapathType = Config->Route->DatapathType = CXPLAT_DATAPATH_TYPE_RAW; } return (CXPLAT_SEND_DATA*)Packet; diff --git a/src/platform/datapath_win.c b/src/platform/datapath_win.c index 27c137e245..1ba9cca243 100644 --- a/src/platform/datapath_win.c +++ b/src/platform/datapath_win.c @@ -122,14 +122,12 @@ CxPlatDataPathIsPaddingPreferred( _In_ CXPLAT_SEND_DATA* SendData ) { - if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { - return DataPathIsPaddingPreferred(Datapath); - } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - return RawDataPathIsPaddingPreferred(Datapath); - } else { - CXPLAT_DBG_ASSERT(FALSE); - } - return FALSE; + CXPLAT_DBG_ASSERT( + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER || + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_RAW); + return + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER ? + DataPathIsPaddingPreferred(Datapath) : RawDataPathIsPaddingPreferred(Datapath); } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -142,11 +140,108 @@ CxPlatDataPathGetLocalAddresses( _Out_ uint32_t* AddressesCount ) { - // TODO: XDP doesn't support, could be inlined here - return DataPathGetLocalAddresses( - Datapath, - Addresses, - AddressesCount); + const ULONG Flags = + GAA_FLAG_INCLUDE_ALL_INTERFACES | + GAA_FLAG_SKIP_ANYCAST | + GAA_FLAG_SKIP_MULTICAST | + GAA_FLAG_SKIP_DNS_SERVER | + GAA_FLAG_SKIP_FRIENDLY_NAME | + GAA_FLAG_SKIP_DNS_INFO; + + UNREFERENCED_PARAMETER(Datapath); + + ULONG AdapterAddressesSize = 0; + PIP_ADAPTER_ADDRESSES AdapterAddresses = NULL; + uint32_t Index = 0; + + QUIC_STATUS Status = QUIC_STATUS_SUCCESS; + ULONG Error; + do { + Error = + GetAdaptersAddresses( + AF_UNSPEC, + Flags, + NULL, + AdapterAddresses, + &AdapterAddressesSize); + if (Error == ERROR_BUFFER_OVERFLOW) { + if (AdapterAddresses) { + CXPLAT_FREE(AdapterAddresses, QUIC_POOL_DATAPATH_ADDRESSES); + } + AdapterAddresses = CXPLAT_ALLOC_NONPAGED(AdapterAddressesSize, QUIC_POOL_DATAPATH_ADDRESSES); + if (!AdapterAddresses) { + Error = ERROR_NOT_ENOUGH_MEMORY; + QuicTraceEvent( + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "PIP_ADAPTER_ADDRESSES", + AdapterAddressesSize); + } + } + } while (Error == ERROR_BUFFER_OVERFLOW); + + if (Error != ERROR_SUCCESS) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Error, + "GetAdaptersAddresses"); + Status = HRESULT_FROM_WIN32(Error); + goto Exit; + } + + for (PIP_ADAPTER_ADDRESSES Iter = AdapterAddresses; Iter != NULL; Iter = Iter->Next) { + for (PIP_ADAPTER_UNICAST_ADDRESS_LH Iter2 = Iter->FirstUnicastAddress; Iter2 != NULL; Iter2 = Iter2->Next) { + Index++; + } + } + + if (Index == 0) { + QuicTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "No local unicast addresses found"); + Status = QUIC_STATUS_NOT_FOUND; + goto Exit; + } + + *Addresses = CXPLAT_ALLOC_NONPAGED(Index * sizeof(CXPLAT_ADAPTER_ADDRESS), QUIC_POOL_DATAPATH_ADDRESSES); + if (*Addresses == NULL) { + Status = QUIC_STATUS_OUT_OF_MEMORY; + QuicTraceEvent( + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "Addresses", + Index * sizeof(CXPLAT_ADAPTER_ADDRESS)); + goto Exit; + } + + CxPlatZeroMemory(*Addresses, Index * sizeof(CXPLAT_ADAPTER_ADDRESS)); + *AddressesCount = Index; + Index = 0; + + for (PIP_ADAPTER_ADDRESSES Iter = AdapterAddresses; Iter != NULL; Iter = Iter->Next) { + for (PIP_ADAPTER_UNICAST_ADDRESS_LH Iter2 = Iter->FirstUnicastAddress; Iter2 != NULL; Iter2 = Iter2->Next) { + CxPlatCopyMemory( + &(*Addresses)[Index].Address, + Iter2->Address.lpSockaddr, + sizeof(QUIC_ADDR)); + (*Addresses)[Index].InterfaceIndex = + Iter2->Address.lpSockaddr->sa_family == AF_INET ? + (uint32_t)Iter->IfIndex : (uint32_t)Iter->Ipv6IfIndex; + (*Addresses)[Index].InterfaceType = (uint16_t)Iter->IfType; + (*Addresses)[Index].OperationStatus = (CXPLAT_OPERATION_STATUS)Iter->OperStatus; + Index++; + } + } + +Exit: + + if (AdapterAddresses) { + CXPLAT_FREE(AdapterAddresses, QUIC_POOL_DATAPATH_ADDRESSES); + } + + return Status; } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -159,11 +254,101 @@ CxPlatDataPathGetGatewayAddresses( _Out_ uint32_t* GatewayAddressesCount ) { - // TODO: XDP doesn't support, Could be inlined here. - return DataPathGetGatewayAddresses( - Datapath, - GatewayAddresses, - GatewayAddressesCount); + const ULONG Flags = + GAA_FLAG_INCLUDE_GATEWAYS | + GAA_FLAG_INCLUDE_ALL_INTERFACES | + GAA_FLAG_SKIP_DNS_SERVER | + GAA_FLAG_SKIP_MULTICAST; + + UNREFERENCED_PARAMETER(Datapath); + + ULONG AdapterAddressesSize = 0; + PIP_ADAPTER_ADDRESSES AdapterAddresses = NULL; + uint32_t Index = 0; + + QUIC_STATUS Status = QUIC_STATUS_SUCCESS; + ULONG Error; + do { + Error = + GetAdaptersAddresses( + AF_UNSPEC, + Flags, + NULL, + AdapterAddresses, + &AdapterAddressesSize); + if (Error == ERROR_BUFFER_OVERFLOW) { + if (AdapterAddresses) { + CXPLAT_FREE(AdapterAddresses, QUIC_POOL_DATAPATH_ADDRESSES); + } + AdapterAddresses = CXPLAT_ALLOC_NONPAGED(AdapterAddressesSize, QUIC_POOL_DATAPATH_ADDRESSES); + if (!AdapterAddresses) { + Error = ERROR_NOT_ENOUGH_MEMORY; + QuicTraceEvent( + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "PIP_ADAPTER_ADDRESSES", + AdapterAddressesSize); + } + } + } while (Error == ERROR_BUFFER_OVERFLOW); + + if (Error != ERROR_SUCCESS) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Error, + "GetAdaptersAddresses"); + Status = HRESULT_FROM_WIN32(Error); + goto Exit; + } + + for (PIP_ADAPTER_ADDRESSES Iter = AdapterAddresses; Iter != NULL; Iter = Iter->Next) { + for (PIP_ADAPTER_GATEWAY_ADDRESS_LH Iter2 = Iter->FirstGatewayAddress; Iter2 != NULL; Iter2 = Iter2->Next) { + Index++; + } + } + + if (Index == 0) { + QuicTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "No gateway server addresses found"); + Status = QUIC_STATUS_NOT_FOUND; + goto Exit; + } + + *GatewayAddresses = CXPLAT_ALLOC_NONPAGED(Index * sizeof(QUIC_ADDR), QUIC_POOL_DATAPATH_ADDRESSES); + if (*GatewayAddresses == NULL) { + Status = QUIC_STATUS_OUT_OF_MEMORY; + QuicTraceEvent( + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "GatewayAddresses", + Index * sizeof(QUIC_ADDR)); + goto Exit; + } + + CxPlatZeroMemory(*GatewayAddresses, Index * sizeof(QUIC_ADDR)); + *GatewayAddressesCount = Index; + Index = 0; + + for (PIP_ADAPTER_ADDRESSES Iter = AdapterAddresses; Iter != NULL; Iter = Iter->Next) { + for (PIP_ADAPTER_GATEWAY_ADDRESS_LH Iter2 = Iter->FirstGatewayAddress; Iter2 != NULL; Iter2 = Iter2->Next) { + CxPlatCopyMemory( + &(*GatewayAddresses)[Index], + Iter2->Address.lpSockaddr, + sizeof(QUIC_ADDR)); + Index++; + } + } + +Exit: + + if (AdapterAddresses) { + CXPLAT_FREE(AdapterAddresses, QUIC_POOL_DATAPATH_ADDRESSES); + } + + return Status; } // private func @@ -299,7 +484,7 @@ CxPlatSocketCreateUdp( RawSocketCreateUdp( Datapath->RawDataPath, Config, - SocketToRaw(*NewSocket)); + CxPlatSocketToRaw(*NewSocket)); (*NewSocket)->RawSocketAvailable = QUIC_SUCCEEDED(Status); if (QUIC_FAILED(Status)) { QuicTraceLogVerbose( @@ -358,7 +543,7 @@ CxPlatSocketDelete( ) { if (Socket->RawSocketAvailable) { - RawSocketDelete(SocketToRaw(Socket)); + RawSocketDelete(CxPlatSocketToRaw(Socket)); } SocketDelete(Socket); } @@ -374,7 +559,7 @@ CxPlatSocketUpdateQeo( { if (Socket->UseTcp || (Socket->RawSocketAvailable && !IS_LOOPBACK(Offloads[0].Address))) { - return RawSocketUpdateQeo(SocketToRaw(Socket), Offloads, OffloadCount); + return RawSocketUpdateQeo(CxPlatSocketToRaw(Socket), Offloads, OffloadCount); } return QUIC_STATUS_NOT_SUPPORTED; } @@ -388,7 +573,7 @@ CxPlatSocketGetLocalMtu( CXPLAT_DBG_ASSERT(Socket != NULL); if (Socket->UseTcp || (Socket->RawSocketAvailable && !IS_LOOPBACK(Socket->RemoteAddress))) { - return RawSocketGetLocalMtu(SocketToRaw(Socket)); + return RawSocketGetLocalMtu(CxPlatSocketToRaw(Socket)); } return Socket->Mtu; } @@ -424,14 +609,11 @@ CxPlatRecvDataReturn( if (RecvDataChain == NULL) { return; } - - if (RecvDataChain->DatapathType == CXPLAT_DATAPATH_TYPE_USER) { - RecvDataReturn(RecvDataChain); - } else if (RecvDataChain->DatapathType == CXPLAT_DATAPATH_TYPE_XDP) { - RawRecvDataReturn(RecvDataChain); - } else { - CXPLAT_DBG_ASSERT(FALSE); - } + CXPLAT_DBG_ASSERT( + RecvDataChain->DatapathType == CXPLAT_DATAPATH_TYPE_USER || + RecvDataChain->DatapathType == CXPLAT_DATAPATH_TYPE_RAW); + RecvDataChain->DatapathType == CXPLAT_DATAPATH_TYPE_USER ? + RecvDataReturn(RecvDataChain) : RawRecvDataReturn(RecvDataChain); } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -444,18 +626,12 @@ CxPlatSendDataAlloc( { CXPLAT_SEND_DATA* SendData = NULL; // TODO: fallback? - if (Socket->UseTcp || Config->Route->DatapathType == CXPLAT_DATAPATH_TYPE_XDP || + if (Socket->UseTcp || Config->Route->DatapathType == CXPLAT_DATAPATH_TYPE_RAW || (Config->Route->DatapathType == CXPLAT_DATAPATH_TYPE_UNKNOWN && Socket->RawSocketAvailable && !IS_LOOPBACK(Config->Route->RemoteAddress))) { - SendData = RawSendDataAlloc(SocketToRaw(Socket), Config); - if (SendData) { - DatapathType(SendData) = Config->Route->DatapathType = CXPLAT_DATAPATH_TYPE_XDP; - } + SendData = RawSendDataAlloc(CxPlatSocketToRaw(Socket), Config); } else { SendData = SendDataAlloc(Socket, Config); - if (SendData) { - DatapathType(SendData) = Config->Route->DatapathType = CXPLAT_DATAPATH_TYPE_USER; - } } return SendData; } @@ -466,13 +642,11 @@ CxPlatSendDataFree( _In_ CXPLAT_SEND_DATA* SendData ) { - if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { - SendDataFree(SendData); - } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - RawSendDataFree(SendData); - } else { - CXPLAT_DBG_ASSERT(FALSE); - } + CXPLAT_DBG_ASSERT( + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER || + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_RAW); + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER ? + SendDataFree(SendData) : RawSendDataFree(SendData); } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -483,14 +657,12 @@ CxPlatSendDataAllocBuffer( _In_ uint16_t MaxBufferLength ) { - if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { - return SendDataAllocBuffer(SendData, MaxBufferLength); - } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - return RawSendDataAllocBuffer(SendData, MaxBufferLength); - } else { - CXPLAT_DBG_ASSERT(FALSE); - } - return NULL; + CXPLAT_DBG_ASSERT( + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER || + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_RAW); + return + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER ? + SendDataAllocBuffer(SendData, MaxBufferLength) : RawSendDataAllocBuffer(SendData, MaxBufferLength); } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -500,13 +672,11 @@ CxPlatSendDataFreeBuffer( _In_ QUIC_BUFFER* Buffer ) { - if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { - SendDataFreeBuffer(SendData, Buffer); - } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - RawSendDataFreeBuffer(SendData, Buffer); - } else { - CXPLAT_DBG_ASSERT(FALSE); - } + CXPLAT_DBG_ASSERT( + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER || + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_RAW); + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER ? + SendDataFreeBuffer(SendData, Buffer) : RawSendDataFreeBuffer(SendData, Buffer); } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -515,14 +685,11 @@ CxPlatSendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ) { - if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { - return SendDataIsFull(SendData); - } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - return RawSendDataIsFull(SendData); - } else { - CXPLAT_DBG_ASSERT(FALSE); - } - return FALSE; + CXPLAT_DBG_ASSERT( + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER || + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_RAW); + return DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER ? + SendDataIsFull(SendData) : RawSendDataIsFull(SendData); } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -533,14 +700,11 @@ CxPlatSocketSend( _In_ CXPLAT_SEND_DATA* SendData ) { - if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER) { - return SocketSend(Socket, Route, SendData); - } else if (DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_XDP) { - return RawSocketSend(SocketToRaw(Socket), Route, SendData); - } else { - CXPLAT_DBG_ASSERT(FALSE); - } - return QUIC_STATUS_NOT_SUPPORTED; + CXPLAT_DBG_ASSERT( + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER || + DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_RAW); + return DatapathType(SendData) == CXPLAT_DATAPATH_TYPE_USER ? + SocketSend(Socket, Route, SendData) : RawSocketSend(CxPlatSocketToRaw(Socket), Route, SendData); } void @@ -548,7 +712,6 @@ CxPlatDataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ) { - // TODO: refactoring switch (CxPlatCqeType(Cqe)) { case CXPLAT_CQE_TYPE_SOCKET_IO: { DATAPATH_IO_SQE* Sqe = @@ -575,7 +738,7 @@ QuicCopyRouteInfo( _In_ CXPLAT_ROUTE* SrcRoute ) { - if (SrcRoute->DatapathType == CXPLAT_DATAPATH_TYPE_XDP) { + if (SrcRoute->DatapathType == CXPLAT_DATAPATH_TYPE_RAW) { CxPlatCopyMemory(DstRoute, SrcRoute, (uint8_t*)&SrcRoute->State - (uint8_t*)SrcRoute); CxPlatUpdateRoute(DstRoute, SrcRoute); } else if (SrcRoute->DatapathType == CXPLAT_DATAPATH_TYPE_USER) { @@ -593,6 +756,7 @@ CxPlatResolveRouteComplete( _In_ uint8_t PathId ) { + CXPLAT_DBG_ASSERT(Route->DatapathType != CXPLAT_DATAPATH_TYPE_USER); if (Route->State != RouteResolved) { RawResolveRouteComplete(Connection, Route, PhysicalAddress, PathId); } @@ -611,10 +775,10 @@ CxPlatResolveRoute( _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback ) { - if (Socket->UseTcp || Route->DatapathType == CXPLAT_DATAPATH_TYPE_XDP || + if (Socket->UseTcp || Route->DatapathType == CXPLAT_DATAPATH_TYPE_RAW || (Route->DatapathType == CXPLAT_DATAPATH_TYPE_UNKNOWN && Socket->RawSocketAvailable && !IS_LOOPBACK(Route->RemoteAddress))) { - return RawResolveRoute(SocketToRaw(Socket), Route, PathId, Context, Callback); + return RawResolveRoute(CxPlatSocketToRaw(Socket), Route, PathId, Context, Callback); } Route->State = RouteResolved; return QUIC_STATUS_SUCCESS; @@ -627,7 +791,7 @@ CxPlatUpdateRoute( _In_ CXPLAT_ROUTE* SrcRoute ) { - if (SrcRoute->DatapathType == CXPLAT_DATAPATH_TYPE_XDP || + if (SrcRoute->DatapathType == CXPLAT_DATAPATH_TYPE_RAW || (SrcRoute->DatapathType == CXPLAT_DATAPATH_TYPE_UNKNOWN && !IS_LOOPBACK(SrcRoute->RemoteAddress))) { RawUpdateRoute(DstRoute, SrcRoute); diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index c202c9d887..9be032392e 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1071,242 +1071,6 @@ DataPathIsPaddingPreferred( return !!(Datapath->Features & CXPLAT_DATAPATH_FEATURE_SEND_SEGMENTATION); } -_IRQL_requires_max_(PASSIVE_LEVEL) -_Success_(QUIC_SUCCEEDED(return)) -QUIC_STATUS -DataPathGetLocalAddresses( - _In_ CXPLAT_DATAPATH* Datapath, - _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) - CXPLAT_ADAPTER_ADDRESS** Addresses, - _Out_ uint32_t* AddressesCount - ) -{ - const ULONG Flags = - GAA_FLAG_INCLUDE_ALL_INTERFACES | - GAA_FLAG_SKIP_ANYCAST | - GAA_FLAG_SKIP_MULTICAST | - GAA_FLAG_SKIP_DNS_SERVER | - GAA_FLAG_SKIP_FRIENDLY_NAME | - GAA_FLAG_SKIP_DNS_INFO; - - UNREFERENCED_PARAMETER(Datapath); - - ULONG AdapterAddressesSize = 0; - PIP_ADAPTER_ADDRESSES AdapterAddresses = NULL; - uint32_t Index = 0; - - QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - ULONG Error; - do { - Error = - GetAdaptersAddresses( - AF_UNSPEC, - Flags, - NULL, - AdapterAddresses, - &AdapterAddressesSize); - if (Error == ERROR_BUFFER_OVERFLOW) { - if (AdapterAddresses) { - CXPLAT_FREE(AdapterAddresses, QUIC_POOL_DATAPATH_ADDRESSES); - } - AdapterAddresses = CXPLAT_ALLOC_NONPAGED(AdapterAddressesSize, QUIC_POOL_DATAPATH_ADDRESSES); - if (!AdapterAddresses) { - Error = ERROR_NOT_ENOUGH_MEMORY; - QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "PIP_ADAPTER_ADDRESSES", - AdapterAddressesSize); - } - } - } while (Error == ERROR_BUFFER_OVERFLOW); - - if (Error != ERROR_SUCCESS) { - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - Error, - "GetAdaptersAddresses"); - Status = HRESULT_FROM_WIN32(Error); - goto Exit; - } - - for (PIP_ADAPTER_ADDRESSES Iter = AdapterAddresses; Iter != NULL; Iter = Iter->Next) { - for (PIP_ADAPTER_UNICAST_ADDRESS_LH Iter2 = Iter->FirstUnicastAddress; Iter2 != NULL; Iter2 = Iter2->Next) { - Index++; - } - } - - if (Index == 0) { - QuicTraceEvent( - LibraryError, - "[ lib] ERROR, %s.", - "No local unicast addresses found"); - Status = QUIC_STATUS_NOT_FOUND; - goto Exit; - } - - *Addresses = CXPLAT_ALLOC_NONPAGED(Index * sizeof(CXPLAT_ADAPTER_ADDRESS), QUIC_POOL_DATAPATH_ADDRESSES); - if (*Addresses == NULL) { - Status = QUIC_STATUS_OUT_OF_MEMORY; - QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "Addresses", - Index * sizeof(CXPLAT_ADAPTER_ADDRESS)); - goto Exit; - } - - CxPlatZeroMemory(*Addresses, Index * sizeof(CXPLAT_ADAPTER_ADDRESS)); - *AddressesCount = Index; - Index = 0; - - for (PIP_ADAPTER_ADDRESSES Iter = AdapterAddresses; Iter != NULL; Iter = Iter->Next) { - for (PIP_ADAPTER_UNICAST_ADDRESS_LH Iter2 = Iter->FirstUnicastAddress; Iter2 != NULL; Iter2 = Iter2->Next) { - CxPlatCopyMemory( - &(*Addresses)[Index].Address, - Iter2->Address.lpSockaddr, - sizeof(QUIC_ADDR)); - (*Addresses)[Index].InterfaceIndex = - Iter2->Address.lpSockaddr->sa_family == AF_INET ? - (uint32_t)Iter->IfIndex : (uint32_t)Iter->Ipv6IfIndex; - (*Addresses)[Index].InterfaceType = (uint16_t)Iter->IfType; - (*Addresses)[Index].OperationStatus = (CXPLAT_OPERATION_STATUS)Iter->OperStatus; - Index++; - } - } - -Exit: - - if (AdapterAddresses) { - CXPLAT_FREE(AdapterAddresses, QUIC_POOL_DATAPATH_ADDRESSES); - } - - return Status; -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -_Success_(QUIC_SUCCEEDED(return)) -QUIC_STATUS -DataPathGetGatewayAddresses( - _In_ CXPLAT_DATAPATH* Datapath, - _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) - QUIC_ADDR** GatewayAddresses, - _Out_ uint32_t* GatewayAddressesCount - ) -{ - const ULONG Flags = - GAA_FLAG_INCLUDE_GATEWAYS | - GAA_FLAG_INCLUDE_ALL_INTERFACES | - GAA_FLAG_SKIP_DNS_SERVER | - GAA_FLAG_SKIP_MULTICAST; - - UNREFERENCED_PARAMETER(Datapath); - - ULONG AdapterAddressesSize = 0; - PIP_ADAPTER_ADDRESSES AdapterAddresses = NULL; - uint32_t Index = 0; - - QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - ULONG Error; - do { - Error = - GetAdaptersAddresses( - AF_UNSPEC, - Flags, - NULL, - AdapterAddresses, - &AdapterAddressesSize); - if (Error == ERROR_BUFFER_OVERFLOW) { - if (AdapterAddresses) { - CXPLAT_FREE(AdapterAddresses, QUIC_POOL_DATAPATH_ADDRESSES); - } - AdapterAddresses = CXPLAT_ALLOC_NONPAGED(AdapterAddressesSize, QUIC_POOL_DATAPATH_ADDRESSES); - if (!AdapterAddresses) { - Error = ERROR_NOT_ENOUGH_MEMORY; - QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "PIP_ADAPTER_ADDRESSES", - AdapterAddressesSize); - } - } - } while (Error == ERROR_BUFFER_OVERFLOW); - - if (Error != ERROR_SUCCESS) { - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - Error, - "GetAdaptersAddresses"); - Status = HRESULT_FROM_WIN32(Error); - goto Exit; - } - - for (PIP_ADAPTER_ADDRESSES Iter = AdapterAddresses; Iter != NULL; Iter = Iter->Next) { - for (PIP_ADAPTER_GATEWAY_ADDRESS_LH Iter2 = Iter->FirstGatewayAddress; Iter2 != NULL; Iter2 = Iter2->Next) { - Index++; - } - } - - if (Index == 0) { - QuicTraceEvent( - LibraryError, - "[ lib] ERROR, %s.", - "No gateway server addresses found"); - Status = QUIC_STATUS_NOT_FOUND; - goto Exit; - } - - *GatewayAddresses = CXPLAT_ALLOC_NONPAGED(Index * sizeof(QUIC_ADDR), QUIC_POOL_DATAPATH_ADDRESSES); - if (*GatewayAddresses == NULL) { - Status = QUIC_STATUS_OUT_OF_MEMORY; - QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "GatewayAddresses", - Index * sizeof(QUIC_ADDR)); - goto Exit; - } - - CxPlatZeroMemory(*GatewayAddresses, Index * sizeof(QUIC_ADDR)); - *GatewayAddressesCount = Index; - Index = 0; - - for (PIP_ADAPTER_ADDRESSES Iter = AdapterAddresses; Iter != NULL; Iter = Iter->Next) { - for (PIP_ADAPTER_GATEWAY_ADDRESS_LH Iter2 = Iter->FirstGatewayAddress; Iter2 != NULL; Iter2 = Iter2->Next) { - CxPlatCopyMemory( - &(*GatewayAddresses)[Index], - Iter2->Address.lpSockaddr, - sizeof(QUIC_ADDR)); - Index++; - } - } - -Exit: - - if (AdapterAddresses) { - CXPLAT_FREE(AdapterAddresses, QUIC_POOL_DATAPATH_ADDRESSES); - } - - return Status; -} - -// TODO: DELETE -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -DataPathResolveAddress( - _In_ CXPLAT_DATAPATH* Datapath, - _In_z_ const char* HostName, - _Inout_ QUIC_ADDR* Address - ) -{ - UNREFERENCED_PARAMETER(Datapath); - UNREFERENCED_PARAMETER(HostName); - UNREFERENCED_PARAMETER(Address); - return QUIC_STATUS_NOT_SUPPORTED; -} - void CxPlatSocketArmRioNotify( _In_ CXPLAT_SOCKET_PROC* SocketProc @@ -1377,7 +1141,7 @@ SocketCreateUdp( Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; } - CXPLAT_SOCKET* Socket = RawToSocket(RawSocket); + CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); QuicTraceEvent( DatapathCreated, @@ -1963,7 +1727,7 @@ SocketCreateUdp( Error: if (RawSocket != NULL) { - SocketDelete(RawToSocket(RawSocket)); + SocketDelete(CxPlatRawToSocket(RawSocket)); } return Status; @@ -2000,7 +1764,7 @@ CxPlatSocketCreateTcpInternal( Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; } - CXPLAT_SOCKET* Socket = RawToSocket(RawSocket); + CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); QuicTraceEvent( DatapathCreated, @@ -2222,7 +1986,7 @@ CxPlatSocketCreateTcpInternal( Error: if (RawSocket != NULL) { - SocketDelete(RawToSocket(RawSocket)); + SocketDelete(CxPlatRawToSocket(RawSocket)); } return Status; @@ -2275,7 +2039,7 @@ SocketCreateTcpListener( Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; } - CXPLAT_SOCKET* Socket = RawToSocket(RawSocket); + CXPLAT_SOCKET* Socket = CxPlatRawToSocket(RawSocket); QuicTraceEvent( DatapathCreated, @@ -2464,7 +2228,7 @@ SocketCreateTcpListener( Error: if (RawSocket != NULL) { - SocketDelete(RawToSocket(RawSocket)); + SocketDelete(CxPlatRawToSocket(RawSocket)); } return Status; @@ -2517,7 +2281,7 @@ CxPlatSocketRelease( CXPLAT_DBG_ASSERT(!Socket->Freed); CXPLAT_DBG_ASSERT(Socket->Uninitialized); Socket->Freed = TRUE; - CXPLAT_FREE(SocketToRaw(Socket), QUIC_POOL_SOCKET); + CXPLAT_FREE(CxPlatSocketToRaw(Socket), QUIC_POOL_SOCKET); } } @@ -2565,7 +2329,7 @@ CxPlatSocketContextRelease( } SocketProc->Freed = TRUE; - CxPlatSocketRelease(SocketProc->Parent); // + CxPlatSocketRelease(SocketProc->Parent); } } @@ -2646,56 +2410,6 @@ CxPlatSocketContextUninitialize( CxPlatSocketContextRelease(SocketProc); } -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -SocketUpdateQeo( - _In_ CXPLAT_SOCKET* Socket, - _In_reads_(OffloadCount) - const CXPLAT_QEO_CONNECTION* Offloads, - _In_ uint32_t OffloadCount - ) -{ - UNREFERENCED_PARAMETER(Socket); - UNREFERENCED_PARAMETER(Offloads); - UNREFERENCED_PARAMETER(OffloadCount); - return QUIC_STATUS_NOT_SUPPORTED; -} - -// TODO: remove? -_IRQL_requires_max_(DISPATCH_LEVEL) -UINT16 -SocketGetLocalMtu( - _In_ CXPLAT_SOCKET* Socket - ) -{ - CXPLAT_DBG_ASSERT(Socket != NULL); - return Socket->Mtu; -} - -// TODO: remove -_IRQL_requires_max_(DISPATCH_LEVEL) -void -SocketGetLocalAddress( - _In_ CXPLAT_SOCKET* Socket, - _Out_ QUIC_ADDR* Address - ) -{ - CXPLAT_DBG_ASSERT(Socket != NULL); - *Address = Socket->LocalAddress; -} - -// TODO: remove -_IRQL_requires_max_(DISPATCH_LEVEL) -void -SocketGetRemoteAddress( - _In_ CXPLAT_SOCKET* Socket, - _Out_ QUIC_ADDR* Address - ) -{ - CXPLAT_DBG_ASSERT(Socket != NULL); - *Address = Socket->RemoteAddress; -} - void* RioRecvBufferAllocate( _In_ uint32_t Size, @@ -2793,7 +2507,7 @@ CxPlatSocketStartAccept( if (ListenerSocketProc->AcceptSocket == NULL) { Status = CxPlatSocketCreateTcpInternal( - ListenerSocketProc->Parent->Datapath, + Datapath, CXPLAT_SOCKET_TCP_SERVER, NULL, NULL, @@ -2940,7 +2654,7 @@ CxPlatDataPathSocketProcessAcceptCompletion( CxPlatDataPathStartReceiveAsync(AcceptSocketProc); AcceptSocketProc->IoStarted = TRUE; - ListenerSocketProc->Parent->Datapath->TcpHandlers.Accept( + Datapath->TcpHandlers.Accept( ListenerSocketProc->Parent, ListenerSocketProc->Parent->ClientContext, ListenerSocketProc->AcceptSocket, @@ -3560,6 +3274,7 @@ CxPlatDataPathSocketProcessRioCompletion( { UNREFERENCED_PARAMETER(Cqe); CXPLAT_SOCKET_PROC* SocketProc = CONTAINING_RECORD(Sqe, CXPLAT_SOCKET_PROC, RioSqe); + CXPLAT_DATAPATH* Datapath = SocketProc->DatapathProc->Datapath; ULONG ResultCount; BOOLEAN UpcallAcquired; ULONG TotalResultCount = 0; @@ -3573,7 +3288,7 @@ CxPlatDataPathSocketProcessRioCompletion( RIORESULT Results[32]; ResultCount = - SocketProc->DatapathProc->Datapath->RioDispatch.RIODequeueCompletion( + Datapath->RioDispatch.RIODequeueCompletion( SocketProc->RioCq, Results, RTL_NUMBER_OF(Results)); CXPLAT_FRE_ASSERT(ResultCount != RIO_CORRUPT_CQ); @@ -3745,7 +3460,7 @@ CxPlatFreeRxIoBlock( _IRQL_requires_max_(DISPATCH_LEVEL) void RecvDataReturn( - _In_opt_ CXPLAT_RECV_DATA* RecvDataChain + _In_ CXPLAT_RECV_DATA* RecvDataChain ) { LONG BatchedBufferCount = 0; @@ -3890,11 +3605,11 @@ RioSendDataFree( ) { CXPLAT_SEND_DATA* SendData = Entry; - + CXPLAT_DATAPATH* Datapath = SendData->Owner->Datapath; UNREFERENCED_PARAMETER(Pool); CXPLAT_DBG_ASSERT(SendData->RioBufferId != RIO_INVALID_BUFFERID); - SendData->Owner->Datapath->RioDispatch.RIODeregisterBuffer(SendData->RioBufferId); + Datapath->RioDispatch.RIODeregisterBuffer(SendData->RioBufferId); CxPlatLargeFree(SendData, Tag); } @@ -3932,6 +3647,7 @@ SendDataAlloc( SendData->WsaBufferCount = 0; SendData->ClientBuffer.len = 0; SendData->ClientBuffer.buf = NULL; + SendData->DatapathType = Config->Route->DatapathType = CXPLAT_DATAPATH_TYPE_USER; #if DEBUG SendData->Sqe.IoType = 0; #endif @@ -3949,7 +3665,7 @@ SendDataAlloc( } } - return (CXPLAT_SEND_DATA*)SendData; + return SendData; } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -4037,11 +3753,12 @@ RioSendBufferFree( ) { CXPLAT_RIO_SEND_BUFFER_HEADER* RioHeader = RioSendBufferHeaderFromBuffer(Entry); + CXPLAT_DATAPATH* Datapath = RioHeader->Datapath; UNREFERENCED_PARAMETER(Pool); CXPLAT_DBG_ASSERT(RioHeader->RioBufferId != RIO_INVALID_BUFFERID); - RioHeader->Datapath->RioDispatch.RIODeregisterBuffer(RioHeader->RioBufferId); + Datapath->RioDispatch.RIODeregisterBuffer(RioHeader->RioBufferId); CxPlatLargeFree(RioHeader, Tag); } @@ -4667,46 +4384,3 @@ DataPathProcessCqe( default: CXPLAT_DBG_ASSERT(FALSE); break; } } - -void -ResolveRouteComplete( - _In_ void* Context, - _Inout_ CXPLAT_ROUTE* Route, - _In_reads_bytes_(6) const uint8_t* PhysicalAddress, - _In_ uint8_t PathId - ) -{ - UNREFERENCED_PARAMETER(Context); - UNREFERENCED_PARAMETER(Route); - UNREFERENCED_PARAMETER(PhysicalAddress); - UNREFERENCED_PARAMETER(PathId); -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -ResolveRoute( - _In_ CXPLAT_SOCKET* Socket, - _Inout_ CXPLAT_ROUTE* Route, - _In_ uint8_t PathId, - _In_ void* Context, - _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback - ) -{ - UNREFERENCED_PARAMETER(Socket); - UNREFERENCED_PARAMETER(PathId); - UNREFERENCED_PARAMETER(Context); - UNREFERENCED_PARAMETER(Callback); - Route->State = RouteResolved; - return QUIC_STATUS_SUCCESS; -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -void -UpdateRoute( - _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute - ) -{ - UNREFERENCED_PARAMETER(DstRoute); - UNREFERENCED_PARAMETER(SrcRoute); -} diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index cbc6751fbb..8a41f762b7 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -39,8 +39,6 @@ #endif -// TODO: create header files for each datapath - typedef struct DATAPATH_SQE { uint32_t CqeType; #ifdef CXPLAT_SQE @@ -48,7 +46,6 @@ typedef struct DATAPATH_SQE { #endif } DATAPATH_SQE; -// not needed? typedef struct CXPLAT_DATAPATH_COMMON { // // The UDP callback function pointers. @@ -82,13 +79,10 @@ typedef struct CXPLAT_SEND_DATA_COMMON { uint8_t ECN; // CXPLAT_ECN_TYPE } CXPLAT_SEND_DATA_COMMON; - typedef enum CXPLAT_DATAPATH_TYPE { CXPLAT_DATAPATH_TYPE_UNKNOWN = 0, CXPLAT_DATAPATH_TYPE_USER, - CXPLAT_DATAPATH_TYPE_KERNEL, - CXPLAT_DATAPATH_TYPE_XDP, - // DPDK? + CXPLAT_DATAPATH_TYPE_RAW, // currently raw == xdp } CXPLAT_DATAPATH_TYPE; #ifdef _KERNEL_MODE @@ -526,7 +520,6 @@ typedef struct CXPLAT_SOCKET { typedef struct CXPLAT_SOCKET_RAW CXPLAT_SOCKET_RAW; -// TODO: move to common area _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS SocketCreateUdp( @@ -595,45 +588,10 @@ DataPathIsPaddingPreferred( _In_ CXPLAT_DATAPATH* Datapath ); -_IRQL_requires_max_(PASSIVE_LEVEL) -_Success_(QUIC_SUCCEEDED(return)) -QUIC_STATUS -DataPathGetLocalAddresses( - _In_ CXPLAT_DATAPATH* Datapath, - _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) - CXPLAT_ADAPTER_ADDRESS** Addresses, - _Out_ uint32_t* AddressesCount - ); - -_IRQL_requires_max_(PASSIVE_LEVEL) -_Success_(QUIC_SUCCEEDED(return)) -QUIC_STATUS -DataPathGetGatewayAddresses( - _In_ CXPLAT_DATAPATH* Datapath, - _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) - QUIC_ADDR** GatewayAddresses, - _Out_ uint32_t* GatewayAddressesCount - ); - -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -SocketUpdateQeo( - _In_ CXPLAT_SOCKET* Socket, - _In_reads_(OffloadCount) - const CXPLAT_QEO_CONNECTION* Offloads, - _In_ uint32_t OffloadCount - ); - -_IRQL_requires_max_(DISPATCH_LEVEL) -UINT16 -SocketGetLocalMtu( - _In_ CXPLAT_SOCKET* Socket - ); - _IRQL_requires_max_(DISPATCH_LEVEL) void RecvDataReturn( - _In_opt_ CXPLAT_RECV_DATA* RecvDataChain + _In_ CXPLAT_RECV_DATA* RecvDataChain ); _IRQL_requires_max_(DISPATCH_LEVEL) @@ -679,47 +637,23 @@ SocketSend( _In_ CXPLAT_SEND_DATA* SendData ); -void -ResolveRouteComplete( - _In_ void* Context, - _Inout_ CXPLAT_ROUTE* Route, - _In_reads_bytes_(6) const uint8_t* PhysicalAddress, - _In_ uint8_t PathId - ); - -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -ResolveRoute( - _In_ CXPLAT_SOCKET* Sock, - _Inout_ CXPLAT_ROUTE* Route, - _In_ uint8_t PathId, - _In_ void* Context, - _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback - ); - void DataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ); -_IRQL_requires_max_(PASSIVE_LEVEL) -void -UpdateRoute( - _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute - ); - - -// TODO: rename CXPLAT_SOCKET* -RawToSocket(CXPLAT_SOCKET_RAW* Socket); +CxPlatRawToSocket( + _In_ CXPLAT_SOCKET_RAW* Socket + ); -// TODO: rename CXPLAT_SOCKET_RAW* -SocketToRaw(CXPLAT_SOCKET* Socket); +CxPlatSocketToRaw( + _In_ CXPLAT_SOCKET* Socketh + ); uint32_t -CxPlatGetRawSocketSize (); +CxPlatGetRawSocketSize(void); _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS @@ -787,7 +721,7 @@ RawSocketGetLocalMtu( _IRQL_requires_max_(DISPATCH_LEVEL) void RawRecvDataReturn( - _In_opt_ CXPLAT_RECV_DATA* RecvDataChain + _In_ CXPLAT_RECV_DATA* RecvDataChain ); _IRQL_requires_max_(DISPATCH_LEVEL) From 41af217801dc6670dde7efd0a01a74cb4a5472ba Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 19 Sep 2023 13:03:46 -0700 Subject: [PATCH 74/87] fix uwp build --- src/platform/datapath_raw_dummy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index e32d690635..f832112e8e 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -134,7 +134,7 @@ RawSocketGetLocalMtu( _IRQL_requires_max_(DISPATCH_LEVEL) void RawRecvDataReturn( - _In_opt_ CXPLAT_RECV_DATA* RecvDataChain + _In_ CXPLAT_RECV_DATA* RecvDataChain ) { UNREFERENCED_PARAMETER(RecvDataChain); From 2fbe7a221853c14f33ed103baa31a4cca33a373e Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 19 Sep 2023 13:33:58 -0700 Subject: [PATCH 75/87] fix clog and artifact name --- .github/workflows/stress.yml | 2 +- .github/workflows/test.yml | 4 ++-- src/generated/linux/datapath_winuser.c.clog.h | 18 ------------------ .../linux/datapath_winuser.c.clog.h.lttng.h | 19 ------------------- 4 files changed, 3 insertions(+), 40 deletions(-) diff --git a/.github/workflows/stress.yml b/.github/workflows/stress.yml index f754ffcb13..fa2c8f2915 100644 --- a/.github/workflows/stress.yml +++ b/.github/workflows/stress.yml @@ -108,7 +108,7 @@ jobs: - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a if: matrix.vec.plat == 'windows' with: - name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os == 'WSPrerelease' && 'windows-2022' || matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.xdp }}${{ matrix.vec.sanitize }}${{ matrix.vec.test }} + name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os == 'WSPrerelease' && 'windows-2022' || matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.sanitize }}${{ matrix.vec.test }} path: artifacts - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a if: matrix.vec.plat == 'linux' || matrix.vec.plat == 'macos' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4f6df79272..f8e8c33216 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -127,7 +127,7 @@ jobs: uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a if: matrix.vec.plat == 'windows' with: # note that BVT for WSPrerelease uses binaries built on windows-2022. - name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os == 'WSPrerelease' && 'windows-2022' || matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.xdp }}${{ matrix.vec.sanitize }}${{ matrix.vec.test }} + name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os == 'WSPrerelease' && 'windows-2022' || matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.sanitize }}${{ matrix.vec.test }} path: artifacts - name: Download Build Artifacts uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a @@ -234,7 +234,7 @@ jobs: fetch-depth: 0 - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a with: - name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.xdp }}${{ matrix.vec.sanitize }}${{ matrix.vec.test }} + name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.sanitize }}${{ matrix.vec.test }} path: artifacts - name: Prepare Machine run: scripts/prepare-machine.ps1 -ForTest ${{ matrix.vec.xdp }} diff --git a/src/generated/linux/datapath_winuser.c.clog.h b/src/generated/linux/datapath_winuser.c.clog.h index 6382fa23c2..7926a2bbe0 100644 --- a/src/generated/linux/datapath_winuser.c.clog.h +++ b/src/generated/linux/datapath_winuser.c.clog.h @@ -291,24 +291,6 @@ tracepoint(CLOG_DATAPATH_WINUSER_C, AllocFailure , arg2, arg3);\ -/*---------------------------------------------------------- -// Decoder Ring for LibraryError -// [ lib] ERROR, %s. -// QuicTraceEvent( - LibraryError, - "[ lib] ERROR, %s.", - "No local unicast addresses found"); -// arg2 = arg2 = "No local unicast addresses found" = arg2 -----------------------------------------------------------*/ -#ifndef _clog_3_ARGS_TRACE_LibraryError -#define _clog_3_ARGS_TRACE_LibraryError(uniqueId, encoded_arg_string, arg2)\ -tracepoint(CLOG_DATAPATH_WINUSER_C, LibraryError , arg2);\ - -#endif - - - - /*---------------------------------------------------------- // Decoder Ring for DatapathErrorStatus // [data][%p] ERROR, %u, %s. diff --git a/src/generated/linux/datapath_winuser.c.clog.h.lttng.h b/src/generated/linux/datapath_winuser.c.clog.h.lttng.h index 35d96341c5..2bf4cac555 100644 --- a/src/generated/linux/datapath_winuser.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_winuser.c.clog.h.lttng.h @@ -291,25 +291,6 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_WINUSER_C, AllocFailure, -/*---------------------------------------------------------- -// Decoder Ring for LibraryError -// [ lib] ERROR, %s. -// QuicTraceEvent( - LibraryError, - "[ lib] ERROR, %s.", - "No local unicast addresses found"); -// arg2 = arg2 = "No local unicast addresses found" = arg2 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_WINUSER_C, LibraryError, - TP_ARGS( - const char *, arg2), - TP_FIELDS( - ctf_string(arg2, arg2) - ) -) - - - /*---------------------------------------------------------- // Decoder Ring for DatapathErrorStatus // [data][%p] ERROR, %u, %s. From 290e3f68b8f08eca393683634c14b6a05eab514e Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Tue, 19 Sep 2023 15:10:50 -0700 Subject: [PATCH 76/87] fix back xdp dependency --- .azure/azure-pipelines.perf.yml | 23 ++++++++++++++++++++++- .azure/azure-pipelines.periodic.yml | 19 ++++++++++++++++++- .github/workflows/cargo.yml | 2 +- .github/workflows/code-coverage.yml | 2 +- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/.azure/azure-pipelines.perf.yml b/.azure/azure-pipelines.perf.yml index 868d7e33e7..e4f02d4e68 100644 --- a/.azure/azure-pipelines.perf.yml +++ b/.azure/azure-pipelines.perf.yml @@ -177,6 +177,27 @@ stages: ${{ if eq(parameters.pgo_mode, true) }}: extraBuildArgs: -DisableTest -DisableTools -PGO +- ${{ if eq(parameters.winuser_xdp, true) }}: + - stage: build_winuser_xdp + displayName: Build Windows (XDP) + dependsOn: [] + variables: + runCodesignValidationInjection: false + jobs: + - template: ./templates/build-config-user.yml + parameters: + image: windows-latest + platform: windows + arch: ${{ parameters.arch }} + tls: schannel + config: Release + extraName: 'xdp' + extraPrepareArgs: -DisableTest -InstallXdpSdk + ${{ if eq(parameters.pgo_mode, false) }}: + extraBuildArgs: -DisableTest -DisableTools -ExtraArtifactDir Xdp + ${{ if eq(parameters.pgo_mode, true) }}: + extraBuildArgs: -DisableTest -DisableTools -ExtraArtifactDir Xdp -PGO + - ${{ if eq(parameters.winuser_openssl, true) }}: - stage: build_winuser_openssl displayName: Build Windows (OpenSSL) @@ -310,7 +331,7 @@ stages: - stage: perf_winuser_xdp displayName: Performance Testing Windows (XDP) dependsOn: - - build_winuser_schannel + - build_winuser_xdp jobs: - template: ./templates/run-performance.yml parameters: diff --git a/.azure/azure-pipelines.periodic.yml b/.azure/azure-pipelines.periodic.yml index 24fe06e79e..ff9d789027 100644 --- a/.azure/azure-pipelines.periodic.yml +++ b/.azure/azure-pipelines.periodic.yml @@ -69,6 +69,23 @@ stages: extraPrepareArgs: -DisableTest extraBuildArgs: -DisableTest -DisableTools -PGO +- stage: build_winuser_xdp + displayName: Build Windows (XDP) + dependsOn: [] + variables: + runCodesignValidationInjection: false + jobs: + - template: ./templates/build-config-user.yml + parameters: + image: windows-latest + platform: windows + arch: x64 + tls: schannel + config: Release + extraName: 'xdp' + extraPrepareArgs: -DisableTest -InstallXdpSdk + extraBuildArgs: -DisableTest -DisableTools -ExtraArtifactDir Xdp -PGO + # # Performance Tests # @@ -124,7 +141,7 @@ stages: - stage: perf_winuser_xdp displayName: Performance Testing Windows (XDP) dependsOn: - - build_winuser_schannel + - build_winuser_xdp jobs: - template: ./templates/run-performance.yml parameters: diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index ecd8dc0818..f22104ef69 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -27,7 +27,7 @@ jobs: - name: Checkout repository uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac - name: Prepare Machine - run: scripts/prepare-machine.ps1 -Tls openssl -ForBuild + run: scripts/prepare-machine.ps1 -Tls openssl -ForBuild -UseXdp shell: pwsh - name: Install Perl if: runner.os == 'Windows' diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 86b8c87533..a4dcfd62e5 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -142,7 +142,7 @@ jobs: with: fetch-depth: 0 - name: Prepare Machine - run: scripts/prepare-machine.ps1 -ForTest -InstallCodeCoverage + run: scripts/prepare-machine.ps1 -ForTest -InstallCodeCoverage -UseXdp shell: pwsh - name: Create Folder for Artifacts shell: pwsh From e933f7318de171d1e776ac966e202cc9c0fd5b1f Mon Sep 17 00:00:00 2001 From: Nick Banks Date: Wed, 20 Sep 2023 10:02:37 -0400 Subject: [PATCH 77/87] Simply build automation --- .azure/azure-pipelines.perf.yml | 26 ++------------------------ .azure/templates/build-config-user.yml | 2 +- .github/workflows/build-reuse-win.yml | 6 +----- .github/workflows/build.yml | 10 ---------- .github/workflows/cargo.yml | 2 +- .github/workflows/code-coverage.yml | 2 +- .github/workflows/stress.yml | 6 ------ .github/workflows/test.yml | 6 ------ scripts/performance.ps1 | 8 ++++---- scripts/prepare-machine.ps1 | 16 +++++----------- 10 files changed, 15 insertions(+), 69 deletions(-) diff --git a/.azure/azure-pipelines.perf.yml b/.azure/azure-pipelines.perf.yml index e4f02d4e68..85c73a752f 100644 --- a/.azure/azure-pipelines.perf.yml +++ b/.azure/azure-pipelines.perf.yml @@ -157,7 +157,7 @@ stages: arch: ${{ parameters.arch }} config: Release -- ${{ if or(eq(parameters.winkernel, true), eq(parameters.winuser_schannel, true)) }}: +- ${{ if or(eq(parameters.winkernel, true), eq(parameters.winuser_schannel, true), eq(parameters.winuser_xdp, true)) }}: - stage: build_winuser_schannel displayName: Build Windows (Schannel) dependsOn: [] @@ -177,27 +177,6 @@ stages: ${{ if eq(parameters.pgo_mode, true) }}: extraBuildArgs: -DisableTest -DisableTools -PGO -- ${{ if eq(parameters.winuser_xdp, true) }}: - - stage: build_winuser_xdp - displayName: Build Windows (XDP) - dependsOn: [] - variables: - runCodesignValidationInjection: false - jobs: - - template: ./templates/build-config-user.yml - parameters: - image: windows-latest - platform: windows - arch: ${{ parameters.arch }} - tls: schannel - config: Release - extraName: 'xdp' - extraPrepareArgs: -DisableTest -InstallXdpSdk - ${{ if eq(parameters.pgo_mode, false) }}: - extraBuildArgs: -DisableTest -DisableTools -ExtraArtifactDir Xdp - ${{ if eq(parameters.pgo_mode, true) }}: - extraBuildArgs: -DisableTest -DisableTools -ExtraArtifactDir Xdp -PGO - - ${{ if eq(parameters.winuser_openssl, true) }}: - stage: build_winuser_openssl displayName: Build Windows (OpenSSL) @@ -331,7 +310,7 @@ stages: - stage: perf_winuser_xdp displayName: Performance Testing Windows (XDP) dependsOn: - - build_winuser_xdp + - build_winuser_schannel jobs: - template: ./templates/run-performance.yml parameters: @@ -344,7 +323,6 @@ stages: protocol: ${{ parameters.protocol }} logProfile: ${{ parameters.logging }} timeout: ${{ parameters.timeout }} - extraArtifactDir: '_Xdp' ${{ if eq(parameters.QTIP, true) }}: extraTestArgs: -ExtraArtifactDir _Xdp -XDP -QTIP ${{ else }}: diff --git a/.azure/templates/build-config-user.yml b/.azure/templates/build-config-user.yml index 6e46977cab..9639dbfd8f 100644 --- a/.azure/templates/build-config-user.yml +++ b/.azure/templates/build-config-user.yml @@ -43,7 +43,7 @@ jobs: inputs: pwsh: true filePath: scripts/prepare-machine.ps1 - arguments: -Tls ${{ parameters.tls }} -ForBuild ${{ parameters.extraPrepareArgs }} -UseXdp + arguments: -Tls ${{ parameters.tls }} -ForBuild ${{ parameters.extraPrepareArgs }} - task: PowerShell@2 displayName: Build Source Code (Debug) diff --git a/.github/workflows/build-reuse-win.yml b/.github/workflows/build-reuse-win.yml index 2881856941..cfca5d1906 100644 --- a/.github/workflows/build-reuse-win.yml +++ b/.github/workflows/build-reuse-win.yml @@ -47,10 +47,6 @@ on: # - openssl # - openssl3 # - schannel - xdp: - required: false - default: '' - type: string static: required: false default: '' @@ -85,7 +81,7 @@ jobs: uses: ilammy/setup-nasm@321e6ed62a1fc77024a3bd853deb33645e8b22c4 - name: Prepare Machine shell: pwsh - run: scripts/prepare-machine.ps1 -ForBuild -Tls ${{ inputs.tls }} ${{ inputs.xdp }} + run: scripts/prepare-machine.ps1 -ForBuild -Tls ${{ inputs.tls }} - name: Build For Test if: inputs.test == '-Test' shell: pwsh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 397a504b90..634bcb0eed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,6 @@ jobs: arch: [x86, x64, arm64] tls: [schannel, openssl, openssl3] static: ['', '-Static'] - xdp: ['', '-UseXdp'] exclude: # OpenSSL doesn't support arm64 - tls: openssl @@ -46,15 +45,6 @@ jobs: # TODO: FIX: Static builds fail with UWP - plat: uwp static: '-Static' - # XDP not supported in UWP - - plat: uwp - xdp: '-UseXdp' - # XDP only supports x64 currently - - arch: x86 - xdp: '-UseXdp' - # XDP only supports x64 currently - - arch: arm64 - xdp: '-UseXdp' uses: ./.github/workflows/build-reuse-win.yml with: config: ${{ matrix.config }} diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index f22104ef69..ecd8dc0818 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -27,7 +27,7 @@ jobs: - name: Checkout repository uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac - name: Prepare Machine - run: scripts/prepare-machine.ps1 -Tls openssl -ForBuild -UseXdp + run: scripts/prepare-machine.ps1 -Tls openssl -ForBuild shell: pwsh - name: Install Perl if: runner.os == 'Windows' diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index a4dcfd62e5..86b8c87533 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -142,7 +142,7 @@ jobs: with: fetch-depth: 0 - name: Prepare Machine - run: scripts/prepare-machine.ps1 -ForTest -InstallCodeCoverage -UseXdp + run: scripts/prepare-machine.ps1 -ForTest -InstallCodeCoverage shell: pwsh - name: Create Folder for Artifacts shell: pwsh diff --git a/.github/workflows/stress.yml b/.github/workflows/stress.yml index fa2c8f2915..806a2f814a 100644 --- a/.github/workflows/stress.yml +++ b/.github/workflows/stress.yml @@ -28,15 +28,10 @@ jobs: vec: [ { config: "Debug", plat: "windows", os: "windows-2019", arch: "x64", tls: "openssl", test: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2019", arch: "x64", tls: "openssl3", test: "-Test" }, - { config: "Debug", plat: "windows", os: "windows-2019", arch: "x64", tls: "openssl", xdp: "-UseXdp", test: "-Test" }, - { config: "Debug", plat: "windows", os: "windows-2019", arch: "x64", tls: "openssl3", xdp: "-UseXdp", test: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", test: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", sanitize: "-Sanitize", test: "-Test" }, - { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", xdp: "-UseXdp", sanitize: "-Sanitize", test: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "openssl", test: "-Test" }, - { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "openssl", xdp: "-UseXdp", test: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "openssl3", test: "-Test" }, - { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "openssl3", xdp: "-UseXdp", test: "-Test" } ] uses: ./.github/workflows/build-reuse-win.yml with: @@ -45,7 +40,6 @@ jobs: os: ${{ matrix.vec.os }} arch: ${{ matrix.vec.arch }} tls: ${{ matrix.vec.tls }} - xdp: ${{ matrix.vec.xdp }} sanitize: ${{ matrix.vec.sanitize }} test: ${{ matrix.vec.test }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f8e8c33216..ed13e27d34 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,15 +46,10 @@ jobs: vec: [ { config: "Debug", plat: "windows", os: "windows-2019", arch: "x64", tls: "openssl", test: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2019", arch: "x64", tls: "openssl3", test: "-Test" }, - { config: "Debug", plat: "windows", os: "windows-2019", arch: "x64", tls: "openssl", xdp: "-UseXdp", test: "-Test" }, - { config: "Debug", plat: "windows", os: "windows-2019", arch: "x64", tls: "openssl3", xdp: "-UseXdp", test: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", test: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", sanitize: "-Sanitize", test: "-Test" }, - { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", xdp: "-UseXdp", sanitize: "-Sanitize", test: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "openssl", test: "-Test" }, - { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "openssl", xdp: "-UseXdp", test: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "openssl3", test: "-Test" }, - { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "openssl3", xdp: "-UseXdp", test: "-Test" }, { config: "Release", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", test: "-Test" } ] uses: ./.github/workflows/build-reuse-win.yml @@ -64,7 +59,6 @@ jobs: os: ${{ matrix.vec.os }} arch: ${{ matrix.vec.arch }} tls: ${{ matrix.vec.tls }} - xdp: ${{ matrix.vec.xdp }} sanitize: ${{ matrix.vec.sanitize }} test: ${{ matrix.vec.test }} diff --git a/scripts/performance.ps1 b/scripts/performance.ps1 index a4bb4ed148..a202ba4074 100644 --- a/scripts/performance.ps1 +++ b/scripts/performance.ps1 @@ -362,8 +362,8 @@ function LocalTeardown { } } -$RemoteExePath = Get-ExePath -PathRoot $RemoteDirectory -Platform $RemotePlatform -IsRemote $true -ExtraArtifactDir $ExtraArtifactDir -$LocalExePath = Get-ExePath -PathRoot $LocalDirectory -Platform $LocalPlatform -IsRemote $false -ExtraArtifactDir $ExtraArtifactDir +$RemoteExePath = Get-ExePath -PathRoot $RemoteDirectory -Platform $RemotePlatform -IsRemote $true +$LocalExePath = Get-ExePath -PathRoot $LocalDirectory -Platform $LocalPlatform -IsRemote $false # See if we are an AZP PR $PrBranchName = $env:SYSTEM_PULLREQUEST_TARGETBRANCH @@ -399,8 +399,8 @@ function Invoke-Test { Write-Output "Running Test $Test" - $RemoteExe = Get-ExeName -PathRoot $RemoteDirectory -Platform $RemotePlatform -IsRemote $true -TestPlat $RemoteConfig -ExtraArtifactDir $ExtraArtifactDir - $LocalExe = Get-ExeName -PathRoot $LocalDirectory -Platform $LocalPlatform -IsRemote $false -TestPlat $Test.Local -ExtraArtifactDir $ExtraArtifactDir + $RemoteExe = Get-ExeName -PathRoot $RemoteDirectory -Platform $RemotePlatform -IsRemote $true -TestPlat $RemoteConfig + $LocalExe = Get-ExeName -PathRoot $LocalDirectory -Platform $LocalPlatform -IsRemote $false -TestPlat $Test.Local # Check both Exes $RemoteExeExists = Invoke-TestCommand -Session $Session -ScriptBlock { diff --git a/scripts/prepare-machine.ps1 b/scripts/prepare-machine.ps1 index 43f5279184..c660f4fb96 100644 --- a/scripts/prepare-machine.ps1 +++ b/scripts/prepare-machine.ps1 @@ -109,17 +109,6 @@ if ($PSVersionTable.PSVersion.Major -lt 7) { "Please visit https://github.com/microsoft/msquic/blob/main/docs/BUILD.md#powershell-usage") } -if ($UseXdp) { - # Helper for XDP usage - if ($ForBuild) { - $InstallXdpSdk = $true; - } - if ($ForTest) { - $InstallXdpDriver = $true; - $InstallDuoNic = $true; - } -} - if (!$ForContainerBuild -and !$ForBuild -and !$ForTest -and !$InstallXdpDriver -and !$UninstallXdp) { # When no args are passed, assume we want to build and test everything # locally (i.e. a dev environment). Set Tls to OpenSSL to make sure @@ -151,6 +140,11 @@ if ($ForTest) { $InstallSigningCertificates = $true; } + if ($UseXdp) { + $InstallXdpDriver = $true; + $InstallDuoNic = $true; + } + #$InstallCodeCoverage = $true # Ideally we'd enable this by default, but it # hangs sometimes, so we only want to install # for jobs that absoultely need it. From 07a7e741798db5a25184f8f9adbac0befbacc212 Mon Sep 17 00:00:00 2001 From: Nick Banks Date: Wed, 20 Sep 2023 13:56:14 -0400 Subject: [PATCH 78/87] missed one --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 634bcb0eed..a591c8b3d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,7 +53,6 @@ jobs: arch: ${{ matrix.arch }} tls: ${{ matrix.tls }} static: ${{ matrix.static }} - xdp: ${{ matrix.xdp }} build-windows-kernel: name: WinKernel From dd47f827d500f9b94d8aaaea7c1ebb51e540a91c Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 20 Sep 2023 13:47:02 -0700 Subject: [PATCH 79/87] apply unification to linux and remove QUIC_USE_XDP flag --- CMakeLists.txt | 1 - scripts/build.ps1 | 3 - src/platform/CMakeLists.txt | 6 +- src/platform/datapath_epoll.c | 531 +-------------- src/platform/datapath_raw.h | 1 - src/platform/datapath_raw_linux.c | 355 ++-------- src/platform/datapath_raw_socket_linux.c | 8 +- src/platform/datapath_raw_xdp_linux.c | 12 +- src/platform/platform_internal.h | 831 +++++++++++++++-------- 9 files changed, 614 insertions(+), 1134 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 684055f708..28cc109e8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,7 +100,6 @@ option(QUIC_SKIP_CI_CHECKS "Disable CI specific build checks" OFF) option(QUIC_TELEMETRY_ASSERTS "Enable telemetry asserts in release builds" OFF) option(QUIC_USE_SYSTEM_LIBCRYPTO "Use system libcrypto if openssl TLS" OFF) option(QUIC_HIGH_RES_TIMERS "Configure the system to use high resolution timers" OFF) -option(QUIC_USE_XDP "Uses XDP instead of socket APIs" OFF) option(QUIC_OFFICIAL_RELEASE "Configured the build for an official release" OFF) set(QUIC_FOLDER_PREFIX "" CACHE STRING "Optional prefix for source group folders when using an IDE generator") set(QUIC_LIBRARY_NAME "msquic" CACHE STRING "Override the output library name") diff --git a/scripts/build.ps1 b/scripts/build.ps1 index cdbea928b3..13714cd272 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -487,9 +487,6 @@ function CMake-Generate { if ($EnableHighResolutionTimers) { $Arguments += " -DQUIC_HIGH_RES_TIMERS=on" } - if ($UseXdp) { - $Arguments += " -DQUIC_USE_XDP=on" - } if ($Platform -eq "android") { $env:PATH = "$env:ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$env:PATH" switch ($Arch) { diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt index d8cc6fe100..c624de13f2 100644 --- a/src/platform/CMakeLists.txt +++ b/src/platform/CMakeLists.txt @@ -21,11 +21,7 @@ if("${CX_PLATFORM}" STREQUAL "windows") else() set(SOURCES ${SOURCES} inline.c platform_posix.c storage_posix.c cgroup.c) if(CX_PLATFORM STREQUAL "linux" AND NOT CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") - if(QUIC_USE_XDP) - set(SOURCES ${SOURCES} datapath_raw_linux.c datapath_raw_socket.c datapath_raw_socket_linux.c datapath_raw_xdp_linux.c) - else() - set(SOURCES ${SOURCES} datapath_epoll.c) - endif() + set(SOURCES ${SOURCES} datapath_raw_linux.c datapath_raw_socket.c datapath_raw_socket_linux.c datapath_raw_xdp_linux.c datapath_epoll.c datapath_linux.c) else() set(SOURCES ${SOURCES} datapath_kqueue.c) endif() diff --git a/src/platform/datapath_epoll.c b/src/platform/datapath_epoll.c index 88181d7c60..dee9ec8925 100644 --- a/src/platform/datapath_epoll.c +++ b/src/platform/datapath_epoll.c @@ -200,249 +200,6 @@ typedef struct CXPLAT_RECV_MSG_CONTROL_BUFFER { 2 * CMSG_SPACE(sizeof(int))]; } CXPLAT_RECV_MSG_CONTROL_BUFFER; -typedef struct CXPLAT_DATAPATH_PARTITION CXPLAT_DATAPATH_PARTITION; - -// -// Socket context. -// -typedef struct QUIC_CACHEALIGN CXPLAT_SOCKET_CONTEXT { - - // - // The datapath binding this socket context belongs to. - // - CXPLAT_SOCKET* Binding; - - // - // The datapath proc context this socket belongs to. - // - CXPLAT_DATAPATH_PARTITION* DatapathPartition; - - // - // The socket FD used by this socket context. - // - int SocketFd; - - // - // The submission queue event for shutdown. - // - DATAPATH_SQE ShutdownSqe; - - // - // The submission queue event for IO. - // - DATAPATH_SQE IoSqe; - - // - // The submission queue event for flushing the send queue. - // - DATAPATH_SQE FlushTxSqe; - - // - // The head of list containg all pending sends on this socket. - // - CXPLAT_LIST_ENTRY TxQueue; - - // - // Lock around the PendingSendData list. - // - CXPLAT_LOCK TxQueueLock; - - // - // Rundown for synchronizing clean up with upcalls. - // - CXPLAT_RUNDOWN_REF UpcallRundown; - - // - // Inidicates the SQEs have been initialized. - // - BOOLEAN SqeInitialized : 1; - - // - // Inidicates if the socket has started IO processing. - // - BOOLEAN IoStarted : 1; - -#if DEBUG - uint8_t Uninitialized : 1; - uint8_t Freed : 1; -#endif - -} CXPLAT_SOCKET_CONTEXT; - -// -// Datapath binding. -// -typedef struct CXPLAT_SOCKET { - - // - // A pointer to datapath object. - // - CXPLAT_DATAPATH* Datapath; - - // - // The client context for this binding. - // - void *ClientContext; - - // - // The local address for the binding. - // - QUIC_ADDR LocalAddress; - - // - // The remote address for the binding. - // - QUIC_ADDR RemoteAddress; - - // - // Synchronization mechanism for cleanup. - // - CXPLAT_REF_COUNT RefCount; - - // - // The MTU for this binding. - // - uint16_t Mtu; - - // - // Indicates the binding connected to a remote IP address. - // - BOOLEAN Connected : 1; - - // - // Flag indicates the socket has a default remote destination. - // - BOOLEAN HasFixedRemoteAddress : 1; - - // - // Flag indicates the binding is being used for PCP. - // - BOOLEAN PcpBinding : 1; - -#if DEBUG - uint8_t Uninitialized : 1; - uint8_t Freed : 1; -#endif - - // - // Set of socket contexts one per proc. - // - CXPLAT_SOCKET_CONTEXT SocketContexts[]; - -} CXPLAT_SOCKET; - -// -// A per processor datapath context. -// -typedef struct QUIC_CACHEALIGN CXPLAT_DATAPATH_PARTITION { - - // - // A pointer to the datapath. - // - CXPLAT_DATAPATH* Datapath; - - // - // The event queue for this proc context. - // - CXPLAT_EVENTQ* EventQ; - - // - // Synchronization mechanism for cleanup. - // - CXPLAT_REF_COUNT RefCount; - - // - // The ideal processor of the context. - // - uint16_t PartitionIndex; - -#if DEBUG - uint8_t Uninitialized : 1; -#endif - - // - // Pool of receive packet contexts and buffers to be shared by all sockets - // on this core. - // - CXPLAT_POOL RecvBlockPool; - - // - // Pool of send packet contexts and buffers to be shared by all sockets - // on this core. - // - CXPLAT_POOL SendBlockPool; - -} CXPLAT_DATAPATH_PARTITION; - -// -// Represents a datapath object. -// - -typedef struct CXPLAT_DATAPATH { - - // - // UDP handlers. - // - CXPLAT_UDP_DATAPATH_CALLBACKS UdpHandlers; - - // - // Synchronization mechanism for cleanup. - // - CXPLAT_REF_COUNT RefCount; - - // - // Set of supported features. - // - uint32_t Features; - - // - // The proc count to create per proc datapath state. - // - uint32_t PartitionCount; - - // - // The length of the CXPLAT_SEND_DATA. Calculated based on the support level - // for GSO. No GSO support requires a larger send data to hold the extra - // iovec structs. - // - uint32_t SendDataSize; - - // - // When not using GSO, we preallocate multiple iovec structs to use with - // sendmmsg (to simulate GSO). - // - uint32_t SendIoVecCount; - - // - // The length of the CXPLAT_RECV_DATA and client data part of the - // DATAPATH_RX_IO_BLOCK. - // - uint32_t RecvBlockStride; - - // - // The offset of the raw buffer in the DATAPATH_RX_IO_BLOCK. - // - uint32_t RecvBlockBufferOffset; - - // - // The total length of the DATAPATH_RX_IO_BLOCK. Calculated based on the - // support level for GRO. No GRO only uses a single CXPLAT_RECV_DATA and - // client data, while GRO allows for multiple. - // - uint32_t RecvBlockSize; - -#if DEBUG - uint8_t Uninitialized : 1; - uint8_t Freed : 1; -#endif - - // - // The per proc datapath contexts. - // - CXPLAT_DATAPATH_PARTITION Partitions[]; - -} CXPLAT_DATAPATH; - #ifdef DEBUG #define CXPLAT_DBG_ASSERT_CMSG(CMsg, type) \ if (CMsg->cmsg_len < CMSG_LEN(sizeof(type))) { \ @@ -601,16 +358,16 @@ CxPlatProcessorContextInitialize( } QUIC_STATUS -CxPlatDataPathInitialize( +DataPathInitialize( _In_ uint32_t ClientRecvDataLength, _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, _In_opt_ QUIC_EXECUTION_CONFIG* Config, - _Out_ CXPLAT_DATAPATH** NewDataPath + _Out_ CXPLAT_DATAPATH** NewDatapath ) { UNREFERENCED_PARAMETER(TcpCallbacks); - if (NewDataPath == NULL) { + if (NewDatapath == NULL) { return QUIC_STATUS_INVALID_PARAMETER; } if (UdpCallbacks != NULL) { @@ -658,7 +415,7 @@ CxPlatDataPathInitialize( } CXPLAT_FRE_ASSERT(CxPlatRundownAcquire(&CxPlatWorkerRundown)); - *NewDataPath = Datapath; + *NewDatapath = Datapath; return QUIC_STATUS_SUCCESS; } @@ -698,7 +455,7 @@ CxPlatProcessorContextRelease( } void -CxPlatDataPathUninitialize( +DataPathUninitialize( _In_ CXPLAT_DATAPATH* Datapath ) { @@ -716,7 +473,7 @@ CxPlatDataPathUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatDataPathUpdateConfig( +DataPathUpdateConfig( _In_ CXPLAT_DATAPATH* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ) @@ -727,7 +484,7 @@ CxPlatDataPathUpdateConfig( _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t -CxPlatDataPathGetSupportedFeatures( +DataPathGetSupportedFeatures( _In_ CXPLAT_DATAPATH* Datapath ) { @@ -735,157 +492,13 @@ CxPlatDataPathGetSupportedFeatures( } BOOLEAN -CxPlatDataPathIsPaddingPreferred( - _In_ CXPLAT_DATAPATH* Datapath, - _In_ CXPLAT_SEND_DATA* SendData +DataPathIsPaddingPreferred( + _In_ CXPLAT_DATAPATH* Datapath ) { - UNREFERENCED_PARAMETER(SendData); return !!(Datapath->Features & CXPLAT_DATAPATH_FEATURE_SEND_SEGMENTATION); } -void -CxPlatDataPathPopulateTargetAddress( - _In_ QUIC_ADDRESS_FAMILY Family, - _In_ ADDRINFO* AddrInfo, - _Out_ QUIC_ADDR* Address - ) -{ - struct sockaddr_in6* SockAddrIn6 = NULL; - struct sockaddr_in* SockAddrIn = NULL; - - CxPlatZeroMemory(Address, sizeof(QUIC_ADDR)); - - if (AddrInfo->ai_addr->sa_family == AF_INET6) { - CXPLAT_DBG_ASSERT(sizeof(struct sockaddr_in6) == AddrInfo->ai_addrlen); - - // - // Is this a mapped ipv4 one? - // - - SockAddrIn6 = (struct sockaddr_in6*)AddrInfo->ai_addr; - - if (Family == QUIC_ADDRESS_FAMILY_UNSPEC && IN6_IS_ADDR_V4MAPPED(&SockAddrIn6->sin6_addr)) { - SockAddrIn = &Address->Ipv4; - - // - // Get the ipv4 address from the mapped address. - // - - SockAddrIn->sin_family = QUIC_ADDRESS_FAMILY_INET; - memcpy(&SockAddrIn->sin_addr.s_addr, &SockAddrIn6->sin6_addr.s6_addr[12], 4); - SockAddrIn->sin_port = SockAddrIn6->sin6_port; - - return; - } - Address->Ipv6 = *SockAddrIn6; - Address->Ipv6.sin6_family = QUIC_ADDRESS_FAMILY_INET6; - return; - } - - if (AddrInfo->ai_addr->sa_family == AF_INET) { - CXPLAT_DBG_ASSERT(sizeof(struct sockaddr_in) == AddrInfo->ai_addrlen); - SockAddrIn = (struct sockaddr_in*)AddrInfo->ai_addr; - Address->Ipv4 = *SockAddrIn; - Address->Ipv4.sin_family = QUIC_ADDRESS_FAMILY_INET; - return; - } - - CXPLAT_FRE_ASSERT(FALSE); -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -_Success_(QUIC_SUCCEEDED(return)) -QUIC_STATUS -CxPlatDataPathGetLocalAddresses( - _In_ CXPLAT_DATAPATH* Datapath, - _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) - CXPLAT_ADAPTER_ADDRESS** Addresses, - _Out_ uint32_t* AddressesCount - ) -{ - UNREFERENCED_PARAMETER(Datapath); - *Addresses = NULL; - *AddressesCount = 0; - return QUIC_STATUS_NOT_SUPPORTED; -} - -QUIC_STATUS -CxPlatDataPathGetGatewayAddresses( - _In_ CXPLAT_DATAPATH* Datapath, - _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) - QUIC_ADDR** GatewayAddresses, - _Out_ uint32_t* GatewayAddressesCount - ) -{ - UNREFERENCED_PARAMETER(Datapath); - *GatewayAddresses = NULL; - *GatewayAddressesCount = 0; - return QUIC_STATUS_NOT_SUPPORTED; -} - -QUIC_STATUS -CxPlatDataPathResolveAddress( - _In_ CXPLAT_DATAPATH* Datapath, - _In_z_ const char* HostName, - _Inout_ QUIC_ADDR* Address - ) -{ - UNREFERENCED_PARAMETER(Datapath); - QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - ADDRINFO Hints = {0}; - ADDRINFO* AddrInfo = NULL; - int Result = 0; - - // - // Prepopulate hint with input family. It might be unspecified. - // - Hints.ai_family = Address->Ip.sa_family; - if (Hints.ai_family == QUIC_ADDRESS_FAMILY_INET6) { - Hints.ai_family = AF_INET6; - } - - // - // Try numeric name first. - // - Hints.ai_flags = AI_NUMERICHOST; - Result = getaddrinfo(HostName, NULL, &Hints, &AddrInfo); - if (Result == 0) { - CxPlatDataPathPopulateTargetAddress(Hints.ai_family, AddrInfo, Address); - freeaddrinfo(AddrInfo); - AddrInfo = NULL; - goto Exit; - } - - // - // Try canonical host name. - // - Hints.ai_flags = AI_CANONNAME; - Result = getaddrinfo(HostName, NULL, &Hints, &AddrInfo); - if (Result == 0) { - CxPlatDataPathPopulateTargetAddress(Hints.ai_family, AddrInfo, Address); - freeaddrinfo(AddrInfo); - AddrInfo = NULL; - goto Exit; - } - - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - (uint32_t)Result, - "Resolving hostname to IP"); - QuicTraceLogError( - DatapathResolveHostNameFailed, - "[%p] Couldn't resolve hostname '%s' to an IP address", - Datapath, - HostName); - Status = (QUIC_STATUS)Result; - -Exit: - - return Status; -} - QUIC_STATUS CxPlatSocketConfigureRss( _In_ CXPLAT_SOCKET_CONTEXT* SocketContext, @@ -1473,7 +1086,7 @@ CxPlatSocketContextSetEvents( // QUIC_STATUS -CxPlatSocketCreateUdp( +SocketCreateUdp( _In_ CXPLAT_DATAPATH* Datapath, _In_ const CXPLAT_UDP_CONFIG* Config, _Out_ CXPLAT_SOCKET** NewBinding @@ -1586,7 +1199,7 @@ CxPlatSocketCreateUdp( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatSocketCreateTcp( +SocketCreateTcp( _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_ const QUIC_ADDR* RemoteAddress, @@ -1604,7 +1217,7 @@ CxPlatSocketCreateTcp( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatSocketCreateTcpListener( +SocketCreateTcpListener( _In_ CXPLAT_DATAPATH* Datapath, _In_opt_ const QUIC_ADDR* LocalAddress, _In_opt_ void* CallbackContext, @@ -1619,8 +1232,8 @@ CxPlatSocketCreateTcpListener( } void -CxPlatSocketDelete( - _Inout_ CXPLAT_SOCKET* Socket +SocketDelete( + _In_ CXPLAT_SOCKET* Socket ) { CXPLAT_DBG_ASSERT(Socket != NULL); @@ -1642,50 +1255,6 @@ CxPlatSocketDelete( } } -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -CxPlatSocketUpdateQeo( - _In_ CXPLAT_SOCKET* Socket, - _In_reads_(OffloadCount) - const CXPLAT_QEO_CONNECTION* Offloads, - _In_ uint32_t OffloadCount - ) -{ - UNREFERENCED_PARAMETER(Socket); - UNREFERENCED_PARAMETER(Offloads); - UNREFERENCED_PARAMETER(OffloadCount); - return QUIC_STATUS_NOT_SUPPORTED; -} - -uint16_t -CxPlatSocketGetLocalMtu( - _In_ CXPLAT_SOCKET* Socket - ) -{ - CXPLAT_DBG_ASSERT(Socket != NULL); - return Socket->Mtu; -} - -void -CxPlatSocketGetLocalAddress( - _In_ CXPLAT_SOCKET* Socket, - _Out_ QUIC_ADDR* Address - ) -{ - CXPLAT_DBG_ASSERT(Socket != NULL); - *Address = Socket->LocalAddress; -} - -void -CxPlatSocketGetRemoteAddress( - _In_ CXPLAT_SOCKET* Socket, - _Out_ QUIC_ADDR* Address - ) -{ - CXPLAT_DBG_ASSERT(Socket != NULL); - *Address = Socket->RemoteAddress; -} - // // Receive Path // @@ -2041,8 +1610,8 @@ CxPlatSocketReceive( } void -CxPlatRecvDataReturn( - _In_opt_ CXPLAT_RECV_DATA* RecvDataChain +RecvDataReturn( + _In_ CXPLAT_RECV_DATA* RecvDataChain ) { CXPLAT_RECV_DATA* Datagram; @@ -2063,7 +1632,7 @@ CxPlatRecvDataReturn( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* -CxPlatSendDataAlloc( +SendDataAlloc( _In_ CXPLAT_SOCKET* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) @@ -2101,7 +1670,7 @@ CxPlatSendDataAlloc( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatSendDataFree( +SendDataFree( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -2149,7 +1718,7 @@ CxPlatSendDataFinalizeSendBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* -CxPlatSendDataAllocBuffer( +SendDataAllocBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ) @@ -2172,7 +1741,7 @@ CxPlatSendDataAllocBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatSendDataFreeBuffer( +SendDataFreeBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ) @@ -2187,7 +1756,7 @@ CxPlatSendDataFreeBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -CxPlatSendDataIsFull( +SendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -2201,7 +1770,7 @@ CxPlatSendDataSend( ); QUIC_STATUS -CxPlatSocketSend( +SocketSend( _In_ CXPLAT_SOCKET* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA* SendData @@ -2556,7 +2125,7 @@ CxPlatDataPathSocketProcessIoCompletion( } void -CxPlatDataPathProcessCqe( +DataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ) { @@ -2581,57 +2150,3 @@ CxPlatDataPathProcessCqe( } } } - - -_IRQL_requires_max_(PASSIVE_LEVEL) -void -QuicCopyRouteInfo( - _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute - ) -{ - *DstRoute = *SrcRoute; -} - -void -CxPlatResolveRouteComplete( - _In_ void* Context, - _Inout_ CXPLAT_ROUTE* Route, - _In_reads_bytes_(6) const uint8_t* PhysicalAddress, - _In_ uint8_t PathId - ) -{ - UNREFERENCED_PARAMETER(Context); - UNREFERENCED_PARAMETER(Route); - UNREFERENCED_PARAMETER(PhysicalAddress); - UNREFERENCED_PARAMETER(PathId); -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -CxPlatResolveRoute( - _In_ CXPLAT_SOCKET* Socket, - _Inout_ CXPLAT_ROUTE* Route, - _In_ uint8_t PathId, - _In_ void* Context, - _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback - ) -{ - UNREFERENCED_PARAMETER(Socket); - UNREFERENCED_PARAMETER(PathId); - UNREFERENCED_PARAMETER(Context); - UNREFERENCED_PARAMETER(Callback); - Route->State = RouteResolved; - return QUIC_STATUS_SUCCESS; -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -void -CxPlatUpdateRoute( - _Inout_ CXPLAT_ROUTE* DstRoute, - _In_ CXPLAT_ROUTE* SrcRoute - ) -{ - UNREFERENCED_PARAMETER(DstRoute); - UNREFERENCED_PARAMETER(SrcRoute); -} diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index 67d9866eba..63be011f02 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -246,7 +246,6 @@ typedef struct CXPLAT_SOCKET_RAW { SOCKET AuxSocket; BOOLEAN Wildcard; // Using a wildcard local address. Optimization // to avoid always reading LocalAddress. - BOOLEAN Connected; // Bound to a remote address uint8_t CibirIdLength; // CIBIR ID length. Value of 0 indicates CIBIR isn't used uint8_t CibirIdOffsetSrc; // CIBIR ID offset in source CID uint8_t CibirIdOffsetDst; // CIBIR ID offset in destination CID diff --git a/src/platform/datapath_raw_linux.c b/src/platform/datapath_raw_linux.c index 41f45d4bce..479006ce31 100644 --- a/src/platform/datapath_raw_linux.c +++ b/src/platform/datapath_raw_linux.c @@ -45,7 +45,7 @@ CxPlatDataPathRouteWorkerUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatDataPathRouteWorkerInitialize( - _Inout_ CXPLAT_DATAPATH* DataPath + _Inout_ CXPLAT_DATAPATH_RAW* DataPath ) { QUIC_STATUS Status; @@ -104,99 +104,24 @@ CxPlatDataPathRouteWorkerInitialize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatDataPathInitialize( +RawDataPathInitialize( _In_ uint32_t ClientRecvContextLength, - _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, - _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, _In_opt_ QUIC_EXECUTION_CONFIG* Config, - _Out_ CXPLAT_DATAPATH** NewDataPath + _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, + _Out_ CXPLAT_DATAPATH_RAW** NewDataPath ) { - QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - const size_t DatapathSize = CxPlatDpRawGetDatapathSize(Config); - BOOLEAN DpRawInitialized = FALSE; - BOOLEAN SockPoolInitialized = FALSE; - CXPLAT_FRE_ASSERT(DatapathSize > sizeof(CXPLAT_DATAPATH)); - - UNREFERENCED_PARAMETER(TcpCallbacks); - - if (NewDataPath == NULL) { - return QUIC_STATUS_INVALID_PARAMETER; - } - if (UdpCallbacks != NULL) { - if (UdpCallbacks->Receive == NULL || UdpCallbacks->Unreachable == NULL) { - return QUIC_STATUS_INVALID_PARAMETER; - } - } - - if (!CxPlatWorkersLazyStart(Config)) { - return QUIC_STATUS_OUT_OF_MEMORY; - } - - CXPLAT_DATAPATH* DataPath = CXPLAT_ALLOC_PAGED(DatapathSize, QUIC_POOL_DATAPATH); - if (DataPath == NULL) { - QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_DATAPATH", - DatapathSize); - return QUIC_STATUS_OUT_OF_MEMORY; - } - CxPlatZeroMemory(DataPath, DatapathSize); - CXPLAT_FRE_ASSERT(CxPlatRundownAcquire(&CxPlatWorkerRundown)); - - if (UdpCallbacks) { - DataPath->UdpHandlers = *UdpCallbacks; - } - - if (Config && (Config->Flags & QUIC_EXECUTION_CONFIG_FLAG_QTIP)) { - DataPath->UseTcp = TRUE; - } - - if (!CxPlatSockPoolInitialize(&DataPath->SocketPool)) { - Status = QUIC_STATUS_OUT_OF_MEMORY; - goto Error; - } - SockPoolInitialized = TRUE; - - Status = CxPlatDpRawInitialize(DataPath, ClientRecvContextLength, Config); - if (QUIC_FAILED(Status)) { - goto Error; - } - DpRawInitialized = TRUE; - - Status = CxPlatDataPathRouteWorkerInitialize(DataPath); - if (QUIC_FAILED(Status)) { - goto Error; - } - - *NewDataPath = DataPath; - DataPath = NULL; - -Error: - - if (DataPath != NULL) { -#if DEBUG - DataPath->Uninitialized = TRUE; -#endif - if (DpRawInitialized) { - CxPlatDpRawUninitialize(DataPath); - } else { - if (SockPoolInitialized) { - CxPlatSockPoolUninitialize(&DataPath->SocketPool); - } - CXPLAT_FREE(DataPath, QUIC_POOL_DATAPATH); - CxPlatRundownRelease(&CxPlatWorkerRundown); - } - } - - return Status; + UNREFERENCED_PARAMETER(ClientRecvContextLength); + UNREFERENCED_PARAMETER(Config); + UNREFERENCED_PARAMETER(ParentDataPath); + UNREFERENCED_PARAMETER(NewDataPath); + return QUIC_STATUS_NOT_SUPPORTED; } _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatDataPathUninitialize( - _In_ CXPLAT_DATAPATH* Datapath +RawDataPathUninitialize( + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { if (Datapath != NULL) { @@ -213,7 +138,7 @@ CxPlatDataPathUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDataPathUninitializeComplete( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { #if DEBUG @@ -228,8 +153,8 @@ CxPlatDataPathUninitializeComplete( _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatDataPathUpdateConfig( - _In_ CXPLAT_DATAPATH* Datapath, +RawDataPathUpdateConfig( + _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_ QUIC_EXECUTION_CONFIG* Config ) { @@ -239,8 +164,8 @@ CxPlatDataPathUpdateConfig( _IRQL_requires_max_(DISPATCH_LEVEL) uint32_t -CxPlatDataPathGetSupportedFeatures( - _In_ CXPLAT_DATAPATH* Datapath +RawDataPathGetSupportedFeatures( + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { UNREFERENCED_PARAMETER(Datapath); @@ -249,219 +174,41 @@ CxPlatDataPathGetSupportedFeatures( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -CxPlatDataPathIsPaddingPreferred( - _In_ CXPLAT_DATAPATH* Datapath, - _In_ CXPLAT_SEND_DATA* SendData +RawDataPathIsPaddingPreferred( + _In_ CXPLAT_DATAPATH* Datapath ) { UNREFERENCED_PARAMETER(Datapath); - UNREFERENCED_PARAMETER(SendData); return FALSE; } -_IRQL_requires_max_(PASSIVE_LEVEL) -_Success_(QUIC_SUCCEEDED(return)) -QUIC_STATUS -CxPlatDataPathGetLocalAddresses( - _In_ CXPLAT_DATAPATH* Datapath, - _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) - CXPLAT_ADAPTER_ADDRESS** Addresses, - _Out_ uint32_t* AddressesCount - ) -{ - UNREFERENCED_PARAMETER(Datapath); - UNREFERENCED_PARAMETER(Addresses); - UNREFERENCED_PARAMETER(AddressesCount); - return QUIC_STATUS_NOT_SUPPORTED; -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -_Success_(QUIC_SUCCEEDED(return)) -QUIC_STATUS -CxPlatDataPathGetGatewayAddresses( - _In_ CXPLAT_DATAPATH* Datapath, - _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) - QUIC_ADDR** GatewayAddresses, - _Out_ uint32_t* GatewayAddressesCount - ) -{ - UNREFERENCED_PARAMETER(Datapath); - UNREFERENCED_PARAMETER(GatewayAddresses); - UNREFERENCED_PARAMETER(GatewayAddressesCount); - return QUIC_STATUS_NOT_SUPPORTED; -} - -void -CxPlatDataPathPopulateTargetAddress( - _In_ QUIC_ADDRESS_FAMILY Family, - _In_ ADDRINFO* AddrInfo, - _Out_ QUIC_ADDR* Address - ) -{ - UNREFERENCED_PARAMETER(Family); - UNREFERENCED_PARAMETER(AddrInfo); - UNREFERENCED_PARAMETER(Address); -} - -// ->CxPlat -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -CxPlatDataPathResolveAddress( - _In_ CXPLAT_DATAPATH* Datapath, - _In_z_ const char* HostName, - _Inout_ QUIC_ADDR* Address - ) -{ - UNREFERENCED_PARAMETER(Datapath); - UNREFERENCED_PARAMETER(HostName); - UNREFERENCED_PARAMETER(Address); - QUIC_STATUS Status = QUIC_STATUS_NOT_SUPPORTED; - return Status; -} - _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatSocketCreateUdp( - _In_ CXPLAT_DATAPATH* Datapath, +RawSocketCreateUdp( + _In_ CXPLAT_DATAPATH_RAW* Datapath, _In_ const CXPLAT_UDP_CONFIG* Config, - _Out_ CXPLAT_SOCKET** NewSocket - ) -{ - QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - - *NewSocket = CXPLAT_ALLOC_PAGED(sizeof(CXPLAT_SOCKET), QUIC_POOL_SOCKET); - if (*NewSocket == NULL) { - QuicTraceEvent( - AllocFailure, - "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_SOCKET", - sizeof(CXPLAT_SOCKET)); - Status = QUIC_STATUS_OUT_OF_MEMORY; - goto Error; - } - - QuicTraceEvent( - DatapathCreated, - "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", - *NewSocket, - CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress), - CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress)); - - CxPlatZeroMemory(*NewSocket, sizeof(CXPLAT_SOCKET)); - CxPlatRundownInitialize(&(*NewSocket)->Rundown); - (*NewSocket)->Datapath = Datapath; - (*NewSocket)->CallbackContext = Config->CallbackContext; - (*NewSocket)->CibirIdLength = Config->CibirIdLength; - (*NewSocket)->CibirIdOffsetSrc = Config->CibirIdOffsetSrc; - (*NewSocket)->CibirIdOffsetDst = Config->CibirIdOffsetDst; - (*NewSocket)->UseTcp = Datapath->UseTcp; - if (Config->CibirIdLength) { - memcpy((*NewSocket)->CibirId, Config->CibirId, Config->CibirIdLength); - } - - if (Config->RemoteAddress) { - CXPLAT_FRE_ASSERT(!QuicAddrIsWildCard(Config->RemoteAddress)); // No wildcard remote addresses allowed. - (*NewSocket)->Connected = TRUE; - (*NewSocket)->RemoteAddress = *Config->RemoteAddress; - } - - if (Config->LocalAddress) { - (*NewSocket)->LocalAddress = *Config->LocalAddress; - if (QuicAddrIsWildCard(Config->LocalAddress)) { - if (!(*NewSocket)->Connected) { - (*NewSocket)->Wildcard = TRUE; - } - } else { - CXPLAT_FRE_ASSERT((*NewSocket)->Connected); // Assumes only connected sockets fully specify local address - } - } else { - QuicAddrSetFamily(&(*NewSocket)->LocalAddress, QUIC_ADDRESS_FAMILY_INET6); - if (!(*NewSocket)->Connected) { - (*NewSocket)->Wildcard = TRUE; - } - } - - CXPLAT_FRE_ASSERT((*NewSocket)->Wildcard ^ (*NewSocket)->Connected); // Assumes either a pure wildcard listener or a - // connected socket; not both. - - Status = CxPlatTryAddSocket(&Datapath->SocketPool, *NewSocket); - if (QUIC_FAILED(Status)) { - goto Error; - } - - CxPlatDpRawPlumbRulesOnSocket(*NewSocket, TRUE); - -Error: - - if (QUIC_FAILED(Status)) { - if (*NewSocket != NULL) { - CxPlatRundownUninitialize(&(*NewSocket)->Rundown); - CXPLAT_FREE(*NewSocket, QUIC_POOL_SOCKET); - *NewSocket = NULL; - } - } - - return Status; -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -CxPlatSocketCreateTcp( - _In_ CXPLAT_DATAPATH* Datapath, - _In_opt_ const QUIC_ADDR* LocalAddress, - _In_ const QUIC_ADDR* RemoteAddress, - _In_opt_ void* CallbackContext, - _Out_ CXPLAT_SOCKET** Socket - ) -{ - UNREFERENCED_PARAMETER(Datapath); - UNREFERENCED_PARAMETER(LocalAddress); - UNREFERENCED_PARAMETER(RemoteAddress); - UNREFERENCED_PARAMETER(CallbackContext); - UNREFERENCED_PARAMETER(Socket); - return QUIC_STATUS_NOT_SUPPORTED; -} - -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -CxPlatSocketCreateTcpListener( - _In_ CXPLAT_DATAPATH* Datapath, - _In_opt_ const QUIC_ADDR* LocalAddress, - _In_opt_ void* RecvCallbackContext, - _Out_ CXPLAT_SOCKET** NewSocket + _Inout_ CXPLAT_SOCKET_RAW* NewSocket ) { UNREFERENCED_PARAMETER(Datapath); - UNREFERENCED_PARAMETER(LocalAddress); - UNREFERENCED_PARAMETER(RecvCallbackContext); + UNREFERENCED_PARAMETER(Config); UNREFERENCED_PARAMETER(NewSocket); return QUIC_STATUS_NOT_SUPPORTED; } _IRQL_requires_max_(PASSIVE_LEVEL) void -CxPlatSocketDelete( - _In_ CXPLAT_SOCKET* Socket +RawSocketDelete( + _In_ CXPLAT_SOCKET_RAW* Socket ) { - CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); - CxPlatRemoveSocket(&Socket->Datapath->SocketPool, Socket); - CxPlatRundownReleaseAndWait(&Socket->Rundown); - if (Socket->PausedTcpSend) { - CxPlatDpRawTxFree(Socket->PausedTcpSend); - } - - if (Socket->CachedRstSend) { - CxPlatDpRawTxEnqueue(Socket->CachedRstSend); - } - - CXPLAT_FREE(Socket, QUIC_POOL_SOCKET); + UNREFERENCED_PARAMETER(Socket); } _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatSocketUpdateQeo( - _In_ CXPLAT_SOCKET* Socket, +RawSocketUpdateQeo( + _In_ CXPLAT_SOCKET_RAW* Socket, _In_reads_(OffloadCount) const CXPLAT_QEO_CONNECTION* Offloads, _In_ uint32_t OffloadCount @@ -475,8 +222,8 @@ CxPlatSocketUpdateQeo( _IRQL_requires_max_(DISPATCH_LEVEL) uint16_t -CxPlatSocketGetLocalMtu( - _In_ CXPLAT_SOCKET* Socket +RawSocketGetLocalMtu( + _In_ CXPLAT_SOCKET_RAW* Socket ) { if (Socket->UseTcp) { @@ -486,37 +233,17 @@ CxPlatSocketGetLocalMtu( } } -_IRQL_requires_max_(DISPATCH_LEVEL) -void -CxPlatSocketGetLocalAddress( - _In_ CXPLAT_SOCKET* Socket, - _Out_ QUIC_ADDR* Address - ) -{ - *Address = Socket->LocalAddress; -} - -_IRQL_requires_max_(DISPATCH_LEVEL) -void -CxPlatSocketGetRemoteAddress( - _In_ CXPLAT_SOCKET* Socket, - _Out_ QUIC_ADDR* Address - ) -{ - *Address = Socket->RemoteAddress; -} - _IRQL_requires_max_(DISPATCH_LEVEL) void CxPlatDpRawRxEthernet( - _In_ const CXPLAT_DATAPATH* Datapath, + _In_ const CXPLAT_DATAPATH_RAW* Datapath, _In_reads_(PacketCount) CXPLAT_RECV_DATA** Packets, _In_ uint16_t PacketCount ) { for (uint16_t i = 0; i < PacketCount; i++) { - CXPLAT_SOCKET* Socket = NULL; + CXPLAT_SOCKET_RAW* Socket = NULL; CXPLAT_RECV_DATA* PacketChain = Packets[i]; CXPLAT_DBG_ASSERT(PacketChain->Next == NULL); @@ -554,7 +281,7 @@ CxPlatDpRawRxEthernet( CXPLAT_DBG_ASSERT(Packets[i+1]->Next == NULL); i++; } - Datapath->UdpHandlers.Receive(Socket, Socket->CallbackContext, (CXPLAT_RECV_DATA*)PacketChain); + Datapath->ParentDataPath->UdpHandlers.Receive(CxPlatRawToSocket(Socket), Socket->ClientContext, (CXPLAT_RECV_DATA*)PacketChain); } else if (PacketChain->Reserved == L4_TYPE_TCP_SYN || PacketChain->Reserved == L4_TYPE_TCP_SYNACK) { CxPlatDpRawSocketAckSyn(Socket, PacketChain); CxPlatDpRawRxFree(PacketChain); @@ -574,7 +301,7 @@ CxPlatDpRawRxEthernet( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatRecvDataReturn( +RawRecvDataReturn( _In_opt_ CXPLAT_RECV_DATA* RecvDataChain ) { @@ -584,8 +311,8 @@ CxPlatRecvDataReturn( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) CXPLAT_SEND_DATA* -CxPlatSendDataAlloc( - _In_ CXPLAT_SOCKET* Socket, +RawSendDataAlloc( + _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) { @@ -595,7 +322,7 @@ CxPlatSendDataAlloc( _IRQL_requires_max_(DISPATCH_LEVEL) _Success_(return != NULL) QUIC_BUFFER* -CxPlatSendDataAllocBuffer( +RawSendDataAllocBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ uint16_t MaxBufferLength ) @@ -606,7 +333,7 @@ CxPlatSendDataAllocBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatSendDataFree( +RawSendDataFree( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -615,7 +342,7 @@ CxPlatSendDataFree( _IRQL_requires_max_(DISPATCH_LEVEL) void -CxPlatSendDataFreeBuffer( +RawSendDataFreeBuffer( _In_ CXPLAT_SEND_DATA* SendData, _In_ QUIC_BUFFER* Buffer ) @@ -627,7 +354,7 @@ CxPlatSendDataFreeBuffer( _IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -CxPlatSendDataIsFull( +RawSendDataIsFull( _In_ CXPLAT_SEND_DATA* SendData ) { @@ -639,8 +366,8 @@ CxPlatSendDataIsFull( _IRQL_requires_max_(DISPATCH_LEVEL) QUIC_STATUS -CxPlatSocketSend( - _In_ CXPLAT_SOCKET* Socket, +RawSocketSend( + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ const CXPLAT_ROUTE* Route, _In_ CXPLAT_SEND_DATA* SendData ) diff --git a/src/platform/datapath_raw_socket_linux.c b/src/platform/datapath_raw_socket_linux.c index 54b4f4f2e1..0cbfaa76db 100644 --- a/src/platform/datapath_raw_socket_linux.c +++ b/src/platform/datapath_raw_socket_linux.c @@ -45,7 +45,7 @@ CxPlatSockPoolUninitialize( void CxPlatRemoveSocket( _In_ CXPLAT_SOCKET_POOL* Pool, - _In_ CXPLAT_SOCKET* Socket + _In_ CXPLAT_SOCKET_RAW* Socket ) { UNREFERENCED_PARAMETER(Pool); @@ -54,8 +54,8 @@ CxPlatRemoveSocket( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS -CxPlatResolveRoute( - _In_ CXPLAT_SOCKET* Socket, +RawResolveRoute( + _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_ROUTE* Route, _In_ uint8_t PathId, _In_ void* Context, @@ -74,7 +74,7 @@ CxPlatResolveRoute( QUIC_STATUS CxPlatTryAddSocket( _In_ CXPLAT_SOCKET_POOL* Pool, - _In_ CXPLAT_SOCKET* Socket + _In_ CXPLAT_SOCKET_RAW* Socket ) { UNREFERENCED_PARAMETER(Pool); diff --git a/src/platform/datapath_raw_xdp_linux.c b/src/platform/datapath_raw_xdp_linux.c index a6f3abb0fc..490e0e3f72 100644 --- a/src/platform/datapath_raw_xdp_linux.c +++ b/src/platform/datapath_raw_xdp_linux.c @@ -19,7 +19,7 @@ #endif typedef struct XDP_DATAPATH { - CXPLAT_DATAPATH; + CXPLAT_DATAPATH_RAW; __attribute__((aligned(64))) // // Currently, all XDP interfaces share the same config. @@ -177,7 +177,7 @@ CxPlatDpRawGetDatapathSize( _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatDpRawInitialize( - _Inout_ CXPLAT_DATAPATH* Datapath, + _Inout_ CXPLAT_DATAPATH_RAW* Datapath, _In_ uint32_t ClientRecvContextLength, _In_opt_ const QUIC_EXECUTION_CONFIG* Config ) @@ -200,7 +200,7 @@ CxPlatDpRawRelease( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDpRawUninitialize( - _In_ CXPLAT_DATAPATH* Datapath + _In_ CXPLAT_DATAPATH_RAW* Datapath ) { UNREFERENCED_PARAMETER(Datapath); @@ -209,7 +209,7 @@ CxPlatDpRawUninitialize( _IRQL_requires_max_(PASSIVE_LEVEL) void CxPlatDpRawPlumbRulesOnSocket( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _In_ BOOLEAN IsCreated ) { @@ -249,7 +249,7 @@ CxPlatDpRawRxFree( _IRQL_requires_max_(DISPATCH_LEVEL) CXPLAT_SEND_DATA* CxPlatDpRawTxAlloc( - _In_ CXPLAT_SOCKET* Socket, + _In_ CXPLAT_SOCKET_RAW* Socket, _Inout_ CXPLAT_SEND_CONFIG* Config ) { @@ -289,7 +289,7 @@ CxPlatXdpExecute( } void -CxPlatDataPathProcessCqe( +RawDataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ) { diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 8a41f762b7..757b26111e 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -157,39 +157,7 @@ typedef enum CXPLAT_SOCKET_TYPE { CXPLAT_SOCKET_TCP_SERVER = 3 } CXPLAT_SOCKET_TYPE; -// -// Type of IO. -// -typedef enum DATAPATH_IO_TYPE { - DATAPATH_IO_SIGNATURE = 'WINU', - DATAPATH_IO_RECV = DATAPATH_IO_SIGNATURE + 1, - DATAPATH_IO_SEND = DATAPATH_IO_SIGNATURE + 2, - DATAPATH_IO_QUEUE_SEND = DATAPATH_IO_SIGNATURE + 3, - DATAPATH_IO_ACCEPTEX = DATAPATH_IO_SIGNATURE + 4, - DATAPATH_IO_CONNECTEX = DATAPATH_IO_SIGNATURE + 5, - DATAPATH_IO_RIO_NOTIFY = DATAPATH_IO_SIGNATURE + 6, - DATAPATH_IO_RIO_RECV = DATAPATH_IO_SIGNATURE + 7, - DATAPATH_IO_RIO_SEND = DATAPATH_IO_SIGNATURE + 8, - DATAPATH_IO_RECV_FAILURE = DATAPATH_IO_SIGNATURE + 9, - DATAPATH_IO_MAX -} DATAPATH_IO_TYPE; - -// -// Type of IO for XDP. -// -typedef enum DATAPATH_XDP_IO_TYPE { - DATAPATH_XDP_IO_SIGNATURE = 'XDPD', - DATAPATH_XDP_IO_RECV = DATAPATH_XDP_IO_SIGNATURE + 1, - DATAPATH_XDP_IO_SEND = DATAPATH_XDP_IO_SIGNATURE + 2 -} DATAPATH_XDP_IO_TYPE; - -// -// IO header for SQE->CQE based completions. -// -typedef struct DATAPATH_IO_SQE { - DATAPATH_IO_TYPE IoType; - DATAPATH_SQE DatapathSqe; -} DATAPATH_IO_SQE; +typedef struct DATAPATH_IO_SQE DATAPATH_IO_SQE; // // Represents a single IO completion port and thread for processing work that is @@ -470,6 +438,11 @@ typedef struct CXPLAT_SOCKET { // uint16_t Mtu; + // + // Indicates the binding connected to a remote IP address. + // + BOOLEAN Connected : 1; + // // Socket type. // @@ -518,142 +491,573 @@ typedef struct CXPLAT_SOCKET { } CXPLAT_SOCKET; -typedef struct CXPLAT_SOCKET_RAW CXPLAT_SOCKET_RAW; +#elif defined(CX_PLATFORM_LINUX) || defined(CX_PLATFORM_DARWIN) -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -SocketCreateUdp( - _In_ CXPLAT_DATAPATH* DataPath, - _In_ const CXPLAT_UDP_CONFIG* Config, - _Out_ CXPLAT_SOCKET** NewSocket - ); +typedef struct CX_PLATFORM { -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -SocketCreateTcp( - _In_ CXPLAT_DATAPATH* Datapath, - _In_opt_ const QUIC_ADDR* LocalAddress, - _In_ const QUIC_ADDR* RemoteAddress, - _In_opt_ void* CallbackContext, - _Out_ CXPLAT_SOCKET** Socket - ); + void* Reserved; // Nothing right now. -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -SocketCreateTcpListener( - _In_ CXPLAT_DATAPATH* Datapath, - _In_opt_ const QUIC_ADDR* LocalAddress, - _In_opt_ void* RecvCallbackContext, - _Out_ CXPLAT_SOCKET** NewSocket - ); +#ifdef DEBUG + // + // 1/Denominator of allocations to fail. + // Negative is Nth allocation to fail. + // + int32_t AllocFailDenominator; -_IRQL_requires_max_(PASSIVE_LEVEL) -void -SocketDelete( - _In_ CXPLAT_SOCKET* Socket - ); + // + // Count of allocations. + // + long AllocCounter; +#endif -_IRQL_requires_max_(PASSIVE_LEVEL) -QUIC_STATUS -DataPathInitialize( - _In_ uint32_t ClientRecvDataLength, - _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, - _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, - _In_opt_ QUIC_EXECUTION_CONFIG* Config, - _Out_ CXPLAT_DATAPATH** NewDatapath - ); +} CX_PLATFORM; -_IRQL_requires_max_(PASSIVE_LEVEL) +#else + +#error "Unsupported Platform" + +#endif + +#pragma warning(disable:4204) // nonstandard extension used: non-constant aggregate initializer +#pragma warning(disable:4200) // nonstandard extension used: zero-sized array in struct/union + +// +// Global Platform variables/state. +// +extern CX_PLATFORM CxPlatform; + +// +// PCP Receive Callback +// +CXPLAT_DATAPATH_RECEIVE_CALLBACK CxPlatPcpRecvCallback; + +#if _WIN32 // Some Windows Helpers + +// +// Converts IPv6 or IPV4 address to a (possibly mapped) IPv6. +// +inline void -DataPathUninitialize( - _In_ CXPLAT_DATAPATH* Datapath - ); +CxPlatConvertToMappedV6( + _In_ const QUIC_ADDR* InAddr, + _Out_ QUIC_ADDR* OutAddr + ) +{ + if (InAddr->si_family == QUIC_ADDRESS_FAMILY_INET) { + SCOPE_ID unspecified_scope = {0}; + IN6ADDR_SETV4MAPPED( + &OutAddr->Ipv6, + &InAddr->Ipv4.sin_addr, + unspecified_scope, + InAddr->Ipv4.sin_port); + } else { + *OutAddr = *InAddr; + } +} -_IRQL_requires_max_(PASSIVE_LEVEL) +// +// Converts (possibly mapped) IPv6 address to a IPv6 or IPV4 address. Does +// support InAdrr == OutAddr. +// +#pragma warning(push) +#pragma warning(disable: 6101) // Intentially don't overwrite output if unable to convert +inline void -DataPathUpdateConfig( - _In_ CXPLAT_DATAPATH* Datapath, - _In_ QUIC_EXECUTION_CONFIG* Config - ); +CxPlatConvertFromMappedV6( + _In_ const QUIC_ADDR* InAddr, + _Out_ QUIC_ADDR* OutAddr + ) +{ + CXPLAT_DBG_ASSERT(InAddr->si_family == QUIC_ADDRESS_FAMILY_INET6); + if (IN6_IS_ADDR_V4MAPPED(&InAddr->Ipv6.sin6_addr)) { + OutAddr->si_family = QUIC_ADDRESS_FAMILY_INET; + OutAddr->Ipv4.sin_port = InAddr->Ipv6.sin6_port; + OutAddr->Ipv4.sin_addr = + *(IN_ADDR UNALIGNED *) + IN6_GET_ADDR_V4MAPPED(&InAddr->Ipv6.sin6_addr); + } else if (OutAddr != InAddr) { + *OutAddr = *InAddr; + } +} +#pragma warning(pop) -_IRQL_requires_max_(DISPATCH_LEVEL) -uint32_t -DataPathGetSupportedFeatures( - _In_ CXPLAT_DATAPATH* Datapath - ); +#endif -_IRQL_requires_max_(DISPATCH_LEVEL) -BOOLEAN -DataPathIsPaddingPreferred( - _In_ CXPLAT_DATAPATH* Datapath +// +// Crypt Initialization +// + +QUIC_STATUS +CxPlatCryptInitialize( + void ); -_IRQL_requires_max_(DISPATCH_LEVEL) void -RecvDataReturn( - _In_ CXPLAT_RECV_DATA* RecvDataChain +CxPlatCryptUninitialize( + void ); -_IRQL_requires_max_(DISPATCH_LEVEL) -_Success_(return != NULL) -CXPLAT_SEND_DATA* -SendDataAlloc( - _In_ CXPLAT_SOCKET* Socket, - _Inout_ CXPLAT_SEND_CONFIG* Config - ); +// +// Platform Worker APIs +// -_IRQL_requires_max_(DISPATCH_LEVEL) void -SendDataFree( - _In_ CXPLAT_SEND_DATA* SendData - ); - -_IRQL_requires_max_(DISPATCH_LEVEL) -_Success_(return != NULL) -QUIC_BUFFER* -SendDataAllocBuffer( - _In_ CXPLAT_SEND_DATA* SendData, - _In_ uint16_t MaxBufferLength +CxPlatWorkersInit( + void ); -_IRQL_requires_max_(DISPATCH_LEVEL) void -SendDataFreeBuffer( - _In_ CXPLAT_SEND_DATA* SendData, - _In_ QUIC_BUFFER* Buffer +CxPlatWorkersUninit( + void ); -_IRQL_requires_max_(DISPATCH_LEVEL) BOOLEAN -SendDataIsFull( - _In_ CXPLAT_SEND_DATA* SendData +CxPlatWorkersLazyStart( + _In_opt_ QUIC_EXECUTION_CONFIG* Config ); -_IRQL_requires_max_(DISPATCH_LEVEL) -QUIC_STATUS -SocketSend( - _In_ CXPLAT_SOCKET* Socket, - _In_ const CXPLAT_ROUTE* Route, - _In_ CXPLAT_SEND_DATA* SendData +CXPLAT_EVENTQ* +CxPlatWorkerGetEventQ( + _In_ uint16_t Index // Into the config processor array ); void -DataPathProcessCqe( +CxPlatDataPathProcessCqe( _In_ CXPLAT_CQE* Cqe ); -CXPLAT_SOCKET* -CxPlatRawToSocket( - _In_ CXPLAT_SOCKET_RAW* Socket +BOOLEAN // Returns FALSE no work was done. +CxPlatDataPathPoll( + _In_ void* Context, + _Out_ BOOLEAN* RemoveFromPolling ); -CXPLAT_SOCKET_RAW* -CxPlatSocketToRaw( - _In_ CXPLAT_SOCKET* Socketh +// +// Queries the raw datapath stack for the total size needed to allocate the +// datapath structure. +// +_IRQL_requires_max_(PASSIVE_LEVEL) +size_t +CxPlatDpRawGetDatapathSize( + _In_opt_ const QUIC_EXECUTION_CONFIG* Config ); -uint32_t -CxPlatGetRawSocketSize(void); +#define CXPLAT_CQE_TYPE_WORKER_WAKE CXPLAT_CQE_TYPE_QUIC_BASE + 1 +#define CXPLAT_CQE_TYPE_WORKER_UPDATE_POLL CXPLAT_CQE_TYPE_QUIC_BASE + 2 +#define CXPLAT_CQE_TYPE_SOCKET_SHUTDOWN CXPLAT_CQE_TYPE_QUIC_BASE + 3 +#define CXPLAT_CQE_TYPE_SOCKET_IO CXPLAT_CQE_TYPE_QUIC_BASE + 4 +#define CXPLAT_CQE_TYPE_SOCKET_FLUSH_TX CXPLAT_CQE_TYPE_QUIC_BASE + 5 + +extern CXPLAT_RUNDOWN_REF CxPlatWorkerRundown; + +#if defined(CX_PLATFORM_LINUX) + +typedef struct CXPLAT_DATAPATH_PARTITION CXPLAT_DATAPATH_PARTITION; + +// +// Socket context. +// +typedef struct QUIC_CACHEALIGN CXPLAT_SOCKET_CONTEXT { + + // + // The datapath binding this socket context belongs to. + // + CXPLAT_SOCKET* Binding; + + // + // The datapath proc context this socket belongs to. + // + CXPLAT_DATAPATH_PARTITION* DatapathPartition; + + // + // The socket FD used by this socket context. + // + int SocketFd; + + // + // The submission queue event for shutdown. + // + DATAPATH_SQE ShutdownSqe; + + // + // The submission queue event for IO. + // + DATAPATH_SQE IoSqe; + + // + // The submission queue event for flushing the send queue. + // + DATAPATH_SQE FlushTxSqe; + + // + // The head of list containg all pending sends on this socket. + // + CXPLAT_LIST_ENTRY TxQueue; + + // + // Lock around the PendingSendData list. + // + CXPLAT_LOCK TxQueueLock; + + // + // Rundown for synchronizing clean up with upcalls. + // + CXPLAT_RUNDOWN_REF UpcallRundown; + + // + // Inidicates the SQEs have been initialized. + // + BOOLEAN SqeInitialized : 1; + + // + // Inidicates if the socket has started IO processing. + // + BOOLEAN IoStarted : 1; + +#if DEBUG + uint8_t Uninitialized : 1; + uint8_t Freed : 1; +#endif + +} CXPLAT_SOCKET_CONTEXT; + +// +// Datapath binding. +// +typedef struct CXPLAT_SOCKET { + CXPLAT_SOCKET_COMMON; + + // + // A pointer to datapath object. + // + CXPLAT_DATAPATH* Datapath; + + // + // The client context for this binding. + // + void *ClientContext; + + // + // Synchronization mechanism for cleanup. + // + CXPLAT_REF_COUNT RefCount; + + // + // The MTU for this binding. + // + uint16_t Mtu; + + // + // Indicates the binding connected to a remote IP address. + // + BOOLEAN Connected : 1; + + // + // Flag indicates the socket has a default remote destination. + // + BOOLEAN HasFixedRemoteAddress : 1; + + // + // Flag indicates the binding is being used for PCP. + // + BOOLEAN PcpBinding : 1; + +#if DEBUG + uint8_t Uninitialized : 1; + uint8_t Freed : 1; +#endif + + uint8_t UseTcp : 1; // Quic over TCP + + // + // Set of socket contexts one per proc. + // + CXPLAT_SOCKET_CONTEXT SocketContexts[]; + +} CXPLAT_SOCKET; + +// +// A per processor datapath context. +// +typedef struct QUIC_CACHEALIGN CXPLAT_DATAPATH_PARTITION { + + // + // A pointer to the datapath. + // + CXPLAT_DATAPATH* Datapath; + + // + // The event queue for this proc context. + // + CXPLAT_EVENTQ* EventQ; + + // + // Synchronization mechanism for cleanup. + // + CXPLAT_REF_COUNT RefCount; + + // + // The ideal processor of the context. + // + uint16_t PartitionIndex; + +#if DEBUG + uint8_t Uninitialized : 1; +#endif + + // + // Pool of receive packet contexts and buffers to be shared by all sockets + // on this core. + // + CXPLAT_POOL RecvBlockPool; + + // + // Pool of send packet contexts and buffers to be shared by all sockets + // on this core. + // + CXPLAT_POOL SendBlockPool; + +} CXPLAT_DATAPATH_PARTITION; + +// +// Represents a datapath object. +// + +typedef struct CXPLAT_DATAPATH { + CXPLAT_DATAPATH_COMMON; + + // + // Synchronization mechanism for cleanup. + // + CXPLAT_REF_COUNT RefCount; + + // + // Set of supported features. + // + uint32_t Features; + + // + // The proc count to create per proc datapath state. + // + uint32_t PartitionCount; + + // + // The length of the CXPLAT_SEND_DATA. Calculated based on the support level + // for GSO. No GSO support requires a larger send data to hold the extra + // iovec structs. + // + uint32_t SendDataSize; + + // + // When not using GSO, we preallocate multiple iovec structs to use with + // sendmmsg (to simulate GSO). + // + uint32_t SendIoVecCount; + + // + // The length of the CXPLAT_RECV_DATA and client data part of the + // DATAPATH_RX_IO_BLOCK. + // + uint32_t RecvBlockStride; + + // + // The offset of the raw buffer in the DATAPATH_RX_IO_BLOCK. + // + uint32_t RecvBlockBufferOffset; + + // + // The total length of the DATAPATH_RX_IO_BLOCK. Calculated based on the + // support level for GRO. No GRO only uses a single CXPLAT_RECV_DATA and + // client data, while GRO allows for multiple. + // + uint32_t RecvBlockSize; + +#if DEBUG + uint8_t Uninitialized : 1; + uint8_t Freed : 1; +#endif + + // + // The per proc datapath contexts. + // + CXPLAT_DATAPATH_PARTITION Partitions[]; + +} CXPLAT_DATAPATH; + +#endif // CX_PLATFORM_LINUX + +#if defined(CX_PLATFORM_LINUX) || _WIN32 + +typedef struct CXPLAT_SOCKET_RAW CXPLAT_SOCKET_RAW; + +// +// Type of IO. +// +typedef enum DATAPATH_IO_TYPE { + DATAPATH_IO_SIGNATURE = 'WINU', + DATAPATH_IO_RECV = DATAPATH_IO_SIGNATURE + 1, + DATAPATH_IO_SEND = DATAPATH_IO_SIGNATURE + 2, + DATAPATH_IO_QUEUE_SEND = DATAPATH_IO_SIGNATURE + 3, + DATAPATH_IO_ACCEPTEX = DATAPATH_IO_SIGNATURE + 4, + DATAPATH_IO_CONNECTEX = DATAPATH_IO_SIGNATURE + 5, + DATAPATH_IO_RIO_NOTIFY = DATAPATH_IO_SIGNATURE + 6, + DATAPATH_IO_RIO_RECV = DATAPATH_IO_SIGNATURE + 7, + DATAPATH_IO_RIO_SEND = DATAPATH_IO_SIGNATURE + 8, + DATAPATH_IO_RECV_FAILURE = DATAPATH_IO_SIGNATURE + 9, + DATAPATH_IO_MAX +} DATAPATH_IO_TYPE; + +// +// Type of IO for XDP. +// +typedef enum DATAPATH_XDP_IO_TYPE { + DATAPATH_XDP_IO_SIGNATURE = 'XDPD', + DATAPATH_XDP_IO_RECV = DATAPATH_XDP_IO_SIGNATURE + 1, + DATAPATH_XDP_IO_SEND = DATAPATH_XDP_IO_SIGNATURE + 2 +} DATAPATH_XDP_IO_TYPE; + +// +// IO header for SQE->CQE based completions. +// +typedef struct DATAPATH_IO_SQE { + DATAPATH_IO_TYPE IoType; + DATAPATH_SQE DatapathSqe; +} DATAPATH_IO_SQE; + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +SocketCreateUdp( + _In_ CXPLAT_DATAPATH* DataPath, + _In_ const CXPLAT_UDP_CONFIG* Config, + _Out_ CXPLAT_SOCKET** NewSocket + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +SocketCreateTcp( + _In_ CXPLAT_DATAPATH* Datapath, + _In_opt_ const QUIC_ADDR* LocalAddress, + _In_ const QUIC_ADDR* RemoteAddress, + _In_opt_ void* CallbackContext, + _Out_ CXPLAT_SOCKET** Socket + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +SocketCreateTcpListener( + _In_ CXPLAT_DATAPATH* Datapath, + _In_opt_ const QUIC_ADDR* LocalAddress, + _In_opt_ void* RecvCallbackContext, + _Out_ CXPLAT_SOCKET** NewSocket + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +SocketDelete( + _In_ CXPLAT_SOCKET* Socket + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +DataPathInitialize( + _In_ uint32_t ClientRecvDataLength, + _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, + _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, + _In_opt_ QUIC_EXECUTION_CONFIG* Config, + _Out_ CXPLAT_DATAPATH** NewDatapath + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +DataPathUninitialize( + _In_ CXPLAT_DATAPATH* Datapath + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +DataPathUpdateConfig( + _In_ CXPLAT_DATAPATH* Datapath, + _In_ QUIC_EXECUTION_CONFIG* Config + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +uint32_t +DataPathGetSupportedFeatures( + _In_ CXPLAT_DATAPATH* Datapath + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +BOOLEAN +DataPathIsPaddingPreferred( + _In_ CXPLAT_DATAPATH* Datapath + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +RecvDataReturn( + _In_ CXPLAT_RECV_DATA* RecvDataChain + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +CXPLAT_SEND_DATA* +SendDataAlloc( + _In_ CXPLAT_SOCKET* Socket, + _Inout_ CXPLAT_SEND_CONFIG* Config + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +SendDataFree( + _In_ CXPLAT_SEND_DATA* SendData + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +QUIC_BUFFER* +SendDataAllocBuffer( + _In_ CXPLAT_SEND_DATA* SendData, + _In_ uint16_t MaxBufferLength + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +SendDataFreeBuffer( + _In_ CXPLAT_SEND_DATA* SendData, + _In_ QUIC_BUFFER* Buffer + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +BOOLEAN +SendDataIsFull( + _In_ CXPLAT_SEND_DATA* SendData + ); + +_IRQL_requires_max_(DISPATCH_LEVEL) +QUIC_STATUS +SocketSend( + _In_ CXPLAT_SOCKET* Socket, + _In_ const CXPLAT_ROUTE* Route, + _In_ CXPLAT_SEND_DATA* SendData + ); + +void +DataPathProcessCqe( + _In_ CXPLAT_CQE* Cqe + ); + +CXPLAT_SOCKET* +CxPlatRawToSocket( + _In_ CXPLAT_SOCKET_RAW* Socket + ); + +CXPLAT_SOCKET_RAW* +CxPlatSocketToRaw( + _In_ CXPLAT_SOCKET* Socketh + ); + +uint32_t +CxPlatGetRawSocketSize(void); _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS @@ -713,7 +1117,7 @@ RawSocketUpdateQeo( ); _IRQL_requires_max_(DISPATCH_LEVEL) -UINT16 +uint16_t RawSocketGetLocalMtu( _In_ CXPLAT_SOCKET_RAW* Socket ); @@ -797,161 +1201,4 @@ RawUpdateRoute( _In_ CXPLAT_ROUTE* SrcRoute ); -#elif defined(CX_PLATFORM_LINUX) || defined(CX_PLATFORM_DARWIN) - -typedef struct CX_PLATFORM { - - void* Reserved; // Nothing right now. - -#ifdef DEBUG - // - // 1/Denominator of allocations to fail. - // Negative is Nth allocation to fail. - // - int32_t AllocFailDenominator; - - // - // Count of allocations. - // - long AllocCounter; -#endif - -} CX_PLATFORM; - -#else - -#error "Unsupported Platform" - -#endif - -#pragma warning(disable:4204) // nonstandard extension used: non-constant aggregate initializer -#pragma warning(disable:4200) // nonstandard extension used: zero-sized array in struct/union - -// -// Global Platform variables/state. -// -extern CX_PLATFORM CxPlatform; - -// -// PCP Receive Callback -// -CXPLAT_DATAPATH_RECEIVE_CALLBACK CxPlatPcpRecvCallback; - -#if _WIN32 // Some Windows Helpers - -// -// Converts IPv6 or IPV4 address to a (possibly mapped) IPv6. -// -inline -void -CxPlatConvertToMappedV6( - _In_ const QUIC_ADDR* InAddr, - _Out_ QUIC_ADDR* OutAddr - ) -{ - if (InAddr->si_family == QUIC_ADDRESS_FAMILY_INET) { - SCOPE_ID unspecified_scope = {0}; - IN6ADDR_SETV4MAPPED( - &OutAddr->Ipv6, - &InAddr->Ipv4.sin_addr, - unspecified_scope, - InAddr->Ipv4.sin_port); - } else { - *OutAddr = *InAddr; - } -} - -// -// Converts (possibly mapped) IPv6 address to a IPv6 or IPV4 address. Does -// support InAdrr == OutAddr. -// -#pragma warning(push) -#pragma warning(disable: 6101) // Intentially don't overwrite output if unable to convert -inline -void -CxPlatConvertFromMappedV6( - _In_ const QUIC_ADDR* InAddr, - _Out_ QUIC_ADDR* OutAddr - ) -{ - CXPLAT_DBG_ASSERT(InAddr->si_family == QUIC_ADDRESS_FAMILY_INET6); - if (IN6_IS_ADDR_V4MAPPED(&InAddr->Ipv6.sin6_addr)) { - OutAddr->si_family = QUIC_ADDRESS_FAMILY_INET; - OutAddr->Ipv4.sin_port = InAddr->Ipv6.sin6_port; - OutAddr->Ipv4.sin_addr = - *(IN_ADDR UNALIGNED *) - IN6_GET_ADDR_V4MAPPED(&InAddr->Ipv6.sin6_addr); - } else if (OutAddr != InAddr) { - *OutAddr = *InAddr; - } -} -#pragma warning(pop) - -#endif - -// -// Crypt Initialization -// - -QUIC_STATUS -CxPlatCryptInitialize( - void - ); - -void -CxPlatCryptUninitialize( - void - ); - -// -// Platform Worker APIs -// - -void -CxPlatWorkersInit( - void - ); - -void -CxPlatWorkersUninit( - void - ); - -BOOLEAN -CxPlatWorkersLazyStart( - _In_opt_ QUIC_EXECUTION_CONFIG* Config - ); - -CXPLAT_EVENTQ* -CxPlatWorkerGetEventQ( - _In_ uint16_t Index // Into the config processor array - ); - -void -CxPlatDataPathProcessCqe( - _In_ CXPLAT_CQE* Cqe - ); - -BOOLEAN // Returns FALSE no work was done. -CxPlatDataPathPoll( - _In_ void* Context, - _Out_ BOOLEAN* RemoveFromPolling - ); - -// -// Queries the raw datapath stack for the total size needed to allocate the -// datapath structure. -// -_IRQL_requires_max_(PASSIVE_LEVEL) -size_t -CxPlatDpRawGetDatapathSize( - _In_opt_ const QUIC_EXECUTION_CONFIG* Config - ); - -#define CXPLAT_CQE_TYPE_WORKER_WAKE CXPLAT_CQE_TYPE_QUIC_BASE + 1 -#define CXPLAT_CQE_TYPE_WORKER_UPDATE_POLL CXPLAT_CQE_TYPE_QUIC_BASE + 2 -#define CXPLAT_CQE_TYPE_SOCKET_SHUTDOWN CXPLAT_CQE_TYPE_QUIC_BASE + 3 -#define CXPLAT_CQE_TYPE_SOCKET_IO CXPLAT_CQE_TYPE_QUIC_BASE + 4 -#define CXPLAT_CQE_TYPE_SOCKET_FLUSH_TX CXPLAT_CQE_TYPE_QUIC_BASE + 5 - -extern CXPLAT_RUNDOWN_REF CxPlatWorkerRundown; +#endif // CX_PLATFORM_LINUX || _WIN32 \ No newline at end of file From 6f3e7254a54aa920b719df1efced59cb1d69e998 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 20 Sep 2023 13:53:38 -0700 Subject: [PATCH 80/87] move types to any platform --- src/platform/platform_internal.h | 70 ++++++++++++++++---------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 757b26111e..70a6e2d759 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -85,6 +85,40 @@ typedef enum CXPLAT_DATAPATH_TYPE { CXPLAT_DATAPATH_TYPE_RAW, // currently raw == xdp } CXPLAT_DATAPATH_TYPE; +// +// Type of IO. +// +typedef enum DATAPATH_IO_TYPE { + DATAPATH_IO_SIGNATURE = 'WINU', + DATAPATH_IO_RECV = DATAPATH_IO_SIGNATURE + 1, + DATAPATH_IO_SEND = DATAPATH_IO_SIGNATURE + 2, + DATAPATH_IO_QUEUE_SEND = DATAPATH_IO_SIGNATURE + 3, + DATAPATH_IO_ACCEPTEX = DATAPATH_IO_SIGNATURE + 4, + DATAPATH_IO_CONNECTEX = DATAPATH_IO_SIGNATURE + 5, + DATAPATH_IO_RIO_NOTIFY = DATAPATH_IO_SIGNATURE + 6, + DATAPATH_IO_RIO_RECV = DATAPATH_IO_SIGNATURE + 7, + DATAPATH_IO_RIO_SEND = DATAPATH_IO_SIGNATURE + 8, + DATAPATH_IO_RECV_FAILURE = DATAPATH_IO_SIGNATURE + 9, + DATAPATH_IO_MAX +} DATAPATH_IO_TYPE; + +// +// Type of IO for XDP. +// +typedef enum DATAPATH_XDP_IO_TYPE { + DATAPATH_XDP_IO_SIGNATURE = 'XDPD', + DATAPATH_XDP_IO_RECV = DATAPATH_XDP_IO_SIGNATURE + 1, + DATAPATH_XDP_IO_SEND = DATAPATH_XDP_IO_SIGNATURE + 2 +} DATAPATH_XDP_IO_TYPE; + +// +// IO header for SQE->CQE based completions. +// +typedef struct DATAPATH_IO_SQE { + DATAPATH_IO_TYPE IoType; + DATAPATH_SQE DatapathSqe; +} DATAPATH_IO_SQE; + #ifdef _KERNEL_MODE #define CXPLAT_BASE_REG_PATH L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\MsQuic\\Parameters\\" @@ -157,8 +191,6 @@ typedef enum CXPLAT_SOCKET_TYPE { CXPLAT_SOCKET_TCP_SERVER = 3 } CXPLAT_SOCKET_TYPE; -typedef struct DATAPATH_IO_SQE DATAPATH_IO_SQE; - // // Represents a single IO completion port and thread for processing work that is // completed on a single processor. @@ -890,40 +922,6 @@ typedef struct CXPLAT_DATAPATH { typedef struct CXPLAT_SOCKET_RAW CXPLAT_SOCKET_RAW; -// -// Type of IO. -// -typedef enum DATAPATH_IO_TYPE { - DATAPATH_IO_SIGNATURE = 'WINU', - DATAPATH_IO_RECV = DATAPATH_IO_SIGNATURE + 1, - DATAPATH_IO_SEND = DATAPATH_IO_SIGNATURE + 2, - DATAPATH_IO_QUEUE_SEND = DATAPATH_IO_SIGNATURE + 3, - DATAPATH_IO_ACCEPTEX = DATAPATH_IO_SIGNATURE + 4, - DATAPATH_IO_CONNECTEX = DATAPATH_IO_SIGNATURE + 5, - DATAPATH_IO_RIO_NOTIFY = DATAPATH_IO_SIGNATURE + 6, - DATAPATH_IO_RIO_RECV = DATAPATH_IO_SIGNATURE + 7, - DATAPATH_IO_RIO_SEND = DATAPATH_IO_SIGNATURE + 8, - DATAPATH_IO_RECV_FAILURE = DATAPATH_IO_SIGNATURE + 9, - DATAPATH_IO_MAX -} DATAPATH_IO_TYPE; - -// -// Type of IO for XDP. -// -typedef enum DATAPATH_XDP_IO_TYPE { - DATAPATH_XDP_IO_SIGNATURE = 'XDPD', - DATAPATH_XDP_IO_RECV = DATAPATH_XDP_IO_SIGNATURE + 1, - DATAPATH_XDP_IO_SEND = DATAPATH_XDP_IO_SIGNATURE + 2 -} DATAPATH_XDP_IO_TYPE; - -// -// IO header for SQE->CQE based completions. -// -typedef struct DATAPATH_IO_SQE { - DATAPATH_IO_TYPE IoType; - DATAPATH_SQE DatapathSqe; -} DATAPATH_IO_SQE; - _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS SocketCreateUdp( From 57a55ecf045a91a54d736aad52aebba942da0323 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 20 Sep 2023 13:58:04 -0700 Subject: [PATCH 81/87] add abstruction layer for linux --- src/platform/datapath_linux.c | 465 ++++++++++++++++++++++++++++++++++ 1 file changed, 465 insertions(+) create mode 100644 src/platform/datapath_linux.c diff --git a/src/platform/datapath_linux.c b/src/platform/datapath_linux.c new file mode 100644 index 0000000000..e13fec073a --- /dev/null +++ b/src/platform/datapath_linux.c @@ -0,0 +1,465 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + QUIC Datapath Implementation (User Mode) + +--*/ + +#include "platform_internal.h" + +#ifdef QUIC_CLOG +#include "datapath_epoll.c.clog.h" +#endif + +#pragma warning(disable:4116) // unnamed type definition in parentheses + +#pragma warning(disable:4100) // unreferenced +#pragma warning(disable:6101) // uninitialized +#pragma warning(disable:6386) // buffer overrun + +#define IS_LOOPBACK(Address) ((Address.si_family == QUIC_ADDRESS_FAMILY_INET && \ + Address.Ipv4.sin_addr.S_un.S_addr == htonl(INADDR_LOOPBACK)) || \ + (Address.si_family == QUIC_ADDRESS_FAMILY_INET6 && \ + IN6_IS_ADDR_LOOPBACK(&Address.Ipv6.sin6_addr))) + +#define DatapathType(SendData) ((CXPLAT_SEND_DATA_COMMON*)(SendData))->DatapathType + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatDataPathInitialize( + _In_ uint32_t ClientRecvContextLength, + _In_opt_ const CXPLAT_UDP_DATAPATH_CALLBACKS* UdpCallbacks, + _In_opt_ const CXPLAT_TCP_DATAPATH_CALLBACKS* TcpCallbacks, + _In_opt_ QUIC_EXECUTION_CONFIG* Config, + _Out_ CXPLAT_DATAPATH** NewDataPath + ) +{ + return + DataPathInitialize( + ClientRecvContextLength, + UdpCallbacks, + TcpCallbacks, + Config, + NewDataPath); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatDataPathUninitialize( + _In_ CXPLAT_DATAPATH* Datapath + ) +{ + DataPathUninitialize(Datapath); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatDataPathUpdateConfig( + _In_ CXPLAT_DATAPATH* Datapath, + _In_ QUIC_EXECUTION_CONFIG* Config + ) +{ + DataPathUpdateConfig(Datapath, Config); +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +uint32_t +CxPlatDataPathGetSupportedFeatures( + _In_ CXPLAT_DATAPATH* Datapath + ) +{ + return DataPathGetSupportedFeatures(Datapath); +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +BOOLEAN +CxPlatDataPathIsPaddingPreferred( + _In_ CXPLAT_DATAPATH* Datapath, + _In_ CXPLAT_SEND_DATA* SendData + ) +{ + UNREFERENCED_PARAMETER(SendData); + return DataPathIsPaddingPreferred(Datapath); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +_Success_(QUIC_SUCCEEDED(return)) +QUIC_STATUS +CxPlatDataPathGetLocalAddresses( + _In_ CXPLAT_DATAPATH* Datapath, + _Outptr_ _At_(*Addresses, __drv_allocatesMem(Mem)) + CXPLAT_ADAPTER_ADDRESS** Addresses, + _Out_ uint32_t* AddressesCount + ) +{ + UNREFERENCED_PARAMETER(Datapath); + *Addresses = NULL; + *AddressesCount = 0; + return QUIC_STATUS_NOT_SUPPORTED; +} + +QUIC_STATUS +CxPlatDataPathGetGatewayAddresses( + _In_ CXPLAT_DATAPATH* Datapath, + _Outptr_ _At_(*GatewayAddresses, __drv_allocatesMem(Mem)) + QUIC_ADDR** GatewayAddresses, + _Out_ uint32_t* GatewayAddressesCount + ) +{ + UNREFERENCED_PARAMETER(Datapath); + *GatewayAddresses = NULL; + *GatewayAddressesCount = 0; + return QUIC_STATUS_NOT_SUPPORTED; +} + +// private func +void +CxPlatDataPathPopulateTargetAddress( + _In_ QUIC_ADDRESS_FAMILY Family, + _In_ ADDRINFO* AddrInfo, + _Out_ QUIC_ADDR* Address + ) +{ + struct sockaddr_in6* SockAddrIn6 = NULL; + struct sockaddr_in* SockAddrIn = NULL; + + CxPlatZeroMemory(Address, sizeof(QUIC_ADDR)); + + if (AddrInfo->ai_addr->sa_family == AF_INET6) { + CXPLAT_DBG_ASSERT(sizeof(struct sockaddr_in6) == AddrInfo->ai_addrlen); + + // + // Is this a mapped ipv4 one? + // + + SockAddrIn6 = (struct sockaddr_in6*)AddrInfo->ai_addr; + + if (Family == QUIC_ADDRESS_FAMILY_UNSPEC && IN6_IS_ADDR_V4MAPPED(&SockAddrIn6->sin6_addr)) { + SockAddrIn = &Address->Ipv4; + + // + // Get the ipv4 address from the mapped address. + // + + SockAddrIn->sin_family = QUIC_ADDRESS_FAMILY_INET; + memcpy(&SockAddrIn->sin_addr.s_addr, &SockAddrIn6->sin6_addr.s6_addr[12], 4); + SockAddrIn->sin_port = SockAddrIn6->sin6_port; + + return; + } + Address->Ipv6 = *SockAddrIn6; + Address->Ipv6.sin6_family = QUIC_ADDRESS_FAMILY_INET6; + return; + } + + if (AddrInfo->ai_addr->sa_family == AF_INET) { + CXPLAT_DBG_ASSERT(sizeof(struct sockaddr_in) == AddrInfo->ai_addrlen); + SockAddrIn = (struct sockaddr_in*)AddrInfo->ai_addr; + Address->Ipv4 = *SockAddrIn; + Address->Ipv4.sin_family = QUIC_ADDRESS_FAMILY_INET; + return; + } + + CXPLAT_FRE_ASSERT(FALSE); +} + +QUIC_STATUS +CxPlatDataPathResolveAddress( + _In_ CXPLAT_DATAPATH* Datapath, + _In_z_ const char* HostName, + _Inout_ QUIC_ADDR* Address + ) +{ + UNREFERENCED_PARAMETER(Datapath); + QUIC_STATUS Status = QUIC_STATUS_SUCCESS; + ADDRINFO Hints = {0}; + ADDRINFO* AddrInfo = NULL; + int Result = 0; + + // + // Prepopulate hint with input family. It might be unspecified. + // + Hints.ai_family = Address->Ip.sa_family; + if (Hints.ai_family == QUIC_ADDRESS_FAMILY_INET6) { + Hints.ai_family = AF_INET6; + } + + // + // Try numeric name first. + // + Hints.ai_flags = AI_NUMERICHOST; + Result = getaddrinfo(HostName, NULL, &Hints, &AddrInfo); + if (Result == 0) { + CxPlatDataPathPopulateTargetAddress(Hints.ai_family, AddrInfo, Address); + freeaddrinfo(AddrInfo); + AddrInfo = NULL; + goto Exit; + } + + // + // Try canonical host name. + // + Hints.ai_flags = AI_CANONNAME; + Result = getaddrinfo(HostName, NULL, &Hints, &AddrInfo); + if (Result == 0) { + CxPlatDataPathPopulateTargetAddress(Hints.ai_family, AddrInfo, Address); + freeaddrinfo(AddrInfo); + AddrInfo = NULL; + goto Exit; + } + + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + (uint32_t)Result, + "Resolving hostname to IP"); + QuicTraceLogError( + DatapathResolveHostNameFailed, + "[%p] Couldn't resolve hostname '%s' to an IP address", + Datapath, + HostName); + Status = (QUIC_STATUS)Result; + +Exit: + + return Status; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatSocketCreateUdp( + _In_ CXPLAT_DATAPATH* Datapath, + _In_ const CXPLAT_UDP_CONFIG* Config, + _Out_ CXPLAT_SOCKET** NewSocket + ) +{ + return SocketCreateUdp( + Datapath, + Config, + NewSocket); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatSocketCreateTcp( + _In_ CXPLAT_DATAPATH* Datapath, + _In_opt_ const QUIC_ADDR* LocalAddress, + _In_ const QUIC_ADDR* RemoteAddress, + _In_opt_ void* CallbackContext, + _Out_ CXPLAT_SOCKET** NewSocket + ) +{ + return SocketCreateTcp( + Datapath, + LocalAddress, + RemoteAddress, + CallbackContext, + NewSocket); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatSocketCreateTcpListener( + _In_ CXPLAT_DATAPATH* Datapath, + _In_opt_ const QUIC_ADDR* LocalAddress, + _In_opt_ void* RecvCallbackContext, + _Out_ CXPLAT_SOCKET** NewSocket + ) +{ + return SocketCreateTcpListener( + Datapath, + LocalAddress, + RecvCallbackContext, + NewSocket); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatSocketDelete( + _In_ CXPLAT_SOCKET* Socket + ) +{ + SocketDelete(Socket); +} + +QUIC_STATUS +CxPlatSocketUpdateQeo( + _In_ CXPLAT_SOCKET* Socket, + _In_reads_(OffloadCount) + const CXPLAT_QEO_CONNECTION* Offloads, + _In_ uint32_t OffloadCount + ) +{ + UNREFERENCED_PARAMETER(Socket); + UNREFERENCED_PARAMETER(Offloads); + UNREFERENCED_PARAMETER(OffloadCount); + return QUIC_STATUS_NOT_SUPPORTED; +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +uint16_t +CxPlatSocketGetLocalMtu( + _In_ CXPLAT_SOCKET* Socket + ) +{ + CXPLAT_DBG_ASSERT(Socket != NULL); + return Socket->Mtu; +} + +void +CxPlatSocketGetLocalAddress( + _In_ CXPLAT_SOCKET* Socket, + _Out_ QUIC_ADDR* Address + ) +{ + CXPLAT_DBG_ASSERT(Socket != NULL); + *Address = Socket->LocalAddress; +} + +void +CxPlatSocketGetRemoteAddress( + _In_ CXPLAT_SOCKET* Socket, + _Out_ QUIC_ADDR* Address + ) +{ + CXPLAT_DBG_ASSERT(Socket != NULL); + *Address = Socket->RemoteAddress; +} + +void +CxPlatRecvDataReturn( + _In_opt_ CXPLAT_RECV_DATA* RecvDataChain + ) +{ + RecvDataReturn(RecvDataChain); +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +CXPLAT_SEND_DATA* +CxPlatSendDataAlloc( + _In_ CXPLAT_SOCKET* Socket, + _Inout_ CXPLAT_SEND_CONFIG* Config + ) +{ + return SendDataAlloc(Socket, Config); +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +CxPlatSendDataFree( + _In_ CXPLAT_SEND_DATA* SendData + ) +{ + SendDataFree(SendData); +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +_Success_(return != NULL) +QUIC_BUFFER* +CxPlatSendDataAllocBuffer( + _In_ CXPLAT_SEND_DATA* SendData, + _In_ uint16_t MaxBufferLength + ) +{ + return SendDataAllocBuffer(SendData, MaxBufferLength); +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +void +CxPlatSendDataFreeBuffer( + _In_ CXPLAT_SEND_DATA* SendData, + _In_ QUIC_BUFFER* Buffer + ) +{ + SendDataFreeBuffer(SendData, Buffer); +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +BOOLEAN +CxPlatSendDataIsFull( + _In_ CXPLAT_SEND_DATA* SendData + ) +{ + return SendDataIsFull(SendData); +} + +_IRQL_requires_max_(DISPATCH_LEVEL) +QUIC_STATUS +CxPlatSocketSend( + _In_ CXPLAT_SOCKET* Socket, + _In_ const CXPLAT_ROUTE* Route, + _In_ CXPLAT_SEND_DATA* SendData + ) +{ + return SocketSend(Socket, Route, SendData); +} + +void +CxPlatDataPathProcessCqe( + _In_ CXPLAT_CQE* Cqe + ) +{ + DataPathProcessCqe(Cqe); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +QuicCopyRouteInfo( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ + *DstRoute = *SrcRoute; +} + +void +CxPlatResolveRouteComplete( + _In_ void* Connection, + _Inout_ CXPLAT_ROUTE* Route, + _In_reads_bytes_(6) const uint8_t* PhysicalAddress, + _In_ uint8_t PathId + ) +{ + UNREFERENCED_PARAMETER(Connection); + UNREFERENCED_PARAMETER(Route); + UNREFERENCED_PARAMETER(PhysicalAddress); + UNREFERENCED_PARAMETER(PathId); +} + +// +// Tries to resolve route and neighbor for the given destination address. +// +_IRQL_requires_max_(PASSIVE_LEVEL) +QUIC_STATUS +CxPlatResolveRoute( + _In_ CXPLAT_SOCKET* Socket, + _Inout_ CXPLAT_ROUTE* Route, + _In_ uint8_t PathId, + _In_ void* Context, + _In_ CXPLAT_ROUTE_RESOLUTION_CALLBACK_HANDLER Callback + ) +{ + UNREFERENCED_PARAMETER(Socket); + UNREFERENCED_PARAMETER(PathId); + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Callback); + Route->State = RouteResolved; + return QUIC_STATUS_SUCCESS; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatUpdateRoute( + _Inout_ CXPLAT_ROUTE* DstRoute, + _In_ CXPLAT_ROUTE* SrcRoute + ) +{ + UNREFERENCED_PARAMETER(DstRoute); + UNREFERENCED_PARAMETER(SrcRoute); +} + From 6c905fc47d37a0dea00f493e4d6e0018e6afdfd7 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 20 Sep 2023 13:59:36 -0700 Subject: [PATCH 82/87] add clog --- src/generated/linux/datapath_epoll.c.clog.h | 44 ------------------ .../linux/datapath_epoll.c.clog.h.lttng.h | 46 ------------------- .../linux/datapath_raw_linux.c.clog.h | 22 --------- .../linux/datapath_raw_linux.c.clog.h.lttng.h | 31 ------------- 4 files changed, 143 deletions(-) diff --git a/src/generated/linux/datapath_epoll.c.clog.h b/src/generated/linux/datapath_epoll.c.clog.h index c3aaa3a517..10356b54aa 100644 --- a/src/generated/linux/datapath_epoll.c.clog.h +++ b/src/generated/linux/datapath_epoll.c.clog.h @@ -18,10 +18,6 @@ #define _clog_MACRO_QuicTraceLogWarning 1 #define QuicTraceLogWarning(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) #endif -#ifndef _clog_MACRO_QuicTraceLogError -#define _clog_MACRO_QuicTraceLogError 1 -#define QuicTraceLogError(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) -#endif #ifndef _clog_MACRO_QuicTraceEvent #define _clog_MACRO_QuicTraceEvent 1 #define QuicTraceEvent(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) @@ -47,26 +43,6 @@ tracepoint(CLOG_DATAPATH_EPOLL_C, DatapathRecvEmpty , arg2);\ -/*---------------------------------------------------------- -// Decoder Ring for DatapathResolveHostNameFailed -// [%p] Couldn't resolve hostname '%s' to an IP address -// QuicTraceLogError( - DatapathResolveHostNameFailed, - "[%p] Couldn't resolve hostname '%s' to an IP address", - Datapath, - HostName); -// arg2 = arg2 = Datapath = arg2 -// arg3 = arg3 = HostName = arg3 -----------------------------------------------------------*/ -#ifndef _clog_4_ARGS_TRACE_DatapathResolveHostNameFailed -#define _clog_4_ARGS_TRACE_DatapathResolveHostNameFailed(uniqueId, encoded_arg_string, arg2, arg3)\ -tracepoint(CLOG_DATAPATH_EPOLL_C, DatapathResolveHostNameFailed , arg2, arg3);\ - -#endif - - - - /*---------------------------------------------------------- // Decoder Ring for AllocFailure // Allocation of '%s' failed. (%llu bytes) @@ -87,26 +63,6 @@ tracepoint(CLOG_DATAPATH_EPOLL_C, AllocFailure , arg2, arg3);\ -/*---------------------------------------------------------- -// Decoder Ring for LibraryErrorStatus -// [ lib] ERROR, %u, %s. -// QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - (uint32_t)Result, - "Resolving hostname to IP"); -// arg2 = arg2 = (uint32_t)Result = arg2 -// arg3 = arg3 = "Resolving hostname to IP" = arg3 -----------------------------------------------------------*/ -#ifndef _clog_4_ARGS_TRACE_LibraryErrorStatus -#define _clog_4_ARGS_TRACE_LibraryErrorStatus(uniqueId, encoded_arg_string, arg2, arg3)\ -tracepoint(CLOG_DATAPATH_EPOLL_C, LibraryErrorStatus , arg2, arg3);\ - -#endif - - - - /*---------------------------------------------------------- // Decoder Ring for DatapathErrorStatus // [data][%p] ERROR, %u, %s. diff --git a/src/generated/linux/datapath_epoll.c.clog.h.lttng.h b/src/generated/linux/datapath_epoll.c.clog.h.lttng.h index 24d76786c3..8afdfaf31d 100644 --- a/src/generated/linux/datapath_epoll.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_epoll.c.clog.h.lttng.h @@ -20,29 +20,6 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_EPOLL_C, DatapathRecvEmpty, -/*---------------------------------------------------------- -// Decoder Ring for DatapathResolveHostNameFailed -// [%p] Couldn't resolve hostname '%s' to an IP address -// QuicTraceLogError( - DatapathResolveHostNameFailed, - "[%p] Couldn't resolve hostname '%s' to an IP address", - Datapath, - HostName); -// arg2 = arg2 = Datapath = arg2 -// arg3 = arg3 = HostName = arg3 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_EPOLL_C, DatapathResolveHostNameFailed, - TP_ARGS( - const void *, arg2, - const char *, arg3), - TP_FIELDS( - ctf_integer_hex(uint64_t, arg2, arg2) - ctf_string(arg3, arg3) - ) -) - - - /*---------------------------------------------------------- // Decoder Ring for AllocFailure // Allocation of '%s' failed. (%llu bytes) @@ -66,29 +43,6 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_EPOLL_C, AllocFailure, -/*---------------------------------------------------------- -// Decoder Ring for LibraryErrorStatus -// [ lib] ERROR, %u, %s. -// QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - (uint32_t)Result, - "Resolving hostname to IP"); -// arg2 = arg2 = (uint32_t)Result = arg2 -// arg3 = arg3 = "Resolving hostname to IP" = arg3 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_EPOLL_C, LibraryErrorStatus, - TP_ARGS( - unsigned int, arg2, - const char *, arg3), - TP_FIELDS( - ctf_integer(unsigned int, arg2, arg2) - ctf_string(arg3, arg3) - ) -) - - - /*---------------------------------------------------------- // Decoder Ring for DatapathErrorStatus // [data][%p] ERROR, %u, %s. diff --git a/src/generated/linux/datapath_raw_linux.c.clog.h b/src/generated/linux/datapath_raw_linux.c.clog.h index 77d1a59253..f8b90a0cb2 100644 --- a/src/generated/linux/datapath_raw_linux.c.clog.h +++ b/src/generated/linux/datapath_raw_linux.c.clog.h @@ -61,28 +61,6 @@ tracepoint(CLOG_DATAPATH_RAW_LINUX_C, LibraryErrorStatus , arg2, arg3);\ -/*---------------------------------------------------------- -// Decoder Ring for DatapathCreated -// [data][%p] Created, local=%!ADDR!, remote=%!ADDR! -// QuicTraceEvent( - DatapathCreated, - "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", - *NewSocket, - CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress), - CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress)); -// arg2 = arg2 = *NewSocket = arg2 -// arg3 = arg3 = CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress) = arg3 -// arg4 = arg4 = CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress) = arg4 -----------------------------------------------------------*/ -#ifndef _clog_7_ARGS_TRACE_DatapathCreated -#define _clog_7_ARGS_TRACE_DatapathCreated(uniqueId, encoded_arg_string, arg2, arg3, arg3_len, arg4, arg4_len)\ -tracepoint(CLOG_DATAPATH_RAW_LINUX_C, DatapathCreated , arg2, arg3_len, arg3, arg4_len, arg4);\ - -#endif - - - - /*---------------------------------------------------------- // Decoder Ring for DatapathRecv // [data][%p] Recv %u bytes (segment=%hu) Src=%!ADDR! Dst=%!ADDR! diff --git a/src/generated/linux/datapath_raw_linux.c.clog.h.lttng.h b/src/generated/linux/datapath_raw_linux.c.clog.h.lttng.h index 7c677d24d0..70aaced766 100644 --- a/src/generated/linux/datapath_raw_linux.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_raw_linux.c.clog.h.lttng.h @@ -47,37 +47,6 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_LINUX_C, LibraryErrorStatus, -/*---------------------------------------------------------- -// Decoder Ring for DatapathCreated -// [data][%p] Created, local=%!ADDR!, remote=%!ADDR! -// QuicTraceEvent( - DatapathCreated, - "[data][%p] Created, local=%!ADDR!, remote=%!ADDR!", - *NewSocket, - CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress), - CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress)); -// arg2 = arg2 = *NewSocket = arg2 -// arg3 = arg3 = CASTED_CLOG_BYTEARRAY(Config->LocalAddress ? sizeof(*Config->LocalAddress) : 0, Config->LocalAddress) = arg3 -// arg4 = arg4 = CASTED_CLOG_BYTEARRAY(Config->RemoteAddress ? sizeof(*Config->RemoteAddress) : 0, Config->RemoteAddress) = arg4 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_LINUX_C, DatapathCreated, - TP_ARGS( - const void *, arg2, - unsigned int, arg3_len, - const void *, arg3, - unsigned int, arg4_len, - const void *, arg4), - TP_FIELDS( - ctf_integer_hex(uint64_t, arg2, arg2) - ctf_integer(unsigned int, arg3_len, arg3_len) - ctf_sequence(char, arg3, arg3, unsigned int, arg3_len) - ctf_integer(unsigned int, arg4_len, arg4_len) - ctf_sequence(char, arg4, arg4, unsigned int, arg4_len) - ) -) - - - /*---------------------------------------------------------- // Decoder Ring for DatapathRecv // [data][%p] Recv %u bytes (segment=%hu) Src=%!ADDR! Dst=%!ADDR! From 7e07687cd2606388b196fbb08d6ce66680685370 Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 20 Sep 2023 14:21:38 -0700 Subject: [PATCH 83/87] add clog dependencies --- scripts/clog.inputs | 1 + src/generated/linux/datapath_linux.c.clog.h | 73 +++++++++++++++++++ .../linux/datapath_linux.c.clog.h.lttng.h | 46 ++++++++++++ .../linux/quic.clog_datapath_linux.c.clog.h.c | 7 ++ src/platform/datapath_linux.c | 2 +- 5 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 src/generated/linux/datapath_linux.c.clog.h create mode 100644 src/generated/linux/datapath_linux.c.clog.h.lttng.h create mode 100644 src/generated/linux/quic.clog_datapath_linux.c.clog.h.c diff --git a/scripts/clog.inputs b/scripts/clog.inputs index b22e83f406..e638867c66 100644 --- a/scripts/clog.inputs +++ b/scripts/clog.inputs @@ -13,6 +13,7 @@ ../src/platform/certificates_posix.c ../src/platform/hashtable.c ../src/platform/datapath_winuser.c +../src/platform/datapath_linux.c ../src/platform/datapath_raw_dpdk.c ../src/platform/datapath_raw_socket.c ../src/platform/datapath_raw_socket_win.c diff --git a/src/generated/linux/datapath_linux.c.clog.h b/src/generated/linux/datapath_linux.c.clog.h new file mode 100644 index 0000000000..962cecc531 --- /dev/null +++ b/src/generated/linux/datapath_linux.c.clog.h @@ -0,0 +1,73 @@ +#ifndef CLOG_DO_NOT_INCLUDE_HEADER +#include +#endif +#undef TRACEPOINT_PROVIDER +#define TRACEPOINT_PROVIDER CLOG_DATAPATH_LINUX_C +#undef TRACEPOINT_PROBE_DYNAMIC_LINKAGE +#define TRACEPOINT_PROBE_DYNAMIC_LINKAGE +#undef TRACEPOINT_INCLUDE +#define TRACEPOINT_INCLUDE "datapath_linux.c.clog.h.lttng.h" +#if !defined(DEF_CLOG_DATAPATH_LINUX_C) || defined(TRACEPOINT_HEADER_MULTI_READ) +#define DEF_CLOG_DATAPATH_LINUX_C +#include +#define __int64 __int64_t +#include "datapath_linux.c.clog.h.lttng.h" +#endif +#include +#ifndef _clog_MACRO_QuicTraceLogError +#define _clog_MACRO_QuicTraceLogError 1 +#define QuicTraceLogError(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) +#endif +#ifndef _clog_MACRO_QuicTraceEvent +#define _clog_MACRO_QuicTraceEvent 1 +#define QuicTraceEvent(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) +#endif +#ifdef __cplusplus +extern "C" { +#endif +/*---------------------------------------------------------- +// Decoder Ring for DatapathResolveHostNameFailed +// [%p] Couldn't resolve hostname '%s' to an IP address +// QuicTraceLogError( + DatapathResolveHostNameFailed, + "[%p] Couldn't resolve hostname '%s' to an IP address", + Datapath, + HostName); +// arg2 = arg2 = Datapath = arg2 +// arg3 = arg3 = HostName = arg3 +----------------------------------------------------------*/ +#ifndef _clog_4_ARGS_TRACE_DatapathResolveHostNameFailed +#define _clog_4_ARGS_TRACE_DatapathResolveHostNameFailed(uniqueId, encoded_arg_string, arg2, arg3)\ +tracepoint(CLOG_DATAPATH_LINUX_C, DatapathResolveHostNameFailed , arg2, arg3);\ + +#endif + + + + +/*---------------------------------------------------------- +// Decoder Ring for LibraryErrorStatus +// [ lib] ERROR, %u, %s. +// QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + (uint32_t)Result, + "Resolving hostname to IP"); +// arg2 = arg2 = (uint32_t)Result = arg2 +// arg3 = arg3 = "Resolving hostname to IP" = arg3 +----------------------------------------------------------*/ +#ifndef _clog_4_ARGS_TRACE_LibraryErrorStatus +#define _clog_4_ARGS_TRACE_LibraryErrorStatus(uniqueId, encoded_arg_string, arg2, arg3)\ +tracepoint(CLOG_DATAPATH_LINUX_C, LibraryErrorStatus , arg2, arg3);\ + +#endif + + + + +#ifdef __cplusplus +} +#endif +#ifdef CLOG_INLINE_IMPLEMENTATION +#include "quic.clog_datapath_linux.c.clog.h.c" +#endif diff --git a/src/generated/linux/datapath_linux.c.clog.h.lttng.h b/src/generated/linux/datapath_linux.c.clog.h.lttng.h new file mode 100644 index 0000000000..da99e78ea8 --- /dev/null +++ b/src/generated/linux/datapath_linux.c.clog.h.lttng.h @@ -0,0 +1,46 @@ + + + +/*---------------------------------------------------------- +// Decoder Ring for DatapathResolveHostNameFailed +// [%p] Couldn't resolve hostname '%s' to an IP address +// QuicTraceLogError( + DatapathResolveHostNameFailed, + "[%p] Couldn't resolve hostname '%s' to an IP address", + Datapath, + HostName); +// arg2 = arg2 = Datapath = arg2 +// arg3 = arg3 = HostName = arg3 +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_LINUX_C, DatapathResolveHostNameFailed, + TP_ARGS( + const void *, arg2, + const char *, arg3), + TP_FIELDS( + ctf_integer_hex(uint64_t, arg2, arg2) + ctf_string(arg3, arg3) + ) +) + + + +/*---------------------------------------------------------- +// Decoder Ring for LibraryErrorStatus +// [ lib] ERROR, %u, %s. +// QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + (uint32_t)Result, + "Resolving hostname to IP"); +// arg2 = arg2 = (uint32_t)Result = arg2 +// arg3 = arg3 = "Resolving hostname to IP" = arg3 +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_LINUX_C, LibraryErrorStatus, + TP_ARGS( + unsigned int, arg2, + const char *, arg3), + TP_FIELDS( + ctf_integer(unsigned int, arg2, arg2) + ctf_string(arg3, arg3) + ) +) diff --git a/src/generated/linux/quic.clog_datapath_linux.c.clog.h.c b/src/generated/linux/quic.clog_datapath_linux.c.clog.h.c new file mode 100644 index 0000000000..fbb2aa191f --- /dev/null +++ b/src/generated/linux/quic.clog_datapath_linux.c.clog.h.c @@ -0,0 +1,7 @@ +#include +#ifdef BUILDING_TRACEPOINT_PROVIDER +#define TRACEPOINT_CREATE_PROBES +#else +#define TRACEPOINT_DEFINE +#endif +#include "datapath_linux.c.clog.h" diff --git a/src/platform/datapath_linux.c b/src/platform/datapath_linux.c index e13fec073a..164eea3f03 100644 --- a/src/platform/datapath_linux.c +++ b/src/platform/datapath_linux.c @@ -12,7 +12,7 @@ #include "platform_internal.h" #ifdef QUIC_CLOG -#include "datapath_epoll.c.clog.h" +#include "datapath_linux.c.clog.h" #endif #pragma warning(disable:4116) // unnamed type definition in parentheses From 8181f2e45794567c12426a754f5a7bc24f5f7c4b Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Wed, 20 Sep 2023 14:49:39 -0700 Subject: [PATCH 84/87] fix CodeCheck issues --- src/platform/datapath_linux.c | 4 ++-- src/platform/datapath_raw.h | 1 - src/platform/datapath_raw_linux.c | 10 ++++------ src/platform/datapath_raw_xdp_linux.c | 3 ++- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/platform/datapath_linux.c b/src/platform/datapath_linux.c index 164eea3f03..574a207ccc 100644 --- a/src/platform/datapath_linux.c +++ b/src/platform/datapath_linux.c @@ -419,13 +419,13 @@ QuicCopyRouteInfo( void CxPlatResolveRouteComplete( - _In_ void* Connection, + _In_ void* Context, _Inout_ CXPLAT_ROUTE* Route, _In_reads_bytes_(6) const uint8_t* PhysicalAddress, _In_ uint8_t PathId ) { - UNREFERENCED_PARAMETER(Connection); + UNREFERENCED_PARAMETER(Context); UNREFERENCED_PARAMETER(Route); UNREFERENCED_PARAMETER(PhysicalAddress); UNREFERENCED_PARAMETER(PathId); diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index 63be011f02..9cb572a4cb 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -43,7 +43,6 @@ typedef struct QUIC_CACHEALIGN CXPLAT_ROUTE_RESOLUTION_WORKER { } CXPLAT_ROUTE_RESOLUTION_WORKER; typedef struct CXPLAT_DATAPATH_RAW { - // FIXME: want to inline CXPLAT_DATAPATH const CXPLAT_DATAPATH *ParentDataPath; CXPLAT_SOCKET_POOL SocketPool; diff --git a/src/platform/datapath_raw_linux.c b/src/platform/datapath_raw_linux.c index 479006ce31..91879868ea 100644 --- a/src/platform/datapath_raw_linux.c +++ b/src/platform/datapath_raw_linux.c @@ -226,11 +226,9 @@ RawSocketGetLocalMtu( _In_ CXPLAT_SOCKET_RAW* Socket ) { - if (Socket->UseTcp) { - return 1488; // Reserve space for TCP header. - } else { - return 1500; - } + // Reserve space for TCP header. + return Socket->UseTcp ? 1488 : 1500; + } _IRQL_requires_max_(DISPATCH_LEVEL) @@ -281,7 +279,7 @@ CxPlatDpRawRxEthernet( CXPLAT_DBG_ASSERT(Packets[i+1]->Next == NULL); i++; } - Datapath->ParentDataPath->UdpHandlers.Receive(CxPlatRawToSocket(Socket), Socket->ClientContext, (CXPLAT_RECV_DATA*)PacketChain); + Datapath->ParentDataPath->UdpHandlers.Receive(CxPlatRawToSocket(Socket), Socket->ClientContext, PacketChain); } else if (PacketChain->Reserved == L4_TYPE_TCP_SYN || PacketChain->Reserved == L4_TYPE_TCP_SYNACK) { CxPlatDpRawSocketAckSyn(Socket, PacketChain); CxPlatDpRawRxFree(PacketChain); diff --git a/src/platform/datapath_raw_xdp_linux.c b/src/platform/datapath_raw_xdp_linux.c index 490e0e3f72..443823fe1d 100644 --- a/src/platform/datapath_raw_xdp_linux.c +++ b/src/platform/datapath_raw_xdp_linux.c @@ -18,7 +18,8 @@ #include "datapath_raw_xdp_linux.c.clog.h" #endif -typedef struct XDP_DATAPATH { +// TODO: remove this exception when finalizing members +typedef struct XDP_DATAPATH { // NOLINT(clang-analyzer-optin.performance.Padding) CXPLAT_DATAPATH_RAW; __attribute__((aligned(64))) // From c6943e3d05aef59a0ddf79305ffbdc44ac3e26aa Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Thu, 21 Sep 2023 11:30:00 -0700 Subject: [PATCH 85/87] remove xdp specific artifact dir name and always install xdp deps --- .azure/azure-pipelines.perf.yml | 6 +++--- .azure/azure-pipelines.periodic.yml | 24 +++--------------------- scripts/build.ps1 | 6 ------ scripts/package-nuget.ps1 | 9 +++------ 4 files changed, 9 insertions(+), 36 deletions(-) diff --git a/.azure/azure-pipelines.perf.yml b/.azure/azure-pipelines.perf.yml index 85c73a752f..28496cee50 100644 --- a/.azure/azure-pipelines.perf.yml +++ b/.azure/azure-pipelines.perf.yml @@ -171,7 +171,7 @@ stages: arch: ${{ parameters.arch }} tls: schannel config: Release - extraPrepareArgs: -DisableTest + extraPrepareArgs: -DisableTest -InstallXdpSdk ${{ if eq(parameters.pgo_mode, false) }}: extraBuildArgs: -DisableTest -DisableTools ${{ if eq(parameters.pgo_mode, true) }}: @@ -324,9 +324,9 @@ stages: logProfile: ${{ parameters.logging }} timeout: ${{ parameters.timeout }} ${{ if eq(parameters.QTIP, true) }}: - extraTestArgs: -ExtraArtifactDir _Xdp -XDP -QTIP + extraTestArgs: -XDP -QTIP ${{ else }}: - extraTestArgs: -ExtraArtifactDir _Xdp -XDP + extraTestArgs: -XDP ${{ if ne(parameters.testToRun, 'all') }}: testToRun: ${{ parameters.testToRun }} testTypes: Remote diff --git a/.azure/azure-pipelines.periodic.yml b/.azure/azure-pipelines.periodic.yml index ff9d789027..67b183609d 100644 --- a/.azure/azure-pipelines.periodic.yml +++ b/.azure/azure-pipelines.periodic.yml @@ -34,7 +34,7 @@ stages: arch: x64 tls: schannel config: Release - extraPrepareArgs: -DisableTest + extraPrepareArgs: -DisableTest -InstallXdpSdk extraBuildArgs: -DisableTest -DisableTools -PGO - stage: build_winuser_openssl @@ -69,23 +69,6 @@ stages: extraPrepareArgs: -DisableTest extraBuildArgs: -DisableTest -DisableTools -PGO -- stage: build_winuser_xdp - displayName: Build Windows (XDP) - dependsOn: [] - variables: - runCodesignValidationInjection: false - jobs: - - template: ./templates/build-config-user.yml - parameters: - image: windows-latest - platform: windows - arch: x64 - tls: schannel - config: Release - extraName: 'xdp' - extraPrepareArgs: -DisableTest -InstallXdpSdk - extraBuildArgs: -DisableTest -DisableTools -ExtraArtifactDir Xdp -PGO - # # Performance Tests # @@ -141,7 +124,7 @@ stages: - stage: perf_winuser_xdp displayName: Performance Testing Windows (XDP) dependsOn: - - build_winuser_xdp + - build_winuser_schannel jobs: - template: ./templates/run-performance.yml parameters: @@ -150,8 +133,7 @@ stages: localTls: schannel remoteTls: schannel arch: x64 - extraArtifactDir: '_Xdp' - extraTestArgs: -ExtraArtifactDir _Xdp -XDP + extraTestArgs: -XDP testTypes: Remote extraArgs: -PGO failOnRegression: 0 diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 13714cd272..fd5b1e8d44 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -90,9 +90,6 @@ This script provides helpers for building msquic. .PARAMETER EnableHighResolutionTimers Configures the system to use high resolution timers. -.PARAMETER UseXdp - Use XDP for the datapath instead of system socket APIs. - .PARAMETER ExtraArtifactDir Add an extra classifier to the artifact directory to allow publishing alternate builds of same base library @@ -202,9 +199,6 @@ param ( [Parameter(Mandatory = $false)] [switch]$EnableHighResolutionTimers = $false, - [Parameter(Mandatory = $false)] - [switch]$UseXdp = $false, - [Parameter(Mandatory = $false)] [string]$ExtraArtifactDir = "", diff --git a/scripts/package-nuget.ps1 b/scripts/package-nuget.ps1 index 7304e98e6e..ac39ea6e13 100644 --- a/scripts/package-nuget.ps1 +++ b/scripts/package-nuget.ps1 @@ -100,9 +100,6 @@ $NativeDir = Join-Path $PackagingDir "build/native" foreach ($Arch in $Architectures) { $BuildPath = Join-Path $PlatformDir "$($Arch)_$($Config)_$($Tls)" - if ($XDP -and !$GHA) { - $BuildPath += "_xdp" - } $LibPath = Join-Path $NativeDir "lib/$Arch" $BinPath = Join-Path $NativeDir "bin/$Arch" @@ -144,10 +141,10 @@ $NugetSourceFolder = Join-Path $RootDir "src/distribution" if ($UWP) { $PackageName = "Microsoft.Native.Quic.MsQuic.UWP.$Tls" -} elseif ($XDP) { - Copy-Item -Path (Join-Path $PSScriptRoot xdp.json) -Destination (Join-Path $PackagingDir xdp-temp.json) - $PackageName = "Microsoft.Native.Quic.MsQuic.XDP.$Tls" } else { + if ($XDP) { + Copy-Item -Path (Join-Path $PSScriptRoot xdp.json) -Destination (Join-Path $PackagingDir xdp-temp.json) + } $PackageName = "Microsoft.Native.Quic.MsQuic.$Tls" } From ee64bc1608afb17d3bc070bf0cddb04397caabea Mon Sep 17 00:00:00 2001 From: ami-GS <1991.daiki@gmail.com> Date: Thu, 21 Sep 2023 14:54:56 -0700 Subject: [PATCH 86/87] add docs --- docs/Architecture.md | 7 ++++++- docs/TEST.md | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/Architecture.md b/docs/Architecture.md index 264554f1b0..1d24224b1c 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -26,7 +26,12 @@ Another large piece of the PAL is the abstraction of UDP and TCP sockets, named It uses asynchronous callbacks driven by the current execution model (see [below](#execution-model)) threads. -Currently, it also has preview support of XDP on Windows. +Currently, it also has preview support of XDP on Windows. +XDP datapath is used when +- XDP initialization succeeds + - XDP deps are installed + - XDP capable NIC is available +- Targetting to non-loopback address ### Crypto diff --git a/docs/TEST.md b/docs/TEST.md index 2c5cd96968..e29499bf9b 100644 --- a/docs/TEST.md +++ b/docs/TEST.md @@ -23,6 +23,11 @@ Then all the tests can be run with: ./scripts/test.ps1 -Tls openssl ``` +Windows XDP datapath can be used unless a test explicitly specify loopback address by +```Powershell +./scripts/test.ps1 -UseXdp +``` + By default this will run all tests in series, with no log collection. To include log collection for failed tests, run: ```PowerShell From c63411bc693111bd780f78281f0000b981bd64d1 Mon Sep 17 00:00:00 2001 From: Nick Banks Date: Sat, 23 Sep 2023 10:17:44 -0400 Subject: [PATCH 87/87] More Fixes for XDP in automation (mostly OneBranch) --- .azure/obtemplates/build-nuget.yml | 33 ------------------ .github/workflows/code-coverage.yml | 3 +- scripts/package-nuget.ps1 | 12 ++----- .../winuser/pgo_x64/msquic.schannel.xdp.pgd | Bin 2084864 -> 0 bytes ...soft.Native.Quic.MsQuic.XDP.OpenSSL.nuspec | 18 ---------- ...oft.Native.Quic.MsQuic.XDP.OpenSSL.targets | 21 ----------- ...oft.Native.Quic.MsQuic.XDP.Schannel.nuspec | 18 ---------- ...ft.Native.Quic.MsQuic.XDP.Schannel.targets | 21 ----------- 8 files changed, 3 insertions(+), 123 deletions(-) delete mode 100644 src/bin/winuser/pgo_x64/msquic.schannel.xdp.pgd delete mode 100644 src/distribution/Microsoft.Native.Quic.MsQuic.XDP.OpenSSL.nuspec delete mode 100644 src/distribution/Microsoft.Native.Quic.MsQuic.XDP.OpenSSL.targets delete mode 100644 src/distribution/Microsoft.Native.Quic.MsQuic.XDP.Schannel.nuspec delete mode 100644 src/distribution/Microsoft.Native.Quic.MsQuic.XDP.Schannel.targets diff --git a/.azure/obtemplates/build-nuget.yml b/.azure/obtemplates/build-nuget.yml index 10c292144f..6e3f6cc031 100644 --- a/.azure/obtemplates/build-nuget.yml +++ b/.azure/obtemplates/build-nuget.yml @@ -56,19 +56,6 @@ jobs: artifact: drop_build_uwp_build_uwp_openssl_Debug path: $(Build.SourcesDirectory)\artifacts\bin\uwp - - task: DownloadPipelineArtifact@2 - inputs: - artifact: drop_build_windows_xdp_build_windows_schannel_Debug_xdp - path: $(Build.SourcesDirectory)\artifacts\bin\windows - - task: DownloadPipelineArtifact@2 - inputs: - artifact: drop_build_windows_xdp_build_windows_schannel_Release_xdp - path: $(Build.SourcesDirectory)\artifacts\bin\windows - - task: DownloadPipelineArtifact@2 - inputs: - artifact: drop_build_windows_xdp_build_windows_openssl_Release_xdp - path: $(Build.SourcesDirectory)\artifacts\bin\windows - - task: PowerShell@2 displayName: Package Nuget inputs: @@ -99,26 +86,6 @@ jobs: ${{ if eq(parameters.release, true) }}: arguments: -Tls schannel -ReleaseBuild - - task: PowerShell@2 - displayName: Package Nuget - inputs: - pwsh: false - filePath: scripts/package-nuget.ps1 - ${{ if eq(parameters.release, false) }}: - arguments: -Tls schannel -XDP - ${{ if eq(parameters.release, true) }}: - arguments: -Tls schannel -ReleaseBuild -XDP - - - task: PowerShell@2 - displayName: Package Nuget - inputs: - pwsh: false - filePath: scripts/package-nuget.ps1 - ${{ if eq(parameters.release, false) }}: - arguments: -Tls openssl -XDP - ${{ if eq(parameters.release, true) }}: - arguments: -Tls openssl -ReleaseBuild -XDP - - task: onebranch.pipeline.signing@1 inputs: command: 'sign' diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 86b8c87533..deae6375ae 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -35,7 +35,6 @@ jobs: os: ${{ matrix.vec.os }} arch: ${{ matrix.vec.arch }} tls: ${{ matrix.vec.tls }} - xdp: ${{ matrix.vec.xdp }} sanitize: ${{ matrix.vec.sanitize }} test: ${{ matrix.vec.test }} @@ -62,7 +61,7 @@ jobs: name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.sanitize }}${{ matrix.vec.test }} path: artifacts - name: Prepare Machine - run: scripts/prepare-machine.ps1 -Tls ${{ matrix.vec.tls }} -ForTest ${{ matrix.vec.xdp }} -InstallCodeCoverage + run: scripts/prepare-machine.ps1 -Tls ${{ matrix.vec.tls }} -ForTest -InstallCodeCoverage shell: pwsh - name: Install ETW Manifest shell: pwsh diff --git a/scripts/package-nuget.ps1 b/scripts/package-nuget.ps1 index ac39ea6e13..dfa4202031 100644 --- a/scripts/package-nuget.ps1 +++ b/scripts/package-nuget.ps1 @@ -22,9 +22,6 @@ param ( [Parameter(Mandatory = $false)] [switch]$UWP = $false, - [Parameter(Mandatory = $false)] - [switch]$XDP = $false, - [Parameter(Mandatory = $false)] [switch]$ReleaseBuild = $false, @@ -87,10 +84,7 @@ if ((Test-Path $PackagingDir)) { # Arm is ignored, as there are no shipping arm devices $Architectures = "x64","x86","arm64" -if ($XDP) { - # XDP only supports x64 - $Architectures = "x64" -} elseif ($Tls -ne "schannel") { +if ($Tls -ne "schannel") { # OpenSSL doesn't support arm64 currently $Architectures = "x64","x86" } @@ -142,9 +136,7 @@ $NugetSourceFolder = Join-Path $RootDir "src/distribution" if ($UWP) { $PackageName = "Microsoft.Native.Quic.MsQuic.UWP.$Tls" } else { - if ($XDP) { - Copy-Item -Path (Join-Path $PSScriptRoot xdp.json) -Destination (Join-Path $PackagingDir xdp-temp.json) - } + Copy-Item -Path (Join-Path $PSScriptRoot xdp.json) -Destination $PackagingDir $PackageName = "Microsoft.Native.Quic.MsQuic.$Tls" } diff --git a/src/bin/winuser/pgo_x64/msquic.schannel.xdp.pgd b/src/bin/winuser/pgo_x64/msquic.schannel.xdp.pgd deleted file mode 100644 index 360a1f2650d089c0994a081afb5709d26e57ca5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2084864 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

EJ7&4vFr4GSMrGCjOLbX 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 z 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⁢jR{1$V{DOa+9EyAl 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

LT*5XCBIMm!5Gz((gUG4#&H8dFCQk;lv0j z@t6~^t7qXzYdUtRbj;7hBTwjHC2UFt*c)gFZLS5MsAn7&0j$k1N{b^5N<7m@zN~Z7 zx|pDm(0Z4zdJV_~`7;Vjm`9*llRmCoA2x+h=2YxlGBA-MOP1V94nr#?V}_M@xQqj& zY`<*pLc)kc4q2u;zUP2;kF`z_DWuI^mV_OPa?X?$i=LffUez=2#5+)yE-g5vy*Fl{ zp!}EZ+~k>j@`_ypG;+LOg{3YbIGA1C%tT6*gIKB8DMui3oGE+mvgXR{+>yvy3=Y!5 zha>gN+7`m8!jQ3ZgWIfWNJ|pK+MG zlrytS;NV-C3`;rhF8`FX%a`-d&X2MqQ{!^kchg))>T*Kz2Y0=@ZvBl3ewm&!?WMLh z`7&A0IPg%m12n5f9AtwZ^sy=-X>&Ty{W8N!SmMT_^k~Mx>Akj~-c`zLzw76mNA`>t zvu6#vJZy=+idE<5S!ZN%qJEZXuA%a* z8Z&gPH_x*6>6!d6G?bgLEGvE|Y5ouS5Ql2P%-o=UnVy~uVKem4%p75v9=}u6>Nsv! zq-W;N_&o2{oXP7~d>k}UxYHVQI!lDb-=V;@=1dT6J8qUPX5G%vFEwv~0J*j_O(0azzr*w5!tC3i5pl zwR@LxXVeQ~_{%(SdT%^)kgsw`3$DyLSC%LKc7$m+0jf)2#;PS16Y-S87J;d5k7_#& zCTJimxq)qVt3znDOPKCjp&4M?TUt6Ts7lM>Fjk;E6n#c)VDqb{v>|9dHirML*}edpv1TVgRp_v zNZKBk!QXUY8}UP0$PH`AUU?~x!SMIW1nePU*(+bJ_-^x8DnD}{`#2!WX1UZWF|lo| z6ZE&0h`za^bgiCoX(^j4O4o_^-WQh8Q`L7)`l<7ss~OgVMh<~uGaGyJRo_tiyBO>z zVW~eGrQgm4OMHfQwKE5-BVc>t=stIR>1~IGP$dZS`$Cu2Lge~QLeG*_YGOjRjdm*D z2IUo6iWOa?w*=Q!=e$gsZqBw9dQKS{ZHv?Dzy)m6T~Rl?^>ndTle!JXPT9>Cn5B!A zre48_E!v}!-&R>A=R&;8g>OPo-K8(m_pD@gy>j-N0&;wTF#A1Wymm6!yBvrh;>$KP z9?eFhcJzgH@(d}|{qZT)JGT%-p8|+orOr3Qs0TR0sP)Y-^cY7Nb)gY9>^_LYsPm1m zXltRHbQ5&#JKCr8I{aoIGwlO)j#Be|Hgw>S0}dMDM)TWk+XwfLsOS5s=2ABE%W3wP zUZvhZ3wgBlvtQlsp#D;?S>x7>k3Xy1a}83q|I|F*Ufs^YCDwT+)+KtzQH!bVgk=p` zCV9KPA+)dYEi&{OZ|q^GC$CPb*YDB`utQC#$4g}+z(bjl2mYsMg-rIlR}v(LQe$U+ z9L%OhKc~D%xuIRPbs-jCn%x+07u4-x{jyYRi7H1eCIVr(s*Kj(HjLKat96LCRX^v^ z6*RCngY8c4#b6mj7GarJw%FbBr>?(TBURb^-mK@;En4q;g8|g~oAk?AS+BvcMSArp zTN@`0J#({(^aq zLYv>Op}jn3t~Zigj$YARU4&(>H%o8*#VMih(0&xW%cCFcUg19HTh!*b_kbQTVwRZi z)H9A=5$$F(>@EW22%{%;k5)u^)lrvr+L}@x3VZY(>6mR6@z}W{ao?+FT%Rs_R)pny zDyydkMtfH5VbLP0!s*{oW2x2nvA!|;6kU480qJF}3F(qw{ry^P&&JCieIhLFF}$%Z z^0kreG4c2w(7NY7Gdb{e47LlG_Kat2UA@z`SBy9JhM;cXuu0fAR@IURF6|k%Y@)8V ze$KZm?Aw8oZ}zX@0_$3DjZ%WG&c$E!*86w6hps4Lso))9##~!6tQc0V?)CV4Tl^dQ zeuu>56&438*9==kfE?Bc+t&QrJS`vuW7`Nz&M%SF%=u;FVBM2BsvJ{s$D9Xmv$|=5CGXbS)y@6V;~( zrt3Z}8&(a;J|(`y)Q!r6U%ifz_Z&NHB_`z@eG7t=iHqO4S~r;^f9plz55tzC6GQsh z{bCJZG^^5|Vb>D3)95{6m6V6GcBJ0(?ODEE5XPGF??6UiW5*Jw@Q6j{SP}8afgD?+ zehwZ=fnReU?97##0VRZ06jf#S$O}}}`s?})W@-~SYMg!-!k-nzwsRgS;SIzkAU9Ty zn;-r@jr~&W%tP+$60m!GxZ&g>R1&T)a7Xz>og+u*LX5`f;eT}srSei#e@|1CJ)s*awf`jiI>oV5g#n!e1Dk#fz>2xzCUs5dCniS zPPnXx?4I0A$@jQUNsP8Ejp^}M8_-gJOER`@l0*Gf-uB16=c*qARk*Cb)FjHgK%>xU z$tg##N^WY1!N|6$@mDO=gsiiD3df<3)UW2qPS$7i7tRspGJB312@u9ChsSTJXN^&t z;jE!(JUx>7yMp?C59>$WA}q0I{UjYbuoycrm+Rf(b>$=42k;$wL-=UGHQ^EbD*>4r|_mf?Go8VHtUH*Q+CzQJa z4wiQYJhl9RAo}@df-_a-&kG(b4Y*&B{YSqb*sH$p7Xuzzz9h)|@0SBEE`K3-mik&> z7d%`2sBZ|ePv&Pu2j69VF5qO~5c?7Qxt9z5cz*eoAmzdFPQkst+CVGXe7`Df84W*B zA%q_!9U%N5(MR|Jk~;N^v#_X3iI4t>?%TPa{6ytI{E5P&_-)=*6km{?vDi0u2K7^L zCbg?QU2T!-i=g+DXFq(k=E|?$*ZbSz-*lz_n}E~HLjh-$zZX1M>AoX4U-7>iaA|p1 zuut~+2ZASvFFy+ZCFNfQY4;xyL?8ZEd||BR?*!YD=syHJwEUwW`|*A+;3?(%g2!q+ z;fDcNmwysGQI_|g1D;*}MR1Kq6aOvX1?Arb&(@gWj{{y>{zLFQy}SP;;1%Vkg7E)8 z1MXgaCYby@K>UHfJt`OV|74Ye+IXs3G3p!VMNqe}W$?Y4ac$FEh3{A@_`X2pfzJ!{ zHVmJasouipWwHh;e*09_E%XPW>v-#lFGl4{tW;4JW;&-u#ycI8+j1^TmB7|6N4LNsPS;?j|Z8 z6+dqjM`H6xl7gjA$&dH*W@=X?fly85@3x4k} z4xs1kUyh!$e>wa=NRkvBI!N{cIUJ|*z~|#s9{7BiI6-^Co*iO`{(F&&;QZ)uW&YK; z-4LTR;Keamsg^;4Y1SXUPs8B6x`GLT|uT z^WkJ+a^iTFt zqa`b7cQlH;TD$_TiuUgq@tfZH6ZC~E?=er1{_)PzX>U5uo#6Zf>hDx(H1&3d+Cz~k zcvRHiZs|GpxLf@~+P(SWv)Wm~v!dODue5uM)Spu?xP7$rPQ7Ts3q(8nV{cK(fLF+d zvcLQFDk1Py;!s_0_fRR(2~O$5zV5GB*jL^mM4w>vhZOGq7xn_Xaf~=BJ0|G!V`z7% z_l%stKg!P=I{Re6Kk~rw9>@Le75{0!v5)L~LH_K^PyUBX58!v(c-w822D`UZ8V}!= z%AUfv^JrfKs(lr_Q2K}cyHNT^yLOuRihVszD_OCxM~ZIbcVw9#@=Lm=C>C_E*DiE$ zz6f+MhD5pMiZ9AfkbUj2H*?8PamV&IY1hb~c8&aX)@bl!vSP!}%&%JOC%feKW4!W% zuj54(_Ly^~u(Py>%D+vz^~#W4KrW=9;>R-uyG7SVLC%TUB)CNNd2_&la=GC7(wFB7 z(q1e55bgE(sxQd<=>hjF&k*FCr40e6lnVrBs~i^vJhWUKun#yiTlL}+Juj3TE)BT2 zJWG&$@CAn!sg_-)=O?S4KRe)x@*Kf)RA06PTwSgZyh8f=ynq*!D+Qmc`Yky0T-B%N z>-kF6Z^5A}H6Q!}J-<-;{K9}bVMZ|d*m<5x?QgkM=e~%~xd+?0kT3DB7GKz3{zZc9 z2{A0Vuk?N-o+p=U;<>wAE7&7>zBu55vQ?0Bz9it1@>0Q7vR^L~JWKZBI>8~;%a;qD zuiq=;d0ly>AbYUCT96aQZxDRE?8CT955&yI=KK^-}OGQH&j1A9n6&@ee!qccg(NOk}GTszXg&nByv{#60gn32WD)Go89gIYr zP)-~Z%6NrC8Q*xa`XpEu#(o%CI7b#7i<7u$MSKD^_?zQajH#TfpPk38z=T~gEzHs3 zJj2kO&&zTUm)zKk+*EiB%Bga!Y9E!y7}fU-4Kw|=g!_%sODGsQwcTa#ND$bta<662(l@zJ>B4d~XmqwVr+EtIiN zIp^tfr=G|g&K<8GIpGEj zsA-`QoCNArj9uZ}Di4a^p2tw#)w?|FGL!q*vOUWxRXR)MWZ5X=uq0LryXhjAGZj0> z&DN4cj&^p9{;!X6)B3UxM-vC9aT^D5&r+TCPS0Jc^^>bz+~L8c>OH$BHD2|9YBdl4 zi?iIE%V|d`aO4VCs`oOyM1dU;DKx|2B#toTK3Tar=T>l&x9qwuFv^jo4n`>j6wpU{ z2=g48w7+yI`3kRCd2JlTWrxU=Th<4tK-z@m3J~5)Jx|*s>O4Y5v z*Q;};L-n%a+CklJz5!z>-~*|o~0BgTpP zSr%Ti$J%1MZOwi$ww|#ai(IMm56A2hba}RxSlCAKIYNlXT%nzD6UXVnv8N;%xQA25 zgkeS2VwMGsA@e76vg#E3IiU-*tF}C}sJ1*<68q8XwO@~@`ZR`JDR(bjAyPCId6F~u zS@T|L>aexZZ~f3PQKev13YR)X9Lj1+q2xzVYAE^fX!&`U%azTkNu=aWeO*w5Mzh0J zmXXkXWxGuSy+}Lgq}14M+u(Y%KkXD@&`W91Bm6u4z*Emj!x1}l5&dh< z82xokL=Fp)so)zeaPro&Y(+i9kDb@dTkH$kpdQGubz~Po_=yba-P>5{%o}O$O3SJq zQUNLfMep!LFf&O;hEjGYO&j9*IV$?zYAC8<+V8AX|BjN zJ+^1?d~b0TdT}WitQGO5NsDteqHB@sCRY32@IWez@566gw?R+>Cs;#~n%Yx^Q6}Qo zb8K~OfD-hLFt;Y`kDTji32pAI3%y|5)_NjzA9>X$u+($jSyu?lmC@T;Pei4 zoVp&XHs~3C)hkl%Mj^@vi*OBTm-=Y+ZacTKBTFk#irGh?F5z}}PAAg|NpOC~#O z2K?NH)>Uadz7I~$_{>#=XfF;efRYEJl|}<+jA|hBxNe7Nv&NMNDQ=|TOESA{=_cPR zMHz#87!N^!d@&&*Bq_NpM z!q#tzJZN(WMI(s~KPtM0o!a?Yf9{9SgwW9W(UF+*WmNwGw4h^EWD#SaOa_Fw+Jq@`<{vLr-=L|b9(C__^xvkXuO z#ggl(xC1yN>`l!l>z93vN2-xtR_^o2z*G^^EJ$ zMZIt9M;fR{cX>{lmxMok0oQrfKUHDF%c?L$gz7ZHM*D>bjqv79>4SR45eB0Ob5BRB zX2kRRgL{NFR*4U<)H9AohAv(sOm8c8G>wEeSAni+gdtnPGPkvorgN(z?F2hH@IT0b z&i#QppZ^E5{y%{_%bRdVL}k3~!5yXliOvs0bd93L9b@2>;=|hp^eHHwNLyRHe_ut|0v)^sFBmAB9 zU`O$H6uu*To2w3T#_PnF{t3VIHJ4+?Uyg-;^?;x0Iehal{eAoD=y2Rw!NmL8zqmJV z1HP4x+ppldp7J7hU^?P{cl~{1d=^!myfL2t3lF<}#N~a;!P};@lY}QPcf61PmD6>G z?|k0Lf&V{o;KR4wvCZP{@+SMgW8gUv{+u5y2s@DWb(@K{;rY7h3nSf6_cH8x!7C%y z5f$9+xER_hH&t-oik@q${4cNID=YtmtE%UO&#ay&R4_@&eO1@yD^(Sc&j~aBZwu32 zC+x(R{=X{hOrQMU9-Oq-xXvVFo-eEH{zbo{{U4K94@aG2{9iiT@F!J-gq`s}#iCca z&sNY+zq|kW)}FAlBztx6pD~BOSG)Y53ujj^_nj&H{OrYZwi$kU^#cB-GsmV&{Yl?H zss=NbTu|Zr2j_RrDci=I>3&mq=Y>Z`(gUAq$cPLs;nj!wnK|#~xQEA<8S^+?vlH%G z_{(xhF|yA0EQEP{uuE%Xbe3|AaS&R6Ey_2AP*Dos{TLJH zmM{aH&-IC5J@TKWtGVCKCOzZk=$fsIu*{VmBAhFs%pp66R^$M-`V6fyyhYDAI;?Yb zah|N>bMAv3Ve_gmk12L(9i+bDjIt5h+*4=7i8Pn)vGV4gI$IlIh=j1jc%8<;jgge= z^sDF?zP?JuC>WheR#SKdNSD@IPAYv2_r(1EdaXBc$at;1+_OwWT=88s28I#9R$MvP z;T0FGBFQonjzgB%lOL@W*jnIj-O!%7@1rkz4uB=uZUSAD*9t) zGDgf8G>n-EuY54?s6p$L>-`Z*F7qXoCKkSmTM}5bn!buhDVP(pon{0R=QRn8TM}m8 z`<%Y!oEKx2Jg1C;&GG{WhP-)dTeqHZywEUKNtiKv@2$Iot%Nd)iL7ur_6kNK5C&gy zXdR9)#wrO*el8+Fjxfe53CkUcmJpya5!SDAPmDDm^e=i;+nlrQ4>1_6%vmI6=Djx| zysmc-tM`V01M4MSpQaAtBO?z>h|rCauTN7)5dLb5PFZmfOVL4*X3{!osc&<2A z#No(JH`ESlPrY)g-eIL4)vr&kLi>?!bFWBbfTNt0w;7iDmbIp?-OdMp#`tr-rAN;= zbe^;A)&+8E3Nulz0ZvH__JGOt6RB%xHQ(WR9~J6Hb2cEgj`C9po3jCYPv?D92xSJi znICxDqRDLqU*+LE`?^Ft_nsU%#-Gy4+LHBeAX3%i`mRaD_Pzv`7IlSr`zqD&@+wu1 z>(}$GUQx$sff;vY6&KBhdCjm@jWFs5^8-mK@%eH%C9$@Oiv-t5PXCM9s6tnECuW@O*0!OoD-$W!i z+SG1c?7Ww}-AsVoY+bW-c^AHx?phI_uV+2$PK^2l)Vj4s`{wAPPe83iDp#q_ZwY%% zDBlj}%<6S|#zD)7+6?b!vQ@fk+d`kfCv_SV`!w0EkbbYKOn(6m9 zTg0Wbja2{@4qJ1iLRi{ZTPKI%#~1bK2X3&Z-C!=!oNNWezfqH;O(aEH48qdR&`LZS ztplOd)TF>Iu_++OnVKcbQe!LX^)uF1c|}gxlIB{RP@yUeo59z0NiBG=5N-Xem(~q= zjn4Y@sE1~n>UQ%m^;>Cq;3@*H*8q6lj}U4-)`0b3+1u(>&MSi;Lisk`&T=CccxSn> z`qs{+wmpaPdWP0>rchq5PJt#nI10{Ym}O_nO}u6poWNn#$-5awNuVrcSg*9H=WUUz zY4yBqW1cnIcQ9aw;QCB)JZpVQ2yc&OUbQ83%PxfB5M7yjl+A zh{azdDJ6B-_NL5w^^7Af@?2U@6qWGW>sk7Ae`CzwFOr_D!5~GRoLy|^Q_!2|9QRit z=m|;B)Gyx_mPjt$(KEryuhu%}Y^4VymA}#+kRQlT)hXG}qUJvmm7B8E`ylz-aIhsCOm*$&qH;Luax-`Rj92D9{IA8y#Tx-jNE_PZTxC+-S$8n;_nblgRMCj}rI!TSXHDK3FtsG%%2ke+;~jqb)&GyXz;TyW zFvmfTubw|SVeI$|b>w@8{p$Gtw`1n`PQuJTpF839!qY$D_R{^T;Tdlyd~7+84bMybSBL3?Pc?wLmbjMe>(ZUjv13=>R0{vQJR0beX8d> zj!3&^|4%G5d_x){KbCLZZ!1_mmIJbjSb2Q9BmSGe;P|b>vA(4|w~BYN4Sf07-;JfO z3jPJ~p)VTGyDIn#5gucY?|jMmeXD|RtKjMiW-${ER^{0qOnxVsH;rXu>9jwc&zbIj z8veHV?kIg{_>P`C%lqrb?@Yh*c_#;c6%IWAj$5}G?Zm3xc(|(g@2+Cq^r6eg#Lx5n z(sM)Axjx~ZvGMNB2o=2ek-2d<^i#u*;2l1G;E#;|P46|lxk{06hX{5?*vWzaLmb%W z2V?sOub;6bsC&Zqx_>E^Fa2)^4}RGV?L`mzJ!ioe?0?yhjQ8tba6Nn5Jo|U{H#hvt z*!-V*y7ByE?b!FGx2|=(sb`Ok|4WJdEdm#I{qN+nBN7k0$9y>YX=C}3=?*3%lVJrH9YTO!#R~+?7G_W?W*9MYJUI1$)4Yz^h3x0_bS~kf1i|%g{|%Qzv7Ey z?eI4$dNTFLI`lLBcLZrZ{B@A$NN67FuW0IV3t5LbEo;0e4mG&TokrtDp}L zX2u`4H#b&~Qm{*ax#u}oy7J92>U<+$qyzezW7LeH)nB?y)fi7T+c;b`kTG_`EbBut z69L8b7a{eSDSmEBy?^k*rJW;Y!;G%savZ9yu%wqUC;ahi5lP&{k2xhcAvc_3sEju; z4#n(HrswqsfBZ5%evGbG>2+_%SQ%qbjX9+Fl7&#h7^5Q0qobUKlV4i*i7J1iM!Z8b z>Yd-_EYmX%Zt>mB!ywU$jR|FCjDb#xr_t3N!vh4%G0MrS;fea19-bm=aNvKM_71V1 zzjxzn`FuSaB&ytgx)|emvSy(?-%#%(OBky+tRakoo;7+xku`)E=almm$2?o3XDC3# zt5p6q!E)qwLAj*Tt}gAQ^SJ6OXYE;6edSCF_E5Dvm_xQ>MhpgAp*0`gnU2*flPWFR zC>>lEZ^oM>5$}gUiWlgYm^K?x%pXI}3-$BYZThlJvT`bht6x-xt8#ec79Bw+C>ygj zIk$%XxW5%h@WMqPh~>-|0CtYd*1{g6~~(G#*XH zF=~tzXhk(8uX<&KOYhU|oNv};F!KeU^F>>mvAXq)BNlreJRHa*}5DV)uPjxhNp$6jL8>?N+NBpR*#s+tqyCwAN z8HcUFmN4_u#D+iXS2Fh{dd8&Fq;69qY@;;pv#_JIb#2zqxwIlBJY@HhtO3jQj2_O>|DbB;dPs>m@+W8) zBIn%|DX)+nLv!FPv7b9S{$;Cx7NhILp?>p>bDFdOwh+y4RDTpUFlv0%5b~U;Iud)o z>3#qt!UAsQ$K1NGU%=X^i0nJzJ9dxrSZI%8`;M;_eaD`)cOiOd?H-k1zc4ymNz_E3gZ@^zWlXqIPd zl%5=p)~{2Khz}nL^K2@+!Ava=1g8jX?Cm_NA=#gJyd-L?+$G)Gr&?jKN%h25mv~Qz z^$fZZKe4rx8EPy=cFcq(W|>-LByGiQ-t4d6=@`;467O91 ztqck%Eum1F+{qbkW6V~nRc?#?pt7?`DJ>iga}Ky(m$|V!O`5` zD2R|B^_z0lVfBiO=3XP{9lZoQ%N3lYw_Hbed>VGSYnw~o&XTjv&~8i~wYIye7J6r; z)HZVio4g@<@YJwdgJ5gt{;KEB!=-S1YFZafn|KechpE02i?8~-D&9fy;aU-vdc;m) zHwFXY!4dk|)olv>9G7n&wVTe>_045LZ!H%j$sW8Z_fF|~-5)Y;t}ROGrM?p8c9k~| ztuT$Ylu+KsQd?3_Is4W-P;fHz__8_b1flHE;n?-60Az@~SUH+?g7vaqkwNG&>LsP^ zp$9lA-Z-&6@PR%Y9H1s53Atf&#I=U}nsw29vOGHLDY@aY+|9Z`?+$-Wt3{IK>&teJwlp4c$E~2>VyZ`|v0oQ)OEz$3fB=B<7NqA#9oODLyp-=>4lHpST|7 z6CLfOi=BLk&;GxTAFFbP*uPa=Z?g|P>DVESbh9eH%&PcuxcUmxiuU1ZcO;c|;$^>f zB--zIRjbDSm8Opqox#6<#qasjK=993dEomovX9_BM*Wl~-n5K<&*P2AeBEFCK|A*s zzt9e(4}N+9E_du_tMYr_*`zafzG3G>x$1)s zvC19(O{vN=r7BOm;!C^Y3u|Fir`zm~qIZ(^QoS_@4lUKrWYXAn(vvXP&(AC2GsH?* z^69I})mN3PPZX`L;QC0lCen^mIn0r|cO+zB9x}@;z7_B)@}&LHrc- zexnDQukE?g6!0U5dcSJM z8sInlll=5fhb@KwuD8{`?QQn8N1p6!k30_&-{9v#;#*UGnA>g6!mo9i0aMrIY)j&n4;LTnhM)J|Wiwg%7#zt$)Q4 ze(w*R@ekHB_wyweh*tl0@rhB8LDf%Q!%nX3 z<;jvKda+V`rk<^gUBcMEIri@r^!~jkNP^IJg5DpX?*!Lh!F*?om7KTGVyUwg`}r%H zwW-wYU8lZMmQRaOoQVBr0jHGD3bOC@=LDySBY!S9RW{zbs0JBYD#!|5L#!D(~k5&Mo%~PF215QozOK%Yrk-x4#T{Qh7kITlV9x z0-jMG6r7{?+P@B{b94lcke+=b;JM|Sg2$+Se=Fbx<=cXk>u*E{J@~&1IHml(ApM2! z2y)KLKM3xxa{Z&=LE`&&1rHX#zaQb<<(~xS$`br@z?J1+1?S10{9C|v{8PcBb?(8>;(1fqMT)nO9aIJ5{EUFTrAu&$&K=lQ@SO7YvYR0L8$TqvvFCp) zc#tgP!vT*f-xEAo{P{t^lgbYT=c+#cOTg9TM}qV8uKw=<&o4g~T%!Hv9|`!X@}Ghy z>pQ#u3V2g_RPdB?Tlsmw+sl6ouF?5PQUbv{N=xv(@}4pw;CstNLHN15=wv_ZNrLFd zo`UGdUV`Yylz`H2LH56%E{LA&8*pjaPw)WobN_(L%K?JjD#vU=^yP8N2YuN?5PjJ@ z;M_7<5PjJv;Nmh(5Pg{u@T4+RaGvzzz<_6$g9HziJ{%nI0yg88erzn=0WT`E1eZxJ za{^vodITvSj`33L17GAwjFRsnl@$9(i@}i;?3@pbb2f(9e+|3CKGoPA`X|(9cF|O^ z1P@Y7_H*ySJ_ydmK8U`4?1La9H}n&hDJAfD@sj=B2cz9wsCuRP+^+hGq9ONj^v;p& zg^68A`gz|LJ)$wbnG-*!V5iy>L=X0Jg6P41PICJt80QMLGyP1($DR$y9;uvaU7}y0 zr*6uprx{^KPggxr`SfK;^pnIR_e-aXKjb$c&XLc6^a8%kRw>}?Y}sG+F_u&M={&Gz@Nl>Rylv4!{ zl0K{uJWH1MG(q%hp5%;v9U?eY80QC^Sq>B2MDSVL7nNfL=WE3DxPX_IWr9cQ3!W1LUR?$Rk5_#>Ip9s@@q(*$ zp4ni)H0~OXFtL77o|O3d_=y;9lj%X_)fahRes)o&I~xYJW+6( zIQ1mK8RGL<0sG2R1l!{C*@AmWkJbyqx0T8tzMUbsk5a4(IJ2x4oF=|KIpEx~Mv$@8 zrv^N#tQG8$9-kBNl=3veUe)(?0aurE1&^1U&I@=pFB`H``T{uM`Q_7*iv#wTO9Cz_n*@(ic`gk&P@W~YOd9bV!2ylGKUeTnwb;)K zxS?DrxK8@?BEf^Czt;#-pI$1+c@P(h9_-#T1&>mDep$d3WwYQhYOgL2ct&})Am@Q? z33zt7Lhw}S!}9~`JO#m3vOg~jczL->@M)T36C8S)M!v7s^N_yf8V-0(84;YV5?&i{ zL3y#@2HE$m0bf;KA_$*eCOT=~UJ-C+d8Huz`RfDrlvfGPRE@b&uv>h2ZNPzYv*2va zX51opi1>4>;6m}|4T49CKW_}UvD_}mIWKnx>@IH?zsbzVaA)h>1)$dVyKUC{KhWOTfrozm5vnN}7r!OtL{W>+c-_m2 z#Ms(0m0{bXY%TGuIsb7b1Sqynd=EJ@Drao)hU>6VE+9J`nucb4cvLf*r9%cdV!>;| zOd)#$H&2%Dk5NeC9j#sF{eCoFS}?v^O~A;{Ls1^a+$?+s3$DS!fI-WKL*f59{H9Dit%~ zXgQ#;IOi26%9<~lqV z*FnBxgdECKIimiN3+U!6b?1eDp<3U<>#InMjDAXYjDApo>2_ec2klnxb>LmY4YiMS z_?`!sS?)I9#xlLh24_X&8RV>qT+1=nw1LVnDVn3!iQ5X6LTS{o8@mVPd$tVz3Jv1h)@xx)BF%Oh0C z>%t-w0S42#%E8lIoN);HBJsmd+F+MO`OWN-9Ey6Q>46KH@WbsqWT(`aG{@*5&GMUg zsJu|Wplg}j7(@#dQsS0_P#&-WJh;dazYDc{%-!D8Q^(Xbe#o5j-re%DIwRk5pOc62 z`b1he`#jbaL;k62ddO8YS>-S)Ewd+N@Ici^l$x0<|8kgAS;q~Tf_e;c&PvlZ(_OTZ zL-hXUt%Nb88)DO1SPydz;A4z#r8nHtv)slK>G$TCP|B$(ot8c=%g?)jBOQ|ey|We} z96Ju(IQJ+G6cLsc$K9=uuP~{EkA#YPBrgo9P#$okM859l(oG07mFE(5>!l zZF;HTFR;g?iX1rGBy+AAxlfWmxz*>Mq$UzUy5Qg}qq~R~HB7xw_G0AMjJ^v)oDR@`tVxhaUz=9kAytbFkSBLIx>Dlhrt#Ba{8{a}F`aD8tm+4y}Zi zB5bTv_9~Z~OeVk<-~KoI)b_zJV7^D5aq7j(8dk~WAJSc(%}*-jDGE#F#vo-=Wa~4O*k81F>bG_Uw*CGB z(EDEM)Z$^s-kLJ^`kN1??9QYEK) z53GmB|8VsflmSD##y~lK%&$UCq>q1ja}jycm$=zg3CEe{0v{!edOXB*f@6?&{Arn# z0h2jn5Wj|6XS4>wQ`6oW2wCrgtxExQ^vgF}-BZ?jG+DoeL|HlMs7#+5aXfuO&~}|N z9~F>xefFW>H{|EW2q6=FJDL@}Jx`-PuPJ}OW)v7D?IMCqypx=+d9&a;dLMso$_#nv zA+Smh7uYR)tz|||^ELX3xAr~)$)JDP>F+HOZT>j@JpEczqrs_taG@JH>B{%YfL)|D znV^cCN%!&kMM`t!#?W#Q|0kuP5#jM?hinetdh0=Zqh_9_y|FHbwN`&tdRCSj-_QHE zTIqxHm;nbZt-d;4X=86VLz_dNZ_tl=N1E_^vwlJEp!s`Ep~GujJ+{Qp<+j75{LSet zIev$6|Ejbl<6ZWlANq*HSaWxK^l!288kF!>?qed~yK#qMkQCm>-0Vs}GGpw<>8r;^ z%ppe&0_(96CqlUlre>rKcaTGHQ8PX^@){tFl4Sr?VXnipr z0vA%yFS?u>u=Pz>sTpmEwm}V*xIwuw(U!_nxEk4?@luT77Z6y6-eA7+zL$n=Z*TQ( zTz1;KX$Vm7Q|!&e+Y5e+S~`bxW@tp>bEQ=RyI6A7W!()jMsF7U0E_;O*1be-%p}<^ z+44-UrRv{&WCM1tbhD@8?F^|#dhugFBGQWSFm*el=aIdM?H$o~G`cQ&{{NaYtK^wnw|6-4`SER@+0KXlEr3zJ>e| zx15*nHXQw;znhe}Xj$NBb^f+#5t`m=>e=FQKP)W~cjk|kg;?-{)(0+H7&|dtrol39 zh|)%@p{3FKzPxy9HP}kpXK8ZLU*VSjy4Rop68DwX<#H+a(w8L|!tNNDY7MQB?y%Q98y^9%5AO&C3BSQRM`_WD;n=*Ye2{#ox}3=y~ESwI!SLg(O%aQ zM22=WU3QMNLwwH7I7uEX*yzr)!eJO+thueRm-@MyUPGy@m;Z6t^c zY0r>7OY>!3=gzfF8a;Y`q%&V%IK`I`#MmsC>qEHFr+H(q7mvG2l=}#lmN-dVcHTP{ z%|Z_y@<4j!cy5c_m|3#N$v#29XnkANu6>6y*eB^1wYp7i%xu}?Wx++P?Hb9t-Ua(q z$&2UhdUv$^&_MVxf9!m9x4evFBQEgehgC}oX%czi?s{^iFv zp`8ZBZ4B=Myg#{iLhqKNnMpYWbDtr{^W}$W$f9ve+yc3wyP<#AxUtH^2*nK=bdjXQ zTRpm$H`HkLOL8c3Mh_n)4HvB*KSn6t>PhD~X(88QK3};^Qsk|^AuOY-X!XQhsFb|b zBa7u22`ncJjuONycVHS84P2i-m=Ox6zHh!EJ*@kP(lU<#5k#3m@l% zjR^b~a6~?lZ+ntK_g^aiNGHydy7S_xrd(FM=qnX@U;AUmB`N*tD-O+}zl)!0=(J&{ zhY$aU3J-rLqq~%D+v+gxQ3-g%xBPsS&;7XD=y^}3;AuDqUa%*cKRPe6bo|x80Xjf_jkBYo4Stn{kD9b z*SGTdVsZXkzz56k8D~2GzVif!(_i86mG@k3d=2>G(;gb1U-1E3o=9JAgWt&R2cze| z8Xxui@8>%ux3|f`gV7i91BRRge6aWtF5rWOe;EHT2mUKKpw(i{#ra&_6cNBaL|4Q< zSJxpwRweLbKLoQ{%$d_Kurn(lU09w$%_jljPJ%)GI3@rBjpV%%;RF<#aP*+G_UOQ`1Sr|y0_V_*(p{@dNrROvq;38 zE1G%2Z_5>bg_O%XiU^G`I09)iGp(K=yr%Us(b~HwAp1WYio|A z&b}NsUMBJ&Jug%2t@iW@N8apK8n)F?N>`q13I7Pe`3U)hN?S1_4me8e`!>jjHQgu+ zHH(R|u=C4t9uxoa8yILW_nKKzOIb0E?Nd(fkZgC1TE|f zFa4;csanA*{V3~MIv*>`TeDAY3=&Qo;Qp@Iu`vfkt_601ud{LzlC$bu>U`HVH-Wlw ziGZ8`@9P**7p_@-Eeh@ydofy)GF|?Z$#)>=LB{37&(lE`zy;OtwpdTLmP{P-f_hJf z(42r1>#She$>1FI^+Y&C{Zig?mjiyq4p+4yH`FLK4bHU!mnfeD8<-@O7$F{XKd09H}1zfDF&K4c@PK{dY*SZpgL%oBG-OK%QV`xQu zZM2LDew8Ug;fi(PdVQnpKTzFeZi5yB+I$TeT<9jd9n|z5fAyZcdYAuHOKl`>p`}>a zIw*Lao1Dy`e+dP8!r`M>0g5?+9rlj)MmK~OONZlK3FuA8>fm^={b}8pS!M`>1d*b zFvw<`K5ROTerq(86xyok`p`!R=)+n&@lBxn;}<#vzdB4k!a&nO1Gvy&wT=W^?!Ps) zAU`;QPONeD?tT0sS4vQaacP&AX*e_>xX88Eo9MqoO*;{`)|==fbYZDydI_NX(5djG z)Jgc2Vf*C9kWOg8&=R%Y1Y7R@@tzY+4!zVAV=GEn>B;q_w*f*l^a~m5mm3p0klkgD z^O*Q|t4+nIZ_q;f`s{#wU9QK=ce+{*GKZP(!16{9_GqjSYpu|;K&{KA7X@>y_T~Bx zEzgsaVZFsKQoVcIK=v3RQYKmisg$R7E;fJHaA-hqQEN-2PrTiu|Ju*$L^A1}NtxU0 zaAQ0tZ%1&1v*Lm?v;xYCUr>Y}ZXkx%fFV6^!F7D{!@rD=AMU7`XmvB?Ps=EA(dxhv zuGsaNMyY~64#$`aF3Q!lZb(L~abd?j+d~w-kiT6xqqLHLkL80{-{~RY*%6rrzg9+EX(wxU*bs z*2j=w2i42R9>!I2W00>EDxsfhyt1W9==&GjsDOOAe|X>AzY}99Em8R?olD2C`jB25 zeXLQtSd0Lf#PZ%0Y|(o*L*GQ-U*qb& zzwCV}R~@GA#nDyhGH_8}Bfs1$_JOzFY3bY1WHqkt^YM$iM0?d?>iy`^=Yxw}M}E0S zpC75fH+D$ndT8k>lr&;h?w;zG8&mi4vFobdB}@27o4P6E4_NGamn^uD{#s9wGwmY! z#L!bH6ZDkt1VfumQTq>Rr(F=DKEtBkv5uaiH?O}m;&cetQ9hm_L@`s=3{BQ0|As7Y zLS;_)mtW&8ksE`4nIv1{mVyvZLVtm)%+JqmFT*Y~e@_P~1ouE)MK0`*b>1bNjN_(9 z0A1yyDKuKSKQ=TPB}aR)KQ{Vi{8p=t_-KlB`t6H#%Iv%vRx9y42-irL`Dlu^Q|`ti z6?9j*Hx z#_sKU3Oxih^_ahY8^JGX7@bsyaUCWv(>bFvF3LahZKTvz){!do{<7%n8dqy1{32I0 zQXR(oTY1^&35^6Uavk|L!Z+&C10V+&bi_!FK83pOY$be%sQOpE3XM>Q5@j{KO#|!v*E%_}kb(ngVG{y_yLLS&@?Bimv#g{J4 ze#_Ix25`N`RrUo9V+Q;pSK414rhZqUR%ofA`Na!7E#JoG%Z+Ks(*89yK)NPn!Z4t$vYcbqc{UVsP5)8)Z(C|6+xo1LLfn72^tx z+uckSprsYKrnk>ZeYU^n6ko@*_IsZw9OH-*w?b~(1-Ne6qRlr~fs&afJ5`o1=3HLw z^k(z#S9=c`UQJ*b`d`KfeD4x^Z4C&SqhyEG$~s_nL{C61_XXur=%h$D zvjlkKDRG+&RewhVE+nC^E$n6sTYpDG*pP%R(hV*Njc5A(h{!!kC#g) zHnvK;J%^&SwB)Dg&Y`ljS4)TZXpD58sr!BWhBxQMwbDR78oNO{!}UM)&;0sJ(pz;7 z;G=cU?%NE$JY6L?U*A8y!{DpaRzYZer_|Iq>5pxKhiHWSKEcDq&3`r6n?4|Tge2fv zg9GWqg1pcDn;>6MzDwzFp5eO%IbZrc1`ki~6+A?;xZU8?^nSs^Rm!Uko{+8)JVLVg zL4)(shYZe7*BLx7T`zc~Wa9>d7o?8}PExD-sKFr>c#7!r*0T zry%9~r06_A^>m{kGf|&0I5B1M&H z8n1uZ;5q3Q!CuY4+-mTWberIS+Uz$CUY@=sc!9pJzr*0`(su-(sPEywZ}4Bz4+MuK zyFWDe&UBaHQ_|JxM+UD+KNfs`x*`3<;78Kkf-g-srC%BRe7aZA+X8Z8ry593{M_5U zB|mDXLwieZ)$$u-R8EZo1pPeLgH%)M-5Z>>sGhuWuym+;ZNW+O(2Yo!dnROtI450x zO5eVu6+Fb!-B;<5?!HP#AsZ3zVC7HvgO$JNklfn6P($OCKlDVnGZde2Iz8FKJ<8@T z)6k>TPRelYbnKPs*elb)?OD(1-J<88H`S3O3bAAcsSL-jKlL$H{eebF4Nlb1h)D1_Mn8?nC)1%+Eq}prq@&W? zy$z~ozqf%Cqlm+8ouWsO^(`QprXFjuV+p!Bw*$KO5htO0AEgN0q;bCp;C$qLJG~tr zC^$#$be!O+YHx=LE>QcNU~pZUDEKJ7Id=)dzu$|`(ESI4oxq{JM7dbh*kANl26eu@ zV5jOwaHv!DvxnRVs9b_W2dG?o%6*{ZS8(V+$!~|;oTI#t!KrDqAm^CxXHfTW2p%a) z1&26iy;JTPqI9gm3(`RbbyJz}Gqkq&2)SpeCp=gX{db7L%hI8OXX>8I@djU%4mY?O zICQ4^ha==ZSLZ=LQt)xo14kNs9fgq`>K+#HVL$aZdkaoby^b+BD;*$sl*R;;44$1P z3(iWFc$%&{SG6&>xDNvUQIGlF6^&XZA~*6g$r#G_MW`R}_J4%x4>^6L%iYi;TXJXm zV5m#{MgEWk$=w@BNpjaZ$Dey5kc)h8M8k5i{5w^rv=h&ll@Y>m{yOr*dF{y0nA}ds znFu#LAyUe{OLy$#h)|ON7U_%QbFT%)sVzrX?poa=kgtq z%XdUB-$c;?JrmWQq33Y%o%%Ukqj=FHs=J^^a5C-M(iYIO&nC}QNb0vwAuFG!j>`-k(1CI5o-W8}kIB>4nix!(pp=2Kn;T`$wFsh>`zOFEqQ>-@C)b%y9C zoUeAj(GcgOQ=UiC&J3!Z2~JV}ie8!`y#PPC--Ysx&*kfqJ`-Q%do+Aa&^1K?r;7tK z1-VtCTkT_ta+_+fGfgu%CQUauF7+5ZBpoF zEa`zhgE~*$;2hx4Ea`z+atELCJt``jmD}VHzKs%H$e(s87@zhQ-<;oF#*cl(fA}#* ze1RWhLl~B&>7rNdQ2KGe z@}D7k$p0jbgEZG9cuvwQSR>yvqyAtzL(s6_;URg2(C@9Ocw~gO8w`B zg0D%hO^*|NU3z0$XmDe?Nbrps?_MnUv9v8+BKQ%FCm%2P>2ytcg5a&`!)dX>Yts`2 zw^ZLumkNHi8dI$hTv*+ho-25Lbzk~h!LL^1s^<%yCwV(V?cqE*Fre_FVSKXVQY4F$SS%N#Ns#Z)E~a8mU`!B1ABw7(%;g#^t@=@ZD& zD)EDH(2E4eN?!g>aE{vR8o@>4(<=p^r+Xk?E69C6FHt(2y!=wZhV)O<;LJ2E$UPpH z8$2#uA=oQ@BskQoe&}U#KUQ+|a)aljzZbkjU#q{u;6-Vz;IlL*`40x4oL(h(nd(7s z=rWDF*2(?cv^-sD@UpaC@IdMF7mKa~)!(cZe5T&-UTyG6={17UE~8(o-9By&eK$(l zOzm}u(SVNwJ5B%5kD&jE_m4_%vRcSS!SQN|Zx&>H`F}(QG(XsN|nx6 zxR_N!XQA8RF7pm^WLXm{{g_?lWK^f8!@`xfpzjbKqp=?2KDz}y=I?e&oB7!yU|*`A zr<~G_a$_XHNUz}{)=iQDpL+)TGR<3f-ahxt7=eBoKfXLbmts7O&mfb_uFgn@kBm*Y z3D>_xsEoI8cE@2tB;G~+dj#=P5>ubt7&FS`SMN)w2N?2j1_xm_#(%%AIEu z@RT7Gx*wcLum0Ja?(;if(e`fYwJ!X<)cIXUZV9d#=-^w8IhHRse=T9E)A(KN{cAEp z#ZWru#ORiMkOnd|n#ooRv9Hqi)9foN3nQ%YhNUmRFVAT1o$j|M;nhUUMBMJGI2+m=f>M>ZARe2mI>#7^_i`QP!f%xmxmmkm+>`#o0Ge*Mu7<&F%Zl zxcfje-d0PDcIsLpzZmsY;+Bp1Gc#}ETyK?U9VJ?HyYC-{Ulk^F7~T1PPVkrKcrlrBnu6g%5)Q2WW2RM z>TrB&xbC)RLiySf?}hWNOi;+)hb$H9S=>-Fa0^^$M|O9+b_84QV1p_s^wkzh*dcAc zSL(Mm0+#(~A$h%WV|X{@ea=rn^;n!E?q4~9Whb}u%iAz1P+)!CBN?N-Ng#H~B6QzmTr{S{*y z3RTih3-2RJPo z8R6W%kR@8zkbZ7lQ_)6{eZrt&>)oM*^3%oXlNYu6-$GkAOHGc}@}88KaWjMK2U-Mc z8iXNLbOx>Z5Vbhp^~wC{s5EG56z$P#BMr32eDfp56#X(6o76f6ZMV0qe^-l-&nO8VVB%YDcc*~DI zgHXX!MnAnKafD;^W-|wR9+bsUgR@1;^4v3w&O5m0s+Jeq$hBX2_|t&O=w~cgX?+1XlZhyV`%{kW3*LpES zqr${FY~l5S7c8;I-+jR2&CBiQw3*W_{{uhk=XYIr!ShW;pO*0vf5+eZxn1FIhi{3_ zF(am*KQjE5hPxx*I3I4{4W94VZpSbGyC+!+SAF)Oop%;=+#6R${UH=&-&f$uvm%ZQVX!j5N?%@7g z8H1$sgEb=s=Fpdqbnher{eH)P{PFR~zrS~e^_QE^8qtlXSJ}Iq*7x^z{@yZ?Wc`}s#KwXCBb-|r>InS0@DeY_U_ zXKr!#gVEWT=OKQ1J#YV7ozCO9fLERSIrG2eidS0&{&uU+r99_ZYexw1&%X2}lWPmC zzo);}-S7JBbr$a4w>x^(DSmEmgnROrK5vLS9`o4n8UZi4%JW;2VU+*P6P*vw%J7j5 zcQYrz>O;)xF(-ndn`foVzr7$$eeG$ZHL#uh$;3hpV9M`7*1$fY4a=8L!E-pNA52_PET^sgsYP51Y;1ZH_gYc44UT~T|4p=Qe!8Lu^6=%jpSW0=kc7#mK zKHUV=GZgY=`KngE@~x4_^0htcmHAEQg`Z7Ftoqz1_oGf(6NwzD#bTDf8MQ`@Ap_d$ z)(VT?Il7z23W5n8=B9I`+$Q}P)1H`j81sQtf!a4 zTbH~{^5CC`g1dx@dGVMHCM{M8mfP$(9GEIf35^NO5Zs~^ek&q7ZT8G{L%F?f2)R`Cdnx7*+dFsSq70iwEk$Ct!AKN7^=5c*S zKIX{bozIcOJHKzz(rgBY;R8IVcR$rDE%sftt0JHi| z1sZ~Hgj|=n?xAjaOkSv)HAXG5rfLl}&K4b%8-tc$K7YKm#*y>+zT$#jVeK{e#C*Qj z4srdKS!mbFy>y!Q6RgEhchnQON_@6`eW||36CAyQ??GwTIt+3Hq^@B- z-g6-q`o`BN$$_0g#K%ONqULDd^{$AzC52>wi~QKL<`NEeoYwMO=Qy`X?a*Q^9yzGj z>(N+lLJr1jEZ`KmMB&egW1gn>@eS2zNMCP`ANB4UlzQhLC8yEPfni0buI=)syxBPo zM1lHb_jKr^xf#cT4!1V>cbnAh(dy}gcl|jOJ*`M_kilxM@ytLODp6dFN2aU z-zP}!r|3srQTL>ES~@+uchy7fBiVN2acX_RNG-TXC(ko`Gx8X#Nv$AvE@f4^i2{ix zZ&N`5*n=e(A#DLepK@L@VW1X2KQGxltmW`TPRK<=8aKk?SMHtgaf#~zCSA3`4 zl4ELwItCXlq^G4j!TL9G&f`A0G3>ef#7UJhL`4@JjuJ*cct-y0BUCS*!cwcLh$hXuMp%Vy?f4L(z z=a%`9UU7Cq zYa~W^4~~z zbq>B$Ho_rXv<*5lB#!uAFMF9hm2Q^4OE>MTbdv^dH+~!US^C4u*2 z{<_&YHO~typ0w2bUw5R#OFfCSG($rceW z@ke`&vy}gF;^79LR~=*VrbtU-F3j-vd41@>m-@WQz)kCnnfK=TmHRTh|LoV8|MhD; zT;GSUvvkh8)?v?+d_Lv!i@r=lNZ-B&J(1Eq8Lk^Q+T6o!@MYO;$UpM8{JZBH_D8om zobo2;&$Bl>{3B5f_0&9X(+;1K{hx!1lnLmr|GxiSFZVw8?HQh*;V*JCJml!;lmE0b z#QgDfGzggwXV|`O=vZlhobMwt{NWQk-%n*2;R5#NaN*t^jCt+d;ln?|1q}b)!S--5 zPZ_X1Uib%W$3Il}gT=q;p(gY{JlvfqUzzrLaNqy;|I_KFWp)Q0``7!Adfpvu&v|#@ z!oNNIgSjJKz<*(wBNNbF^?!s57~uj&Jq4^|KK%5T^BS89wYQ+z;^tf>Ymi&biMmG@lO1w zrtsGd&;0Og)!$3A-i&g$!-q=6pBpdIXQl@e&|UR^d;0C}I^FlkijYv51zx;!p zBmAGcz)SG>*X=rd#2XQ57mmBHKh)ljn};4|koicy*V-em^v{s%$@*oa<_p}+6rfpx zi8z3)bCT?dvMt8*{QO^H0P2MOgf<&@aLc%Z*{<>?yJ@(=Wo}$4My>c8%Pad9utq{k?t> z7QeOnMcB1+V@{VvCSIvugvIYw`bF4va%0Yr?U!ArUxdZ)HTp%^^>Sm*lzo)!mHI_k z{9dbHgxw%FhM7F(yZ%w9gYAijVw|IY{9mVEgx?5C=Fzfe%f4Q}NE^S6ItRw%r{*TP zF{Cpn`zHM&EPiiI*V(+K9(7xio5u;9D|@}pkICuioI*E`6?ly7NAwGNwidYgdj6Px zf!mgG%;TJwaj`bDU2^E(|AGBP+L>dy@%zgzW- zT(6ZILrE@_y)FIFW)=yH-%rv7Rg~-oxiO1m7s_5(J+6wm`kf$T{zi6(X7D#MrK*`j zPRdzWMQ%6ZDYH!WV%de&C6&()dP#25Y@yFZlFs64DCb@26gkiL$<tq)$?p0kDHJ?svWHq?#EN+QrV@l;9`GBRdr_W zbW~$9cL?{%>T}|t^Ptl}C9SKq5%IylG<}*}pRYdO;uF{}R9|Shq3NsDS96_d9orL2 z&yxQ$GA^WJT!DM0a9^vwrgZSsmxa}VY3m_+w3xS7-&Tr_>8i%2(JTV!1RvE!bw^Ic zSEA^}I%V->l`&?cN6U>tmRQ~P^N^vW!`$HNH7Wc&SznXtRC@|3>`=&Zg?b!OWyoPK zS-u@448|%(Dea?Y)+y_Fz5o*DLD0HwDYrsvgVNiOHcy{hRM2@m>dxP&(GHJ!f_>ESw-OTd6ux zeN*UzemrGZk(w)BIDZe2>lA5v=SO90vxGQHUxT}r@ly*QD;*QNuI3AmIaqc;EzMIJ zRCzX;jyXvFm*^K|M&n_ilu4dT(vb46^yJj@MfojONu&67;$Hg_JKB zKkhT}0sB1ta;)YGxiL`rNZFM-G0khCL(d21ogQFc)EbS{dw!4#=S^$RPt*BPuhMeX zlc(q{0twn|yhl#?!Wub;gK+1M-_qC0#d0MUX;2ov^^ILiH+g|Y6V!Oxof}d?Bd|Iga=>mlPu26_yK1km z*%rz7RB~o#uki+rz-~LwE820b5jf4?abCCRf^tX1TuaqgYtac)w0`KL5)a=mZmzPv zkG1Nfr5hrrfgF?eO}l5eL2#bd>-9TdoOaF=j$32AG%lfV3hsEV<}*~~4t+{q^bp%< z=*C~aYmV^j^&OQuO+ylHSNqFIhdKlow6SZ+X=8_y(?&SHhWC=!H#d3BY1gf_Y^beI zeyO!QeqJiy{m9wK{zbG-=8}}`Y)LWsP~%=+PJ?pevFo-%DZSpkXX>$@kx~{raGOX7 zhB~E`C62npfb-HtACsD64dXb3R`&*N| zB<)^L;9`tCsL;_WV~k9=GxY8h8g3zx#SwqE*f(8mEsq*GSHB1qy&*hbr24wwS|N3J zZA#76vi-96i6<&w^9I>r*#X)6WyQ1R8reZvrO@0dyH-|m*}PG9ovh@rxn5TEG&M3E zmh3femfaw$F=lgu;6~ZSvKm)5y977ME|Kk)yaA=@cCL6-33ggr zJ7m|$R^rD_!L_WvOy_*F$2nH{2(X+~7e7sfVS)#bza*Kb>I}~<{ z>@vBR%iblsO}0n4*@Ab=_NtCl&MPWC_X`dSLerpZr|f)Lm1lT?;5b=mSR_lCyJVNh zE|u+(T_)QrOWG@C=g6*7+)A)Rv?<-;NCQ8mf2Gp7Qu()dL7vLb@>(cJew3$AmUKx| z>6pLb4nqTY@>L!!{zISgxKe(@gpV}K@T5l^(PZT(Z)mwzcC~E3?0_uscgU`h9hBWE zyH<9-?2WSPWP?BJ^$g87%WjY*zfH1BWV>ZI%l61_kzFPW-CJds%l65xklil3O149G zbcSOEMQ5vgoz6>>2|c&!W_R?jQJBFTf2|xbge9rPNu6>2c$7x;Ymkn zK>L-V$KopA;n`|iIq%_N`Oi_iyI=5X!5xBwve2?tmiB(5;IQC;ENSl$Trap&aINfo z*_&jS$ZxzLWxPXni|jJlF8PuF62ZF!H&^M(ZQ$gFKU+o5D#1$jTG6*!uwQnEp4SKt z%0kah*|oCs%Bq~LcE3^2n^acv?~z?5yH)lsS$NhVJ6e`9%#|e%`56z^=$Ula z2`-S`AiGGmOP2JO$}Sgvh2TnA;*wUUEVPg&v`mnVI6Zpqm7OE2{I0ANpE!ehCSJrL zAMzmVY*~eat}6!w3A0c((v39xgJLfe_*!i;L1^vWZBuhCK%aTUUYk0Yy z`((+hq@A?Z$gY(oEzutOet5myq)A$&6KPa>CSIp3WyCGohl4hyf&VaZp`ly0M|QR> zVf$qj-r728kT2;3?ZjCkyHu8VLAT;cbE|WTAVdEHslA;f1%nZV)7|0a@}}qj<{G!mSk? zE4x6nk?&1|}_1vXrbf0K!-XnOw;8ukVJ-9^pHS(J)+@RoH z!c}^P&)Y=HwR+yE=N0l_t>?9Ro~`G8JgSS) zvQPX|`Xj;xetZ!|^j?WSe%-S0E7Bg&v+{1~&(&m`jIy2E|7($ZduX@e$Li2@q+HrdZw)7W#N7JlSa5B4C&4FFqh}@ zT~7IgzkE0Gf^uq+qpE|Tq%T_W2fyG#~(R>(rnO4(JiIqg=zKU#k94EjiOKh0%o zo}$s2x{`2$lbDNWRGJ(37X{)>&aHyn2lh^bho^T59;vUX-edUAv|Vt|)REranHtwC;S3!TG?Ud0GS3mW;-^X;J!+!AsJ$ zf(z5q^kIXSrt1W|Q&0Mc!K2bg4fXbUY$vrd8Ngp?Oa{4!ebAdxMmGdX$J|K0b zPZ=DOK5cL;aOeQ7Lft6$lC&ay*5Gr~=M1g{4lU6t!%cEumWI>k4PKu9UGRlzUHXE- zSEri=uSvJ3uNwSj`kLT%>CW^GgWpfL3*MyF%WoO{ZThz0m$kNdhrxZT?+D(OI;-y* z98-Ny@OxSb{Jz1%s~-q{KXp|h$&WbwTFJPEYA`$lHB#Bj3Lh94mRd-{6Gwd%=TLkCi48x!-J0 z!NW8c-XSyV{#NwxZS{W& zj#bD%7@SJr#zCnsjWVb&w*<$hfwY&wv(nyz6C|Je8oVg&Cpa-JPWu~tV(K)wJRNB8 zvNTq3lH}wdgU?Nm5IiBRPU8%|Bpo71`7x~W@XXBt%!N)A-OO7~QAwo()o%SBGH%t2 zS$pP0>-aYB-H7{D@)v*&&eB)72KP0R4)--`u2kh5XL{x!=@aw}Uy%w99jtb*Sxc3> zi&EgafXw3IVDweO}Hk1zW zXGotAe}>wl^qwGJb5qZ(w9QPB>5ad41nFc4NV{&{YE&6-~a)#=MdE`SC6TTg)bgAzNN(X*T$oVJm%5%@Vr<$;g7#Y`!1KcU~-b*jok{WFt|eC{nNL=Gt*hHqK* z+{mZZ3vwO-_b*y~H@H^}x+ouXQ9kIReDr&yB)UF9ddDhb#>2YEi#;P^CG@KDL!JcHfoG{GZP-=`bwP5pvX zRsJ&sk5)hMD8XK>!JlbRXOjpXo93rS3mzx=8Wfzzy<7&DrE>*Om)?1d!4+w~!By!z z!Lzly?0kc3(qjc5qg`SP1TWNmTo(vlEWLZ7!Ac0esUZ@`YDRRT_rz&60z*#Ct|9PqW z4^aM36FgY?3rfEnl%6g(_ZBWQcu0DNAgBKe4jm!+c&6N>_bjDDyIC$b?dG!V|7`hl z|Kr~ZqQ9OexToYt{zKes@*E4_nN}Dalb$QceXJ`D9-f{rNctG=laySFUy@46Yi>^p z@AuN?ubErnN2@%<<=$k;ujn&57kw`0=s|CPF6WWY!J?D!+-Hp5<92~`vR4lHPaUwKSrm&6Xaxs7Yp`Cu2%~lqyF+G z24|<28l0D!2Ir<>!I_fxmkCah{`h;rIqL6TVQ?_56}(WA_)5Wx(vq}JaI)xlwcxX~ z2jevcSEMTi*GazD3tq{-M02l6|7h;@>Hi46S33$f2!2R%{yM=gXngT{!CTV5q&EoO zCi&hd_}%oL^hSf*(whu!Pyb}_nzTvq7rKA$%?7Vc|19_j)x;{*=Oe`57YUxG_IbI% z^U@W9=St4k7`!OGTyU|*iT_~m()239-%9S^V(=qrvmiCvpVI$fCGZS`W7&Nv_d)4Q zLHLeAZ}W`a^ZM+he&n+K3=QrLrXEL29t4L*ORkX%-TH0%Z%ppTB0c1mXXIRRAm}#; zjaCgKpO19CDaif_-@Aa`>{2?&#Wd9)axqOB41IpIG#C2(Xw@+Nc;qut`b72$_(Y5FpXL2zoBQAG$ZuP z5;p~hW{I27b-wC@e)xPz6#ei5ji0nsD)>a{YTfgv`q8_X;H9e3)L@iCzcx-(s9#D^ z4(As4$JtYZah-5OjO$dsMuek1Q{H@EtXzf}!dFiX`XTCte#rYJ8*dG9pPTp~x$3h1 z=@FUFhp1&yF5MPtC5Te_Ecd735yc8w9xr`|XlP?!kVC;CR*VR)bU1I|Uo6mvRr;{O7p3b2 z7wLUQaA=Xn(bvoU6t(z|7<_g5sNhr6df?Dg)&4&w_tVp+w8P+=)5itj)4!=)oJ03X zg9oKg38F7QZSHaD7K7u{R|Fa7enRoMr+%m4p(_842B)Ua2p%bZ{=2~w(ia4K#E+W| zo}9iYc!D_kC4*Nc)05Cv&u)KtMoa8J?SQcN2kvVLhr4D?6$kj z;9lwLf&@@Bm50H>OmKAoA(XIJV)a zRU0mLRrDwY-&Mgr72i)pIL?xxHxEwqNfP`#ByyOM^^_hZ0d6P_8_LjOv0I59m!A4u z$&#l|xLMk}S@M~^D!V#Kg>5rz&Oi^B;qhl5oM}D7>raNQN^ptunt}#)H#rTA5u66X zagS1&Ci`MIB_g=COl}OEZOATGn=QlRzeK+Xzg%w2WZ6lw;DXC5M6J`XQZ3JEAly=o zTVq$sYPo~kt3>}++q*JFewXPNq1IR^dIC)qO45Aq3N`$ z8K;&wQ>yL}=^rD%x9Jxg-a&AgS+d8^1u3ep0h_7{7P)cIlPTs0+OdmMK)0;wkd`pnhe}q<{*eM#*dtnd!hbEb5^bl^@a+Zl3wS-+^2jvVSt z`9CV-oIBb{SvB%raj>^)ZY$`#K<>_}E9XoO-_$Q;Mypn4&es1~vft7#cCwDECS{oc z`k^(HflU@Zhe|>-S?wc_in?{<6*#QR4yHCZ>xA&Zv{jbLRXEeY{ZX0 zeqK_)qpuF*y_13%-Hgz|x%6`iWSm9WDGcwhAu&BfkztfFMz+RPtb*lVus5fO9?L-(G=h$Szh|zH^`PNtwb@xiJk{#wj(fYK>3u3%a{TgsHzag@Q>L z7ro_3eLC6=Q`Y*UxmEB3FGXo9i(-MGGlc4!TrMEQaz z;K`Zy_TVz|&}Uj3Ebse~7k!D35rQ|tH}nc7c*8uH$MWwl*qJ}ais4*3R3VA$=Q;a$ zfb*O^b@;{Ia*;Z11p{B;*J0vFq)u4oNbWHn5Q0<7eLiAO$zj~d`@l0~>l+BYW}Q(4oI;x`7e&-ge2M0M@Voxj!gZv)Og7;}d1^N$qcnVN=k?>?VK7%o zei$?$hU$Up@?_X{DFNsAZ~BdY-urin&H<}kugogl`vO6vXN6T{aq^ zrfK!AHGGUMFY62WR&Se0JblSjjr}PTCNw;;>1)axR_72v1|0oMiDN8^0Z0D=E^;0K zAp@>ooa-Q-llY93__gjS% zu2VW9^i9+>(n2r4#LC9^UgIdfd}sv?gSfr@)S9ay95j(TQaGtKf8bwv75DBoyDXgR z9%^%%G;_PeP)^FkYFy}w9;NK(I)mlAm1rx}KeES(uTYBVbzpS~$Lf<}D;t+ki}(fA z$XDqm&Dnb6f}50|Z$HPL1=%WpL_|}=P zP5dz6>$v1ypA~DnNeE=2;2GK^N?tj&KFN4O$;g+~ykyMlVVr-Z)^mD2EM;&BwZNHnzB}?V*71B*694+_ zTbDJ)fi4LyGo(cALj9qV@!(MK1LnYaSNDQ=1@3j<0A*l{q>8ik5Wz z#BwiDm)saa(T95a$YrU2x(OmfDC81c^zX>Jrwz8g!?~8`7*)^F*Tmkc{aa$@(EQt4 zLe!@e*LsrDqYJ@>K41>5J}LbSz5RMg>Mz0IA%66^JkziH*u*&=^-aBaJMs51YL~h# zWY_AH6o`YYg5!PxQpXQ2dyhij?mY_WBAqUAmC?Zb{dki)V$m0tQa#Qv(288RsmoKh z)IE(p;hSD&?}4F)_d740)8#TsyYi6K4aQ}0RJy&TA$g+U>=} z!Kh?>aEKuPL46joeoL)!^;;@_aEr3j za(eXk5x2v{C;|@67EkWwbxu9Q-nqY|wQ=u^%aPBA5rQ!R3@dR1AY{NHvCMJ@AMra@ zzno5UP;LxDL(PG68tZ%I!7+B|)0ic23*^Q?FK@2LX=D=m1HXkTnM*6NPt?!HyNpM) z!fB-=+$nlTigvI>Zp>8K#j2_MY|KDV#xqg#%LpO^lHp$cO=e}I2>kJj64SAsu3vC& zx!f2e2vII@%@rVIz|o+<1$C=H$befZdAZl9Yshtmevunk?xcy_R?Cf|^wc6xYg)flJ-8^L2+kQjJvM$GOuHcAfdVwx8C9TVLR& zx8XJvxE?(}PPbz`=ESF4tuQN%r@NC0(Xp_hxGG&J0!jAba_fEo$Wd7EWy5viyFPQz+J5t>HI%V zzMN$ddiMs6UvIa!5z<(uU(mQC$3Ic<_%__m0yn!2cVmG&LC>6ifxe>#N$axooLnAe zpWVz6I7yaME}VW&;(0_`YElSxrB+aUtk|J><%6@HzFn=__4LUKvntPkpqUwAjg}v> zLoG0e7d=AM7+j@C=rf&6x#^Ld*Y(&4Esx(AU-_Jv^o3CYO+FxZi7|2Co7@}4ERodO@R zAsK$62le46dJvECbd-}^amP(?&J!PZF@{*&l8kYp&P!R(CIy$_RM5>B!RZEjcxtE> zkQ;Nzd#6DgqQUL<(GMK2Wqqt_3fiJXQCf}SEd@$6Xe@!d{Wj)-x`QO+Sray+exZZ1 zEVNM0=a?CCNpTLxIVJXe0a>@Cs9t^dHxTx0g=BOo)O@2PWZfm>GnaqgnEd>2Iz0ar zbV*7dJ=Ea~@_mjk{=sw2|H1x#`KxbPkoP{_;V*}uXztIv$l;QgBbC~-m*J=A7-T+? zp}QXX|H*IqmJ7){zisf$nO=_P{MO+^Psu}$d6Y}fJ{exdP)FuDZSYyy{rh*GX87y3 zIW$M{hpe2WZt3iIIsR<}j{K57Ccr%ZJM<@7%g*VUqxkE|@#ekT<43rFMbKS7M!0|% z=k&hQ23Ndi*Yvk1`psOCq{H?yIluJdzCNt)9?iTz!wYhRfbH@ByScs9?w+c^2i#rAz%zG&X@{`K6@{T6 zfRFI))$@cOyWTr+*7X)~!H;&W2j^t}pSImMd{1^?lJQT=aOpTt?+I<;ojRSqnf>o; zyHEFb*}XL5Kjz&}YKM_d@TDC-RD6VMhaW!DecEA!Tg9MIrX9Mk{^@=&?a+NwJH6q~ z=m*A&bRJ^-NUt5X(%;QP$jP4ze|!B!Oc11RyyRSik9tHYm35{MHQes_*Geh)60n8v zhdq8W-9-MBa~$6LSckjw|Jad!kM&hYIBa+C|Gu{|L`tI;d4DqWL*6QMI_-}?x|xiw z`|dv*{_?qRHfTuwU3Gp3!<)4At^Z{HS8sXK2z=1fp6(q-cj;4PtrQMVvvTZrz*K{z zg?Zn%-Tz0Yy8mzfZ6rR@_0SLezr5l6_)53K_7GE#bN^Q^`qR=qaJI*L$qBno_l#*y z_Yr*#Bj4T8yY-{a&%HW5y~$r38Sht@-EM+*@&4|A*u^99QIC<&Lxq2Cyx+aS<$ibL zg8wf5U8j7<;qD!Ke4X$bG^cusvM(x5M3qYtOgc9ei!t%!$Tr4+5{u{dD9P@!Q=GmQLUw zYI>2+gTZz>9xQy|Biw8cp>}@$Pji1J%U#G{z`u4mVtys~9`H}ix2`{K zJl!{$x@hJ>n^hWf7xj#tuV;B~F@Ho4k64#0oiSI$TRt5+xR`Tcg|=RMI!yEK-Y6>D zaf@Ko`-5Wk#&OKV?CQIKF8MP|C~^2b zRxj{oC^?)1XB^Xf5q>?IsgAOcHfgY45=@$mT9ejS#^u1>S0Osvn8xIB?zyhJL&*$__8 zgR&Unjh8)5D;7TARhi#>h4k56IC74DQJMvEV~EM59;aBw94q?`Jr>ydX3Y9zXzHBl z;>T&_C}>$MH>M%W)Y?V*MZGMM8`F^Gh2|2i7{%_erD_G98`vTJJVosflKZLp1N0~r5G(~1qc~%lVz9doQ6mTzg^u{L9HRFm#2SFi7P#EQ-zzO1XhWczLRFE zT(8o;<`~?sDMGPQ!#xjyTU+3!>-jbMW-?Y|))%;SY8k$IN{e7Wv{TTbFPGT){9cn% zB5dSOW`-=QH1E?d!s7R@`sxx1#%yw6t1z^t8}xJjb(r55^kru7b8`;MDhw+#H>WRV zoYoNJ#{WzDMST2j(J$g}ksHHG%W<+_(R~XM7Qfr{i?Cbe#?T6nm;I*gh{vbWnxgxE zS6_#A;3c!ofvvDGLkiBN$-hQm54d(u;niHko@4 z8450tBc(3)Q4j|Mj?$JmbR-5G)RnlgAY{OiKJ)z1KG9DY^glHC8UR@BwNNPEm%*+U z-YWVa>ZRla8#7JZ7;+>pU+2Nc&IPgu!cxkD+GcP7 zDkx=v%PSsU65{SJI=z&>ia}oGDiCRut3bgedLL*m`rOtE0=*VAu)D}67xX+q7K57_ zE%$ok*QM27*D;mR*d?l*W8^eVzgThSR_X<|HbHH2n|iLLW{=z$-bm>AxH&ym7x~7{ z(*w)C05@I(a2hUp4e+fkPrlQ|*G#@-5-dp}#d z+^aPpJciK)Bb2$?nUL3b?duzG=y`UZ-fm5dnqZFtT%inj65>?#=A|Dgl2hbNxbw9O zH8{0MZVY1&oqv;8uwd7MBGeLs$Pj9=O4@HN{t&q?)JPykpM1r-z=qF+yIA`SJXe=Q zxDOxkbBzZ#iCd)wmLB8b#Sm<3-h`_qx8CuJY+;Z|;`%;s(qi>8_TxuuCKTH47ULRg z$j(1fE_YMA^tygTvcb8QL=W!S(t~j;5mc1BtI3BG3xcm9ozw*@gT5~v`9`*)#rKHb zMJ?^gE^9XrO?yJ8^Dy?_)!GvrW#rs^m)d%7rQaApPUwLVeMK``Kea`fihiUSeGE8C zQsaDgId1YKUDCnr5>S74RQvgNN1Te`^wm4k$p=XR7g~DQzM#3-Rqw@Hg=8-vtC30gJ|D%KMf+5mD`{XB`iIo#^kR@h+4i?tjw{x1RJs!+wOVN?9kZ# zfUFaS^~UGzj&M(q=5j7MO_StTeg#Kul;2*jksCuxo+A5X?KQF!x0J_P>DtAnQP!ze zU1zOVUHa1o)trADOx&mI7p2`OH)fjbR9SGLJvXV&`%O;3F4yj&N{|-JegfJP?G8WR z6RHP$c1X#{g?-Z@dBFN>#p27tRx3w(sJ{{yt(?}6b|`V7Rfz|_#DxY0hcuTs6Btng z4vkoRk<)6wl#Wu?xLRxA=iG3uQHSwa2t|{Gxz+#|Jna@w{5v$Tw0^IN4pGNkLAYsy zZV-1g1v@<`A>4MYf?lZ4S?D#Kw_mtY-rNy7gRTs1050lewq(#p(qK88$0@3;wYoMq zs--teZNR(#I+Hoq3o}NIy0r~CG=+)DRF{sNVU$D$J8h9gMgc~&Qb&1me=Z@7lw4{L(^qdCrI$l4Q z7Jsvqm&q6MIa``H=<5d|gY4n!^dZ&rRDVI=AVFl1-T~qeW^<{?@TrwR)fV(GC`AUMT2VMl>0ue5s_? z=|b??qZ)KA7u`-5;fAE0{VPDPJDtp!N3tuWpPfP|VZUf_aFyH`+8FKQQfb^gORHVJ zZjKjNE$MUWX*Da;i&f^z&}%)A^?YkY@BLOD+Rtj;Y#QZRpF=U%wod%;^3ayJ|1`?8 zK{R-I2=_9bxmA{DqqKwXE!(8_>E)rneTB}Niy5XZa$_2@o7IxNWgv9_pc$;7W}Dm? zNZl$O=hX1h$!(@V&32W^sUh5Jbx2l8&DCltPR+H_h)zvI;a;Eqxg~=;M1~ZX}2deeX_D?$k_@YqNeq%}r`$P7UGSF1kxbacao*-RT38 zF|X}Awy;U@MecmqZ>oQ1-zB*mgqU$aK%g__wsh-;DnkpA3Xarq%3-5FhjvG-+ zy6#bKPS?HC3r-jL@6dS|F)qGeZp;kX`=k}Bh90ahs!zs{eys3ioF^?=9i6E;N-pj| z4QfVLGcq-V`@ClRN@_Z*-b~HdYG$T}T)!lKcnZ?|IiF+YKdw3^%QHRQZLRY!r15g7 zW8^=fIyO@?Q!btV+M=er>dVv+?hehWmDKc9$7O1It67;Ea=lZhFa>XBTRxS|FZNZ( zr!_VoK0v-IZDwH!u?wFfMw|yR&#Rc7gZ4v{i`uKALrbv>hxR(Xv_nuaV=VhGZp>vUtRTQTIb1iNOfq7 zR-N&0=5+n9sm{o>o+j6!3Kg`js~(kUCESGS$Pu)zug=W0Zm0$_t>oHObycy3v9UTU z(@A}E{*QC3BWI))2=MV?YjSLfenMt5_Wnuy+zZuYA{nv4!u=xE9a|o&V%xQwCqfswjE=|YpZip zujK}|uj*4Qg6sCZ>M@xQ=g2*~n%zooM^10P{LgK}?JRJQ5$=TQgjR|-R`XLIorTRe z&8g3`*_;j^0K!vYM}g5 znX=z*JlsQ=G3x)LwuT7V3VoZmjf5A`&xKhp~_JELq zV#3eT_ai06y())OOzczA5dDOnTsHwpp-*vcvcfYOWIW{aDtZHyoA1+nCZ97LeNK{c zvc?u=?3Wt@9~cEbUn^(9`#}&gjPeG=1<#4>7{gFga2xD!>s+DuF3tJQDDUMOuTUqL zg$`_d$k?vDcYRUD!4X7ZZF=2oM+VPIKNjTN)1MeTFWoJ; zK%?cK8oVU^Oz=X@YX}ZqsI%mLF83mhaqlsBY5IlWB`W1F4PKUhB{-zA-fQr=>DPiU z&{-(|VesYYH-f7*O1;nEtJ7}cc1&*+NaOIt5%JxR;{Y8s;>Tr;RWfRLe7_8YIt$_m+(}L)c$RF zIgO|O1@WXXye5^x)5V9H;Vr3`@Enb_It+Cdneal54_7q&ZdyrrsYZgm4S$eU7G9=v zzg98)QPP&I`q#Bu*~jql)K_?;&MVf|G2t_*pYT?VBKjNtF%1yjrtcA~ZunAKLwJ`? z85?N$Y8oWGS0k^%hVP^y!UxpD4mB)`VZsM>7TB7GD;H}Cf27mDh8y-RMhKtKSZQs; zK}B8oOZ8fvh9ioR!e6D)#VEru#X7=gb*9~D!|}x!;h&^8#~N-{j1xYu(Z_hhEsOPp zFKe8!zTq~-1mWM(yker^{9*&)8~UdFhK4&9UBZ88yfDdd*J2}KQ7kGpHr%tAEG&zC ziYbQs7MloHDh?<%H9WAGD(qJrQ%o~FwwNv)RGd_N!0_Z^hH!9kMzNXUnZ@S9;lODo;~x_qsprj|3}>fL3O}zI!kvXTOV8{n>|6XLbqhHK zV>`u5|MO8{f9aHYhJ(_4;YOPO+`({S+ELh$w*0u^wDbwX8DOKKr{@K-pQ`q@i{Y+m zSK*h{-gYzGlaEN$FH!qiXn0`S-S7~wafvkQ9U_Q=K~~Z2VNG5AG-XlUgC)-|&WXfbc1m_&~#3(m}%Cs(lF?ztxkbc`?+&!%Gy zf0vFEKBRM>7mLpirAyOZhD*|C4KGQ13#mU$eE)!X8hU{|zG^TQm7hB2s!A&h2hg|H z>(a35qlJEsJnCH2QvvjKJnZo(fbLR^e=o$SY;iuJWe8upLv_yy& zSNI02OeZMc10_!<8jeVx6RssaeUjn$bh6KHP`1ct))gSp- zX0&=iMiVoW*pw(|Z}EK{aady;;bgF}j?FHn`iA1W#wo(h7|+y6`%(q$t8}jva{l>c z${#B|mkW6(@e0EM>1)E(l!L1bN2ad}2a50CFdUz*77kbX*BDMp*9u3eJzi%xEnP1h zr+nUEsB@Nu6I2hk7;c@uDdc?QZyC-_w+c7bI_B+$^V1!|O;t~K8t$6DBiu~&beG}o z>2Be=D&I{i$GVyYyIIKhCvG#`Hho)2dA}><kk2}C{7m^=P5z!T9F~4593lDrjc}Cs{#(Pz=~>};^$ULxt|$IHC)_|a z@<+pY=}*FqG&cT=a8u3ry&(Lc+S{wbS=tTunvk- zE5hJk){82&odd6SJx07Ff5w01&-vL$KHy^?*Q@n4yqyb2@ElX`lswXo^_3BxJ?g~& zca@XVj9)kGo!%C*&*L3KeV<3j^UJ@5U25k%5KI?e3*jc>R4JS-{?>$DDt|9wLwxQq zoR?M*E>eHIqHu4uw^fAG6=GH45t?W1BRouR==T@$J%l$@Zua85X*dvU@coCkWM5PK z{)ge1^iRWaU}H_y^itXPR{LG4AGmw!ZMX<*?5+A;S@vVZ@4kiyrqzU>Q~w}rd`|sw zKiT2O0LzEIoFn9W1A~NoUu3Xwv347d5uT`V#d<=%zp=W)@%@c8goDKIA%^<0k8rT` zfv_=H^)^iQq0(={#!%&VP1z@iUuzj|mWB&C-+hGP)@f~Fm!6I4hC8NC;ZBmLk%l{^ zQNl$k?>dINr_sW_RK9f$4@_f)hw2GzoZ$&+yznHAFV;6aGffblqVfDh!wb>|!c*0+ zY-o6K>JodrsRK zPD>vZen@gM-*C&cy>KVV!N&~erX7TzRXg0#aHsTf;SxPJ?__v#`lR8hU}K5aWOkPQ zD$PvnCRFj{(+%@TOz3frxB5^sr)FffgGq{8(zjI)aK&d%*kRza9fZD1XJ(JhVK0&$ z)2WNy2R_@u?t@ogtjfs>!9&!pOowWKQTjv8l$V&h@2IXD?P1FSDdU%TIdfamAk(3C z!cO5Qbmw`$+T4AO=Q2r;@5H)Nt!B8*wu}<}N$m~xnOQH*^^#rRld+DdPibW!c4^*W zvAM6CCD{is{i0`?VcGv^*`7-;D?Gem9R=h5?-o}Vl4aGhdN=)oyZ7TLGgjBSx}e$j zdsK>tR9c_?c~r9DcaMqccTb83K4QTrEh*0|5M zs%=_3gFB^KO{^q%?K5t$EL8T(RZbv5W0^rypf#YyoIGc>oq_5wJE;>D0kwf zH+bIafE1Zo^?LG?GV0<*oRoq7bQ$P*QU)pKuO}%6&v#)uORP(Gn@qu{-l}cR-okX+ zf6oEqq7KUvBT_%{g!4Z9#1pifw-BByPqMjEG~L-Yn@5?e@5YSI@sbO80xfoUj#K$v zGA1Z5K39mpPW`H7<_Rj!Tb+^PLnC%c(;aV|nyfg_HaVCoe&26-qP11$hiB%H)R6iY zqq%-cuiY5_IH_@2Z<)~1)zF%mX#$B)55N zd-&Q1N9CJdd9nVxFTxO4a2)D3t=}DQU{+hm9v+j#TBPRBe4NTA495mEV>lZPe zAv=a110ns4O7C;g9n$xM@*Xp&oJy~>qq+`t&7mj1TgCU;*po8wZ{x; zz>S?dAvd8LC=I&*e%NX0RZr=p2>7V(szpjL(TZAo!q~&+?$vV#sZ$Iz+Bf^sJ%@g> zVCCd%t5^I+yJ)pu-_ebG^?Xmbi=l+H!YU13VUTy|)pO5rqh(VDOs4~5Ak(+dg7>2{ z4VhxBQ>Bfo&hM; z{Je!3ZJ9N_^Q~Tyv@sgpcngGb*J(pCKq6OuSn-0_(3F+npXku%o%yf%=k z)_aWfA8wE#OtCY6f$SIxJWs97M*=&DGmqg_-1HN0qb7Dy zD_dyX;h&!(i8OVn$IzFvEew*#t~EFBmPGoF+@Eo3%EKb&LcQrTf{ z^;8!X<2&ZDhdhK{2%S||tA_)`2Mzb}%3JKPrhFLUr-yvrv?*h(XYG!0)N^)+3KxmDVXcCZunU=5DI7j#f@RR(#A;KN=D^&cb=Gg#^;$ zMv5WR^k~Q+G#`~Q#&H7;8T*)i5iX>fLZa>1*B7+VWN;8^gyt*R?Qhn&Cd6MulHlQe z-z)J}`_WmlW8ggE0xiaJyiwylJ?FQ3ocLq^b?B!ZWXB+7oZo)6wK}A6Kb-*SqvS$0 z!}vgFxLfVu_dxwZYT(16`h^|ehw10WC6-!?{`~`za^UTkcE?`)h>00Z&m|$MBLw({iPnbp4Q<7VgD06jfXWky z8!r)gA~r7(`^dd4nSYs9rW(6bUvc(-QJG9VHv|S&CQ;t=l)QW$M?6fG%d!hU4 z`9D$*>`DFK#}@eutmf>2^_IexblO4Giq84l!?Al^KS$??-8|H&ogDvEPBnks7AmE{ za9^^c`}mxO59TiOgvZC2!)$ z;yy}Ol<$&DTjgE{kMCg^C1I9_%PWV6($4hdlr!l6Q=EDI1{TGn>m0Wq;`mx_e}Bsu z>HO#==gWOBI-dIr$L{g&BXA3ZS|;p)?)q-`b~^rON#RawrHNJT&_g}~~SA3T=^OwImGxO!UYCB?CluzH`|qa_89c@_wp2mv?bJ@ z-}dGzXs1`Aw%d@O zMyVdQI_Sohri{~!#yzkx8wh%J4cqs zhvm5kAKx#1dH$B?{-4IbJpcbU{@{#bJ3rd(J$=9b_P4xvmggSn{HOGG|LyjOH}L;> z`r;_59Qv?_|6_AM5b^Y zSN$@&#U872_N6jQkem$CSbLnu+npa!Y)+4vK|k-Ik9_%??7ERFqk+o#Kiv~>mLW>U zu*_A*4BBJ}GL)U!?%~EWo;q9S3i|pUK75v2H%f&*z2&F1(m@0~jX{ff7|x#XRduMm zkHd2?v5=?ANrc@q&E20bcoyQ_jP`e381=C7>3QYJh!vNZ5tx%>s0(=tT3@?ith*Nv zaU(SI<>5aDV?60FUTMvx`+0r1;}$i`lTyo1>wTHJ+?na!!1#!7PmixPdD(epnxs|Y z7%nZ89aGoE#2;^wM=H4SwYeB6Es~uWN9up4E@U5nyFMJ3u_|sLO*rPi>L55Vt)15t{RV-X^rii26bOoVTr0`jF9ebydFM z)P20{7-%&5t+fX2->~6(zkTzG9j8h<{d5le={>q!LZ2msYqUJ_!5p8DY2D6$>XW=c z!w;p!tvXj2+yr}NTF5A^&+7?lYyH&M66Itmk_+C7bAI@_Va$&DTg2o^dl2Q*bKv81 zU)ALOHlJNZSMbfoXoH=yV`vksJq@g5>#fVbZ-dQOPVrG)X&nto20eimffjp1Sn={5 z0Z@4_F6xjkxcclanu8ThH_m70i}RIPZ0r~>QEPs=#i7Ea3|_CS8jN}gUSlVYiZ-;e z$;tfUuk|gNcXONFr?Jk!cQ5a?sSQ^Bd`*D(m-mHSI9;X1N({V$znmpPzA;e?(1OpA z?r8b)bUCxk)5Tx)v^3YUh0Tn>@rqV!uDPI@;W=NJbbh+F?rQ3cmL5g>O{T?KPk`RW zSG2YnvSaXtwq{>S)YMD}GNgvq_HW1GLaTIx9!7&t*Du1&Hj|&rm3rEKJV|V8Wiy@mK`%v z7do5ofdy?J2r@`gT^Hw@MlIuZOgcra6yM%r_{#cFJZj%_?{u?0EEDf(`bD@=vVTE8 zH{L>#JfsLe?AD0gAkpGLR;GN{66v9_BA3WLhFYX{tMB;sTra79t&7Y>OO5^u+43Ct zc!`!8`KDF!o%GNKCd8tWI<1m39K$bet4kw>-_V-T602Xa#Et#me1!|sW&fQYjGLC) zL@;{BN)3@N~2#plh$gVT-C_@|J>%zW2ZH9RL zIwE3iYXhyV$J%+c1$Z(`wLGJaFYT+(Jwto@jkW+^$!M2asgM6WKb*|sCs+_8=L-k7 z$k8XXPbqf#g@jW`PI2j~>qh+aGgbVIIb?mcoF`6h@!pCM)vxh|Y|}QWuWB0$*(SfV z>%FwTTFvhP(lnl5LLaQTaPo~g#=%-KEuXbkAJKJaKl~bL3DFt#*mPXxzPIrcwX;8@ z_M{vOZH`L(oPI$&wL+tfpQQHzyjN%UtsC0$87kFKYh_g7Df&f^c)rRsnv9zcIaTe= z*B5guO<*O``EzZL+!coBgzUP4?ZyhyxsFrH^(PC89-OXlclK zwf&L`1{y7r?+M20EF+O|bvYV!dAn_BXV7A#z-dc9QlMqB+Uw>&8xK%2%7GzYXok=+ z&_aH3!@jJt5)MkN8|&@xJSL;GHT@z~wcr>ZPL>@* znK^}Ov1uT*2H)j%jKcu$sJN&FPB?UV;Ox^0HgcLN>QcKylT=4Avt-A>H}nUz zkP&ucxQsyM&3QL2Bh&z#rj84xlzWJ7uXEQJ@&l>LcJ;zv2De}5atbYW?ddCiV-6PgF z*2wD|QNF-fe>nE16D^%sPYCR~*;i~nxAY%9@_FzTcbNY#Uhz?L|MhQouyW4-$3f

ib9Ov;o@CeUq_|GBvaJ+R_qnYZC@ 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 z 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%0AhX!lT3`p|3+my*IE} zqRwI$-p`lpq(Wc}=eOw?NE(?vO#YuYT z!aX3`5swNZKCDD&wMwfc)V#9BIzpUL|aNXVAUHx%(clXE9 z*;QPOGOxn#>i&6AY=2B$`FwvYon2Jq$u;#PO6(+QKpg#6t+TVc*l*nyclOIt>{{F} z4|PwvY-yytvStX2#$MXeZ{El{xPBSC`tOR9(U5-2=`NOSX{=@4#j-++Y`jbtvBA}U zS9C*>t~iPM^~+N1f28!^#;$%d={JvjzgdvFL)r8Ft9$>06$1kD!|$s8H3eM3koC`b z65Vz(RqLZ248 zzg(gV7|gJ8Why+O(O_Ne$d)@cyJNi;CU|QD7B9$+E!Rx01T8N3_zM@o>N}TSMJIST zKO|d;wM|oN7EcxA4wsfZBZW(4S!@9%oKHt|o!YvT@{3)3nuGl!9uKsJb*^q@m_OV@ zYVPKZ9S6}VDt5MEgF^Q`qDw#=jr7>87U(RhA=%lPYP;cTS3ML|Ax7SV-n2pjN)c$5^YsEA4rx#%FbB}2L^NvCzvjv0lrCgRkTrNlQBqQ=u2 zacK%m^c+2dl>@wV8=g1O5o9_O&WS=6IF3Z<4BS)VgGUo60lj*U)u;c<4wG&ZA(7ay zw4Q)XtJm}`!uHg34|syheU z#)h3Y4F^0~(4D`WZ;^t+Y2NqOdB%r*B3o#|Rg+g%c%@vPPQb{J1 zvw28pDo8b{kRNFQv_k7@A03DM^wdci%%(SNL%G7fmK>U|P$nJpkOihKB|QkS4?%+7 z`0V5mIexet9U+JHZ0TsZALeje96xpLI|ZL7$5Y^TT_?>An6g4$&0%n)c_xg-ArHf6 zWN!;yP2E^hN5hRAppNl?xH_6R)8Qr*Sp?aeAnib3-$)mU!AF|jHCg%A8%-a5;8MQ%%2~(c%$8&;?AZI$O2l#&>IXPhuEYaH5uH5V)<&12G z&f5ZWA6-HthvzjGF{W95&P*03oSMy9oJwJp#&^6*7iVbxl82Z4n72#UBYJ{Ym!TI9 zuzISat8!tIAZycrWk@}afq817@?|iF(?T7Q`e3AJ*)t5rcU$ zn99)spbOPwYkh1eeT&0=zU~>*kS17@_S8)Nqm^8D5+YgXpm1EhUQ-=fV>&k$&6&(D zX8b~2f>)Nn(pr>w>YuR-aG)FQ;7lubu;TCmWcdh27 z7p9YK_I@>A0*8g8C%KwKp&j=d!ZT@H%FX8OUPw7v^wTcsr0kRzwnbs*q0efCEvSFUEHrg8 zj;_g2L=WX@$EBxAu0m1k;tFlmlv2{sx^x7qd7e3>eOALuTWCoTtpcOLA5QSOKs;3p zZ=t{($#`DZW4^L9Ck93!ID z#tO|PVbTu|l%t~is2%GiS{-IlWKkP#JO%CK!J}smJk5sI=!IWeeN@^pm1a&eL{t{& zO{yX0!gMPebk+-g7m=i8Zs_WurP4~8!B$E##9@NW1-)cQnV~p(MS!pF=pu(r8+0qv z`yCjbN|*!1V=jdXMvsQWD%Z5ia?@C*c~X>(v%uCOonK5*#z=@Q7DnE~hk01%05Lt& zlkefn_sYo!2j7+-jP$H9JdU*TH^EBZWfe)Kf#QR*~00^15vmkCa zWgAw1J}C_uQzqAv^$l}H<>Mnt0|uLv&86=K@P&hsAlOo6$ncuPcsqn^)>qNIfXngg zy}DL&M^eFN%`W1aMyzwW=X~elaxUf&t`r&r4TKciX;Bt#44BJ^(X{~*ipGa~l3`76 zl}o3@rk;)tTog&S(gK~E1xk*Aq7faPvq6KK)3n|OQ(pMPIUM?pt}BtS8nftTUG0Z= zA^G;r9J~*Rp40d8S_%uVY0kR9aQ-Hx(}2WICKzPDI|}1QUlgFNHZEnQ@G_AaNi{39 z;BU<`a~iy{yVxif|_k5~drql`IflpX7r;NVYj!NVc2f0x}DlN$(6M(?x1rE-jtH zg((k=xVnmS*w$PMcR5+oa(=9D$Z{YVVlo3IqscoSqb9nV)-^$MGPelt7xAfgI0%nk zNtHl83MUEctklcemD+6AG@^%#3pmf0T2tR(g&&UF_-u;pF!t({Dc=5wWe_P94Qe+< zaLb@u(HzP17St}52UR>=0zgCqUL6Y!2~YE5Y~{{S<>DHyOfJPWF1e;zjuAbLp6z{| zQw0|6MJil)(zG+EXt`Xav zO01!r7dNf!UUZENVTEi_ zFF!17rH#Eb3$J0((1QT!Eq-bcjS`6ThAYa>)_fj7k5&RR>wz5jA{Ar0I*FU6q#pGC zx4p#9rY)@J%pE6aQCB$Ylv;61>HRPXfaxkDM=Un8?Mv}O6}Dt<4UYt_qheJxaWaVn z4iavHS_fwF#!pPdHVDluWs)7?LB6(hlN z&7=HyAcW54I-5x!b}$9m=w1riSl>B=9ueSrrCtdYjj<7&WzeAz8y*+(I*iqUgV$$Gd(~r8&v)x8*tJR68s3&}!($RKL;#lvaV|D9!x|0i zT{@pCEsBH4AcIi*(xD(aG}_3Q&W?D~9+FhL8T*kvj`rM>@Fuo?Yaxo|A|;zBls88{ zly8Xk4%G{x^6W%gM_P|%PiQeOb9l7y0*hB)97)Rq4*A&~+NnO}a#;Y@?qcs^XaiJM_8x^nH zaxkW+u*k!R#(Jt0NQ+@!bR_Bf8&Xj`hfOE3Tc8O_+o^SG z$s~Q@3n2hun~dl6=}4l|g(yDiq}h2~Gfx%yezU}5jx|bShKbEe=RO(4rTHo>Y-eWp z1&s&8D%FD~05&QIjb5a$tuw<2gT zhN8vf&CEpaF3`e?k2tvIu><_Fe9(9??e+mnkXr1?Q7BN&5*?Yu0>ol#eEE08fIhBPc1IkYgL?Bcbg^>VaN<*ZA# z8&eEDOh|^EU`SVrgZAV0w2CjN7+b%lo-%=CY@_+iFe9A!>DE+ik60Su*iKT-_3)Fd z5^-{vNKzl`@CsTo3iXxgIkwF-;@3f;Z-0fFMSX&dxI@Ydk&wCFh3N`|B4{0qzMwODUX#TiJ;?@(IC*ZP1$}Ao z#+jof4l&4u61EOlOk#mR?^u*3XDXTJbS}6yyr2=p&H-wy=!}C;J?wgR5*9^aeR+jw zsw`(in8~G7jJ$&y;&{0j?(fG;a}Y^vBS7i(6;E9*ge-WZ_LrKzrxH(QdqV9HFI=nz z;NjnU`4l!a92qmbtnsv3l!GW40^yZde&vEhU(93S#!&^l{DPjJl3F@B*_jt1mJEi@ zaCnCU@99Vr(1GFWN3=x(-GE0=nli5YCZCe-h@N3T!_O^)7$c+~Gwa1~)Q#ez1ajhp zqf2??kUJGir71YCo`a7%@uNdZBm)^W&=p-MoON2JUY(JQf=FKRit0%h<;z8uURr0( z4GY7vV;3Dx3N@>Ug#^0KT3VBvG0?LK(_TUt6p&98isz4JFaBs1Sp?(6b4Y zPS^18Dq9DA!Z60dI~PvDRp{d_7~0tcBaF|W1ZE209Gog~Ag@5edz!dDh?LSGl2KTY z%TNeMMfhf6Y3YiOWrC^nvOiA^oS!6(HG?-*(hMGj$O7Y%zqu5iKvFvQk`?74^$#aA z)SmFXG_=mF48z93wXqb<^>Wdr8QBe4s26n^4nj?Y+jdEp&7?1B$(dv6+$1dj8gMuk ztZb;N+4k)A-R$$Hc#IJn3ZW+Qls7E} z%M>E50K!2b4>BHJ)Cu9+uq`tt*Up$Z1D}PRUjMGT`Aw5&HrCC@9WlRH)3061kcOhv zFAPIT55*8t+m`I4i?6Z}j2?H<+OZ+MfJdIm*o+T8xPxR%XHT>!!jvqR!K0->8P3ub zGS*D56j53dJGGowPvJYBraF!7_?$slc;X{WY^_+4=-Yfe@xthaGpX3JBdAX6($Z2* zwshr3CshV!i{7WjKrPc6Ow;xue((J#D~>*c>V1D&rfiza~4A+O8dA`gcns0%K*8oK`oR$ zV?T3lNX&yiY3cy zf0n|mjnu+T#O&09aMr==nJ5E{3pv+h^@)*tTtayH+sl1oy!_-LI z!{~&BnO#w;G&=BuwWHQW5uw3RFgKd=jn4|Otoo2uCIBd=>Ia($c@?EJY``HKiC7q` zUuZLnNRLLssXDE?7KLvVafgwuFP964sm|%kLHM9}^mY&xDg!NQ9!uT|Lw9s+qQaW5 zoh%-n9pQ`H_R|&gXpe9EmkcL$J1eN!dX|uQG1Efy|P)3d9n%CW4i!HA^_ zt7r)c>pv{w%CwdqkC!IrSPoJy83FkiE$dYt?<2KiGV@a55@WRs$6c+9!-wW7@7#1c zqt#=Dj}911P%a-6U4BW<*PMf%Q$CWO_8)v+6HH|EB7)r0hAE7{CulBWMi-cj4ds1> zX_Z)7ex}o$jb6y4Ug0q)rxR4CN%TQCJ=tkbrSt|X%S#^dvS6jJX-TejtTX~jBPhPS z>P!xAk^!iNs?tZRU$GdwNcGYFu&nqwfnvcO+&$~ z(VHW5{DCVmp->sRx1dH$4@+1E*pWa%GFFFnSgr9tUnFj|smn^gw(L>;HO zxZKH45avh=C85#WNc=b*lfh^1>hv0*+!A zAISMyqE%5f@jiI2rt0i*wyDxp@0z4jMog@A+Iq z6<*$nra^_9WLR85OYqsU3Iv%|qcSq&Blsc5c*O_6Gv3kd{hMB`DgN!n2 z5iej$%T7*>fq%qs6Ma<05>H_lOTN(E3EGjQDN;s$Q2u-k;At^D+T7Y$IbSh2d z&%|^^;tbn5e8DapKAmPymUrCL*|VOr>2V@xKx%ZgbVTWM2a zIT4edwM_P!C#}t`>So&F8`-cq#6@0nsL4OR8{?l0Eg?yIt6sU^wQ&sEko4z^0|PjV%*)Rhh8IT^N-+27f5i z=0|0`)rHMQ*mY&XQZ9_@k}eZ=DuisypmJYPChU9{MlzUzFxzhzA#8S;uw^bS)pj$DvhL2=_F~V$JC@j%AFv{;kfEoO5LD)2kgCl-&_aba4 zlZiKo|5!INJdb(COV}eH8<;pp=(mgiD8i^Nu_F?gxPZSkx(c)3OzsBKsXPFv@C7*1hNj`jhQCh{s>ZC8J zV5uiP6nYF_>*GTas7-AVe~lAgjiVQidy5QDRq>|)A@rrO>c7)F=6oWI`y3C(5+6g`_5n(uEtPqR4oZ79|fuLxxERl&21S91voq24N)X z$ZZdQeF4{)qP%Q0E(KHdxsVVsxtK5D52J(^m@7SaGmP~3uM!*DMBNK3A-8emibu5Y zr5AHx7eeWB+yuzC{30f9AO2Wf$^OWa-?N58i-?Pb5ff5r%oqXESTj(bO5ChwAJ+b& zS0obiMkW&HV;cDaRw3jr9F<6X;`&75**TaU$0QQ_F#Vj?ok&b*gH?Yg^V|n~=~AYr zyeW|we>u}1yB4drH!@BC-`A8#TstU{creLf8&h$Wc)XCx92_i)U6Cn-nCzwd7_yF)%lC4II2$@UUUMCH(rKffu0 z5wW2is}iZ(GL%j3D>oz(x1!#ck6}#zKeIfMXod`5Xyve9fcLwQ>%Mm}rvJZ#{`}r; zSe>sdg%NcheM4}OSsM~{Z>#iNlsNt=p(AlWx zd5{IUzwJsSE@@yo^?IyCkT`f|C8`ie!}FNnwQ!}$Bso8w_0 zh8|h>1XkPDJptW$GYLRkt&E`$quy_I<-dS&PwpP*_ti2sdoG4fG$s;zFy4l}7B<1@ z=+Z@rL<)Vg5`Di5`uveoIFI=MXT)JeG_U*N5U2c^87LZVX!!rn_n!``{`^Wc&~%mJ z|I*=q?)qu|{rQ#TkQXgh-}7m>_vcAE^);R6({S~DykgBqedSmE>#uzpuj#zsU%cn9 zgf*X&^(*D0?XDF6=MLZV=Psb1{98^rEpU7tmW|wR|D&w=;z;~|)-xQheiZRPoR~YY zdM!K@Z$7>?7NzBGcn=4EPW*omU*k2A;ziO<4ezYbZk3PsHJ$pJ?_ateTAx4G_x9{$ z^Xo5NfAN*d`9srH%I|+`xR;a1zTN%Bzf}H{%}@D!{(mUe{3`iAU(e^`J?@Xczj)10 z`4s=L{`i{PLY7;;nE8XUPTg>N71g2sHv@wIQtR``zjmHQX%>HNaqwSi`ri#bKTPDG z{K?SZ?{0n~)MMnxVF7nu%Kj}`4j+2x@mM|o*z_vD>VM)>{$Flp{|3REKF|1z;(tKm zhx~qYkhjY3%kRVS4cy84JKp=dz_0RaA3Z+cH;-ZeGlGx3iSY~K|J&t^Ti$;~koWyu zY{L8ZRS5Tdq0m3We>xwk|Hl@g`KbTDJ--kBW?X3E(C646Bff9<)j#L>nZkdj_@9*U z=MQrHz_;;{!jC7)(Sl_3{Siz&DE?>Q?u04L_Ga<{hKjQXZ{CN)l$I2>(SpZ%|$ab77B)FcW2w)mHb-~K+=5wTQ>L?wHqY?b0QT=~5J#MA*H zNPUg}p@gfi;V=6qFM)64g#KjRnBVu!`B%$0{(JB3o3B#r&t!c5RQyVO|5NHQtWpIf zT;tX6hrMDnZ7Lo@Kasy1et-FCy!sV0?k9rEBmE%%JMjD4tUV_EroQJ_tiBfSv4*R! z`Kj;u6|1l5{#5?T-#i?$QomBZ8n1pOK0PN@yXXD?$KI6y*i^motAvUoOCedxT4wC~ zKGy8Jn2a&=FwABaTe40y~n%WoAIpO zQ|5QS^_=sa<(_-)efOGnSm!{`l)KT;H|@a(n)cvVF7c`swOsa!#(xD({S7|mpkAij zq@jm=Ou0!zFJUE@BUpK0k8+tb_$I0w$(LWp{k;z1He;Ox+6|kL{Z<(--w}TQ@Z&n` z#~xzT1pS#Pe9*ud!k=U4hszJfhZ~|CZfM#;kI&AQ<%ZC6L(>j=*q5&;3+yuHLR%D@ zOR-#4Q#-SMpiv$-gr13R`rrrE$K80K7{U&ILPgjCkF!HQz?01H&8AWMjIdvw zKq#$!h_9PQ6=K@$?;rGRsY<^K9u;J6&i|II$F^P50xvN1D){SBd0Wly; zL;s6>fo+4--)t9?4n1eqggx3H_Mm~_Kgy>cklDt6#o|#ppSd|WB!Y*fzdaxbI+shF zcDgw<_|W@A_@INK;U=pe3~)P9hynVGY46cq1iT1%5jfQmz)gA&InpN^hwQeOnIG)I zccZ}vLLUgehh79u6oJd=*Enj)rLG(BD5{Sx0$N%PtY|Bi{#B=cNmjZw+{&~PtT>_u z>Z3#}o{mQnFPV-~=qR1&QC1zRmK8(Can=yBPp1ETRw7B#tu(P#7WDfjD@y0lh4?Af zVEP+N`INC*lgC7|QQkW09F;ts{tdD+NZyNh=~RMP(kf$hCtfDypFr|xtA7TOj5w{oR8Ey3--4wFs9J&j)I+Z)Zs%h2Mf0dW&bgXKt${S6;99E^s zpx>;h8ltAruW-f?uK%8rt`wLv#3jf!NfuKn?C5<_)~r}#MAH-dPH^dCys&gw)_>qh16 zNdG!fNt;_8sI;A^M$l2m9q3=1EM5e~BbxrHzbRDGp@h!zL{mLds8&AGQ864S-a@fV zC5u>!!XWJ-$?!0eeEFy^W$Jz!f1FgcR}%f_%vbf0II>8iK2XDurI3Y^1}d-W_w-`z z09NK0aOS7_Cr8@JA4vJ6S)-|4GK^9ryGr~P$Co{UJPo0ErBcLFDbCK^VyO%xL~^|C zFsgN$u6tsD`eo{V6)0YV$!kIY3+}1uR1O}2&a$LaIaQAhl$RQ>z5pJXA4~0)5kMNa z2dH*T(y<>Lz~;meiPZhRC5?pEG!A-FukS(s+F31VL~t+8)U9N;T_lZeWy7O9k|G{S z^GhU^!#}e|QomJmteROPjhQvlb!Lqu4{Byr)~aVaeX7yyqj7?f+L21#iOSoWO4FUn z?#Ay)dTM@FQ6HgOr7^v~K+PoWD31{oLGBwBX|_{kZcZhLBR%>aDV;|u>Um1dus%9Q z9!X@MOuB=#UIuwrqg0L6_$(b2r4|&|Hd&l56gd@LH9NY+?fBWz9}Va1=#QgwHgcaG z&3L9#X)#wtQVe@i>AG7t>1e7svH``dGsU>Oo)hh}d>1{-tC_TQR_!{|nL*9{?a0c{ zRlO?876Z^YQ`mcrYT*Rx0msj;PAzqYRP!)eL-pGhl&^|w^(<~ns!1nm&5pW6E$FB_ zl@~Uh=!~Ii*O5xy+}3;AX%5E0iCQXWJ)BD(X`DgKma(5jBB?~G2e+kqbfOwGkn=Wb zGMIX;>NW8siL;Fl)koFJMU5mM$#^{jtJYNaZsb+@>!Qzgsx`Y1&wnmd?cP(gR75&i z?MYk3B;4z%6sm(7`ScP^J@@b`WGIa~b-qHG+fg~y`B{~;0j*rBS#@b$18E!Lc9I&j zrqXuEs#{AM$0sWF@nbH0D-yG1Q)zwZoFk5(eck3=x0Y^6b?qQ=aE_GF#*X z&-SW^sooXg7>_54w13QGXT9B#O4n7-&2iMG8FW60p+2C>?C&@3bI0-45!BcGHIAe@ zB++@C$J~i$XMg##b5_gVm_HR)EL?))=T&nZ*zkno#9#aDw4PhuZY$r0G%KkY#{Ful zj8%zdvwC#(QknF6>pqC75twv`W%bNuS;r`;DkkOYSbNM@yK=oI zd{q14TR*L(`2REbssEdFt>XV<@Jml_QB(0t&Sd}aV?Dq7?moBe$~D;J?@DSgnfT8g z$8SCHCgT5X@=FYVy&v&g-)Z>owdw8N#GhmG>#luY@jo;9x%w1PSxxv{g2Vq53}Os9e5G&BH%^9i+~paF9Kc!ya;#^@FL(vz>9zv0WShx1kU3K z1o>%oj%r>&tU|>cW#Som5%415MZk-I7XdE+~621zCm@2lVTH+BT@zq&7Qxw zlr&$o`r~kym@qIcHf?mc+)<(96Ospq%Uzmpa6)=UTCDooZMb~KrY3~TRa{E4|5M!I za+(?&H_Vq2E^i}zY3UTam}Fnr8ii^tpQ;X5Arq;QeaXJGaQRF3Wn@qC4%@6Si0bMKSMgHmTk7HR70yOYOi53ViT7pr;=+0Q;HHhqNDSNT z!V`W>I$_w_gw7!`bl#*hNf;;96M2$EDD6B5Z3#DXU%v zhp$h?We!Y;3s=1d4h-M$NlZ{H;}~A=$M{CkSu80t2WOnr#Mq2MDQQX3IB=}z!BqKV zrspKA@DSL4dPYiG>|o!ia<6CCn1LFy--stJx*8DbToscRJ5rTD-2O9Ce5r{E8Dqlk zH!(fy$}rr%hs352$%q}8n1dn~dx}J*7@OAQy?4FXD7Uzpfh|dysPe`PbMdIk@15?s6 zG?(3U_hq#4W$kMwE-w|LYo;&L*DWP8!xxp3l$z*M=YB2JW$%zOSQFGNJ$P|N zN8GAe9-1d6ro>rMqq@*E-7a0b>PAgG+OLqBnmAfDi(I5?fxj7b3H)uDCGaJTK$(19 z=+cMIC0Q;d#;U!fhoCj6_&vJVa_CN$?7U;Til9HHol|{W(-ypi6GeSjMFH3P+6kgS zmDDskM)SBhUuuS-tjKOOCDO9C8wE1W7f%h#%_Di314P)Jll`9B#wN$74~ZS7N-`)R zEy>5dSTwom{o1-v4w-cRtZKAtsdbczEo#)7$?2&y7`nu&KAJ(p%;ErHU?lrTPoSa2 z)lyz*D5|ECs7qN|`Vxtk#$APV*|C&?3IF&Iv%q211Fj{l#$war+xtdK)|%@QEmbZx zurU#|PD@GZ?i-#JiJr*`$q6)g)qJFxS-ozAGord`Y^L+G4Q?4PY|rzTO{#4=&;?mb zT#r$gP(e>h$k5g1S~xU1Cv&`Lj3uXxO!UPMb~e767>;jVdpM*3{p$)VM#R5jrJ#gL&fg61cho3hlS5>wJ?YF5?F z;&o2$J|r`PHpOWg&~q@0I23aBjH1o(8LP~u)R;NoSTQSsr83LO$% zF-Ms&8fXTWPODoiZiLEtbZQ25o93ArLr9tshX!$k3=58|IO|1&n(qAZP%Jet{bwu1 zQRi;8JJupLBaY9Z4#!>6e5tW%9CmenF*^;Ko>w~3WU5daLcm4H@JiL?%)D||;tP+LD&pDs_m8BB}T1+Uh9vU)Cv!X?<*0h*?uKn4mM9s z@XtSTrZrVtrqO+_EG-U(BS)Zq8;HIm1?n}zS0({<3?OWL^S{q!{bZs*M-N^;P^0A zkLF0aE=mbY4^1E9OAJ$_QWAaP2^2Ys&CCdouW-%7aVbghiGxqJOmS%$`V5gt2LT67 zbjO6aw3PIeK^bLwC8TG@CYI@*kr|(m5?!NOjT+IdlIbEiH7$XjIO!D?-GjP7dUVI` zbXdJsM9ta}HEYzUUR$4)(o^XaOiz}mvr&h3EuuR1I9<7Q?$EMR^Ny{$pRW99<{1%} zmPre2x_26A8VXZHRU}gf(`JOuB;=I~Ju?(JFd-SMmU!PFE86yvzH;;d&6_J(R*73I z>sk4D?KPDx>%KOY^=og-8Xsv{JLr>4Tpn5X^@uj)TO(b1o_x+uN)o9MBvmQIVeCNtfU%8YbX3?J>SVp+qe?zfU1QZ#+s za0q?>xBM{r7ECAlHuGqfjBNo8KQ5nYS;-WiLtKYR`8^TGoYA{5*BEoZ{Jr ze6~pU^FN||kv^w39YA$0LVc=3Gt25iK9IH(-<}#4Vya^!Kero|vlzufrF({3R)vmy z>?>_q&ya7G`b*zZ$6u3=hi|s5UKIO2ME{(jxWt>F!OFCafn(H0p`?eCSnKD3mi3Ob zM_w8y-&|)|t!ex$>P+>j=J(W-Vt1mHR_;VnDtk$%dfJOms2|zW*YqtR zd-|ICPF)oMNzG*p&7g8OGV=eI#=T05sDIayu{ru$>JwcoYdej%;#bm~VD!-ugDuNP zZ8L@1{!Qx7cU-_e0{vZk70nscCMxAB#rP1F;p&=v?Dlti8T$Ts0*xaoN1&8MbwT>Z zR#)&kRky4vg=rj6K6Pw;1oC;(zb}`5cNNVYM@V=-_5YI8hkvC$R$%~Z|4M#_iJszo zDS2-j%X0kPJ>0TtQM@vQ-|jZcYEQ9TILNXtu4P%#(wBzywyf*h!iweuiv2H>Xk1FW zuD;H)=8>(LzA8xX-67w7NHJU>kN#!-0|QEbXlJEc)yRp|kmCx7p5 zS*1uvz5l3{w26|{mN`Hjt9KOH(+sjP(?M#RVmAK9OD$_q2g|C_-m>!6r#dyUtnRJY zY7W&oP?|w)Gl$B$gXVgb1~sy*2Pxj`8e7&&DV7y^E6w|6st-~ris_%%fbwIhdX}|<>UJNs znOnNK;_;F=%GXRashw9+A3IRhvaTCxS<|Tu(hTJVMKgU!eMhCqr`VD0?{}!kE&KA2p)DEc>w>{l0>!(rF7Ts8T=n%hEZJF~f zARVONqz@_jHR_kMs1H<~U|9?DK*Jq(&^W9@bIDq2N2Kj(RF0OGbs5#;MvBd218DB3 zOXnAA_va`+M`*r4`jN)7J&i2G)Q1%Rk4gWhIJ?w&JXvC+xVKQQ5I^yZL0!S~G#l zBmDqYMP#l9y@Te^#bk4bt*tJq&eCI4r}|g%@vRYziyG3}pJF_m<^`nJs9mq2_U+w< z#t!jcoXnEHsV-}%Y)v}Td6IlqrLs@In&!GXbPZ6G{8X{5fwkx#^}CML7iv+gr%{X^ zC;3yEG@sMlRg3)Qp?~r5mi5{H=2&~mJJe6D zqjGkV`Ekc2=C`6Yzc-g(M;+Z)ndvWv`1wO~`;QZQ(m0{Ab&$N?qOqh>MQYEqOqG*jR^Wy4jp^-hD8BOzv)U&{3>eFOig2r6M z8~lC_Qyb2swn7?7Z9I$OtkT64Poxn=X>CJo`2xjyG0in!4zR3t)K}i1{@s__w+Hpl z%ZWFj7c3at+2&BiiJ~#Oi|Y4+t*V2DKsUH-fc8{dK`Xsf_{HB!uOv_U5pgc(Z zAL$XQuRSfLcItSAWvO>_s&se&wRL`)iz;bS|9Pn!ACITHtMo4Q^-r2uR#%FrnQGrc zYphtydQ|3ujfGjG0kzeq)DB4Xo6)(Q&aHdN$ET8)dLL?_REXNOS8tXaDbCnV`o^_1 zcl7VbyhAh(?4t49g>))iY+3uu(zv5O?Us&Kb(2ucXzp-J7f3z+YEE+_#o`IV`P5f_ zX=YjDE}=Cp;dL}$snmhWGeP>uhZL9S(bQ+D9g9)FQ0e2F>HOV{+Kt9%f9hXIB}?;> zkH#I+HX0NER0QS6r4;i8)b@W+|LsP7I*s~%%a*i`q`WVo@e-X#`IVu$yCI!(Qfa*$ zNn@SH)LmogdVu==|HjaKL2b~6bn;QZnMZB1-bZukbXsfQPU{xxN4=wHZ-{tEYi=X| z%|JP^2K5b*Q0YG^{MyixRvy~KPa!Yws~8t=OF12evng^#+S-zpMSC-0qo2^cE`*CD@XHV)+6ub4*Ue&1YeSQJWljP%F z8e4m)U%cFd&aZc{Rb9%*o+ePgTUUxXRgJMUh5F?uE$Dib`rwJu59C9oBwK$`^cCbc zhRRo|AC-mr;axJP6}d@Cov9Gbhbqk;PcgcR&XswX#@|BZyI4CK!?wN+*;mvSDlKf! z==S%;B7Vt{Oj<+6GaX?YtFvwXJ9ejWMq_r=RMtC2bHYCt(REmH%WB?<+LL0s&t~iQ z?tb*)C4Sk?33R?GL~D8JYmhue{itSMTGL!d>n4f^QmN^rD}C^J>gQ%EOtQKObp1#3 zib@-)jzg*cRH6Qdw1dV>q^*ws8%X1x@+wUA$RPjsH)GA0$w#rlw0@!(wW9HebePH( zC^e`}Kb8Ig$*mH1(7-}eUZnC<_-GpS1(o*O;_#-l$DL*9dbPY3>p%Nx z?hBL>sJ&DgaVg_r8dp6j4r$b%ex%r6+>`k`sePZO`DSW2TFX-`ZY)aUn#RsWljuA- zgytmDMM|V`q0-GVc9`P7y(iE*_cl7eQ=fgc0j;Ykj! z^1P4o8G9w2kEmU`(m1b3d8zaYm9dB|-Zw}LRt{yk{cohy(a!5kZMfzNYF8@v7c_?y z&rRnQYA5;tURL@#kq;k8W$bwqfLa$t@{yT%(`$O_^xBzvx+3>>n*j0YI zIrWED)DA6aj38B|IDSNYq?#phL}eqrn<<~V)E6W3QQYX?d(;QiyUxw@%S2l1Q5l}6 z{BESU{92G=LiI*^o@5_U8_uOMzKqUM!)2aaC+iXD-AMgZrLui2YlG3p4^n*BlOLq- z#?pABdGHNtlfhTgIq)t>XpKgBsnp$8x8`k_SGF3X`gT=-PZ1wbxZN7gBu! zrDtdi6}i?a*^}RD)Y_1 zyC_Ec%9vcHFHfR=OZ}xxf4}^?$uuqsQ@c@&#}caaSTV+{B5BU1Ht9-hp|@#FmZE+= zhGJKm#uw7DLevkbjqam(eMM!>Lq5KvdEjP>U7fD99-}#TDz#^y8L*&pPapqrmtk}s zC!5TMv@c6#8`Yi8wR8@6ocat>Q9AA#%~WkU|Gq+XsZDdmMHJsCib;Gk%j$d?je|_f z`Zj|0JEl;7rg^6s`7E0PPjvo|q5Czlmes(YEcPRcyeB zzl|u)lg5K0eadqb#m%1P-a_YPn)3?Mzd-4Jn!`5J*lJsmq!de)MoNsD$~ZbgZS#mr ze>qHXy@u+tBAxC(Q*5?V{uffaH(2`{+ZI$Cn-+9 zUP0HZRHx`=~5bR-}*WQ~xCYQ>ctzN&oqn z{HIfU{YrH}x{AiHN)J&Tt}IXYv1mL#mq7n+V*a(%Cy=HW=c6M{>Hh6pKK{KNU6WBf zRH{pLN1EH1kA7-M^9%W!M*ZvIB9PKNpFw?>bPn{UF+H62@(3G_rZ{w?>tb6y{1!B) zHy|5o1GiMHhEsAW#bq4TReit3p44||RQk82AjNK3CCh4BpXpHkR-`skY3>b-ZHa!0 z>WH+1VrixyWQ=#EHtifkF{gjGP~25oPwlDF#T4&WZE3!uwi-+GM&awIZKeH`JdOHO zqwDE9dkU?qXuj>&j@C`nY2S_dO`tT4=8q>yFoF8w1{$wJY;}BE$}_o<-*R}O|5zPe zKa}Y)9T}f0%80+W4WMf?it}>nuLUFM{6qaG?jDx*qrTBV`T_KRF=)l_Me{OJgv_Bn z8gnVs57jqkkZMwlc2XbekV*BXdId`DXzXWD%=%wsSzF7}Ihw}jK++E-4Q=Ju8eN^v zhZNh+R0fqg_NDV0)eq_4aeQ=bGnyMJfXld)>hq1n%`S-^PxC#E9hGjNIF6=v{-z$S zcd1P$)Sw~nsSh_HUFsW^FQxgH&KXGkuf`GM3~IlR zO4BtxwTDWN52QXtvG9@?0WShx1iT1%5%415Mc`bD!0uM7b#Zgk-=ti}m%PC7ERW+w z;EY9}&;>r7_kAnk9c7rlkT3RfZ=+2HNDuR7P`G(cdJxrg@fd zGohEf2zU|jBH%^f+>XGaHMCLVB`*R&A`tQN`P)xQmk&~TPwYj&i+~paF9Kc!ya;#^ z@FL(vz>9zv0WShx1pdPjDCb=_|A$-OtGX8fF9PRt1a9!=_w%`Vy}Ef3IAnWm-Rk!(KpPqO_mb>*9uk=tS6XVhZm5+jdGpe{y%iRzpd2us_NENr5Q(m z#fbO~$ajO5gEsNk-mL#}ywlFq^XTwKU|R|HXCiVbPruDp>0Qxd%KPg@;QWk0@gUVQ z<)DEu2Mq+@!;_1^qw53wSTKOg#UbhiFbylqoHosUB+$@v!(fFn?M!{-0}Qv^^ykrD z1kQ&DER%WU@ECg8PJs0KNzOB`ouqw$B6%z?0$v2X2zU|jB5=+}V54zC*j{(4{~y$zS%k^T3fOBBZJ<@<)S)xwWb##dW4)?3XlIR3C2y#P5t z`lCPFKXU(O?I+)6w)1UaoVkf{zR;f+zFm8HyBOpARkjx>2k_s0%p$!J-COiK6cPId zHFWvwUg$JZH$PGDLI0uq!pg<(S@$>TYBH%^9i+~paF9Kc!ya;#^_-{tQdoDJ6?T&Z> z)qdiCY5ygA(ekwaixKE)e6#Pr7=!bn&S*#Asi+g4mv{3GIndMtA9$+e$j8K!&DZpI zs%W#E_^j&b+D{AhJtJ;?as4c(Y)C!5F~-gkJI@;6=cT!0C#>a(NzjiQp@OYX$Kf@vB0=B>1wS2fK9! z-?U#Pa{N9BexDKk8bOo`<$(XyB8T02pu0TH0qJva{Slyw;Jtfw(ox}9;d6m=-)XVHyez03$@%BC#F8iWAe`w?N-iE z%kyFm;}fcX=wbfFJPv-i@KpH?wR|`~gsShRhk4sgZt}yGuj%((r4bJkgVhK6!Suo< z$9ly?(?8DlCO@Y%)&s%pF`mMmKk~)83bcvA0smSRQC(F_^voj^D_FMTRFmg zesJ@L^Ml!rSf88xoYL9#xAZi%gIj%YedH!T*Z7Nq#1r!KrQZ6-3?Veq<3ZvQf{Tz; zPFFr573Mrp!uj+~X+K;~<2nJ?<+y%<9d;iUIe@doO=-ncE9RmYm{C8raoxk$(ARWJ@Row(;j*z-=j|)0mKbxYHzsMo< z#BO%5g4_+E=Z2=8n_js1W`3aE{0Eb}*?D{~0$v2X2zU|jB5*E7VC6~fuRImRewN4+ zgXo1CyH?_7^SaR9pKSX5UhLfVTTT6N(!;&y$-)^<&zBd0(-r|d&x#D)t{0$ZBJ6;{ z6$w|65L%-~%C7-AkRH6s8HL z3uXvr3XTvQDL6`SwBQ)QTLi}n-YPgwa6Dl#%5Q?ucMy8Xi+~paF9Kc!ya;#^@FL(v zAUqMcy)gTkAqcN+^5ZCHX!>nJZtkhd>&O*^UOKlTP^W(leWg%+7~{EB`ZHJBhj!)B znZLV~udL=@#ARtA*X`YX*;@{@Q^C zVpkpe{-9qM6X==f_vk!=o|EgRAkQxPJp?`q<`4A(2KM6TA|61$B=C`$zo~_Na5psd zP1>|G<>98oo!2MnZhI(dj& zuslQ6gCEq-4WW0YFuV0RQ|0`x+VyG!oQr4CBeT&3yZmus+qOj*|TZCMWbbm7rArt9)g z{^EAoi8x(3hc}-+m3iOeFG17$Z^A!p$hQpT6XI?`)6S#42zU|jBH%^9i$G2yFyN}i z0nPH3Wd1$9?$q+hB?9XW75j>!AJADCnYoGwE=RB|su<;0i;47zU{;>oi@@0xfdkj& zY#si57q)|ak%+*4;`1^8rs%+OGa3(F&wBGqIhM&Hy%Uk=c)ebBi$9$J!WU)*BopqS4gY{npR!4mxedd8zvi4;Z?}dw< zfmGF4O8YemmwEd(DnwrLBH%^f9FKtcyyZDwovh-YY`tI)%yKLT!T4@+H$S23huhxG zpBo?LROi8Fk$gUEPKfUa;QIkU_`&xFK%4sD1Myt}e0Kl{JIL|9K-itFi1$2?uD;sk zP0)c$4m;q|mU;9W1Ln3rrq?TT8}T|NPVnnAOUri-%ByktJ)5=TzPB?5&gIl|(Qix) zHD}Y#q)k7Nn|zNxwGlwvf!WOscEIe~pDulrPtAiZX#Vq(7XdEZGfzVcnAH2t9-ZfNSCZ*+F+ZR!V$M|Smt`7`CFf0H)#J=%*vb|P@H z{mHa9=}^l8yHNE#z4J2yWno3?3#)0&_uO{@ z3v|UYmIoA`M&^=XkLd?g3cbVS?!1%nG#256Sz#QWP{}5QKF}s|90rp+lx+m;fHqM` zhp=A8W~S=>)=9Z|v|Ml{i?T2`P z4?0wIvjZP!`U5{yH0@2=^kd4;H9A;*AP-hA=mDW`(%_#g!mk^e{!P7L@?iR=eRgU1 zH|@X&nsSdmyCX1loO2!4N9e`!yO;!_XBc{JwR+e^?|^*I^;?(k!M|PQ1#vpp|cdp}am5dkIgjk`@@_xylA--o4?HZzcYZCewFjT`+q_<4^!{HG7#eUKI zAVmFBRP6t(TYRVDL6_epnt_=n?Ud8+Y@7b3NWPf=O}R9zv0WShx1acaIpJ(%TCF1Aarf=K~m*-R$Z{_a_clBWWQa&L0 ziQISUA#3zjjY2E`TtG^s29q z+SFtFtEX|fOCRI!N<1ZcK;-X+$j1#|tigT{zr)|F$h+wSsi~WV$3GnrSXg?NqhBCT zzB&SRJigP5+i_Do_n%*$bT%jY$?=v>&d05JUj~SLflh6DPv!*9cjm>?k3<3a6_~fyg&Vrg57&PqhUZ3u3*#9L*v**Wfd)mmR6S;km;rwY#|N5*G6 zeC%td9qjX^GJkww#x=<-c}j3<3ez(LVFz3wdh-kfAMNgjO~-M*jb990zK6x{6hY*B z{b1H#B7FFV{8>XEd=I?{coFa-aIQySLGe1e3ip>}H0!gdY+(M!<(WUzMQ-{z+vr|p zxcsg12d>}763nmkTkU{F-&Ssw=3gS;a|oyufcf{sRW-L&g~kqPx}1+o#1GK?LsQs4 zlxFv5y8AFEAM$Wx%QVhsSBKM+U%1PEw)?v@h2vT;GkfuEAnU9((mtr?R`K^kv+VhU zeNJ(GBIma)DR91DO1XZkA6TB#e9slTpPKM>Owv-my!h&Nel7rXp&iUGRg`PAQtp32 zzDnpHRp)xNnO0|Csv!NIM)6H08nUO?|vyXp_VPi1!WsX5{x~ zh;c(ckBR;Zf-eTa)gnjz-~T6Y|AGG>szZ^=iWirp)M8wIzy_%rR#cDmY{ zJ&q!7e#c3FT+zAisj=EAcNm9(s4d^AySa!nU$}1gBo$;R5 zjNi!povxQLZQ7gkzwKG?ap@nX+@rk+oQ?=gT`?u#eWhw2yr{EZ^$w%e%vqZ8|8~%J zFJ9VF0*-6LEGRi`MrU1~BP|%$UB)KB zw--#-jF?o$pbia%`VVnDRe&e+X2RhPG6Q^v{5IgVS4 z)q#nJUIe@dcoFa-;6=cTfENKT0$v2X2%NL8C_@BW0w5tnE(5CZ>F`9m2FJu0mKiD3ABCozps{$V_!L(`LxB~O{><*m2sed>h z=hy!R#-hjAE@e+(``n)~e}2Be`KRV*e&$7tH46mRGt1%8UIfnO2w**gGGhG*`g}(` zw}bUou=--XXkw`A?N>MOJX7NR!1K_i_X6{Sl?(D>|FV7ju9Ncve>ugW+c^JHGT#UD z6ZtpmU;8Ix&pbT8wkjNWp8b74>uvsqajDGD553EDu>5i=f94JL-+60j`7CrfU*nKeujK{7@1J}{%lj^843;1EBOVRYbq222@m!{vKd!?Ux#ahh@bO&U zma1G|tBMmpzn*Dzv$wI@AKEic-|t>5hbc?h z^kUw1T;JZM1DDe*O^>@oIe%X)nzF1o!Cpm*YkH612PHV)CW6-sc9L=eiwX^_Ec9!lubtAL zNsW1;tJ;JS{F{VsY49P>DPsQ%`);Nm>^tN6`&H`if!&fK)0FXy+d@_sSkVBtSm z_(IdfK|d#9Sn3RiL&r`$BJ=vgf_3CQ?CYpxss-zaj6x*dl1kP5L=G+qj{r zA8vY%I0Tyh`K=k21vKT)81YxtZb|3m3nDA&=dnzE(CIa<3n=f%xSTOf{{2W?JA$~3piwa;z zsVC~2-4ZpDdN&Y69GVFIdR}Mzi%|w0)q3eHO;|tTcOWPx?lXW7H0969yaGND-!*qz z=VCpI`&O{?@YF_NVu1HWhLWS(?|xRwdrvMfepeET&r_lEvXsZf)xv*45ao6&!{-vG zpXB#XI|YC5=KKz5;g8wFdU;N_h|u+F^Y=4x`YS4Pz*D`Q=glv+JEzU7onq@vOk7gX zN8e%n+%@<_H&Xcc9_%v>oaJ+?-z)F3pJxT#^uop8{~qV}h2SE=Zi1*L5Z|kHL&O{9 z>--EXnS=XzxIYZUbuR2qHDcae=rT_R%OCf1aX;47$MXcZ?}~l7S0q2|*C8IJJ^bG- z`q)PR-_-YLF9JD+4laOmfI%{jh(a-GoTLZ{x#dDslU z=iaxll4fjwoN=Rg1v)YbK-`|6{XcLiV~J&4j^cs=OgxqsfwMLO-J3ht2fLD)Zu?!? z6P9_}F&4LapU>l)cJg&j#5>Gi(VG3V8_nZvQAOs*e8}Sk&KI_F=6_dv_Otid3hY=E zH$B@>AJtvX2>VlomHVEae1erfbvehQV?XD0BT>fav@!#8+v2PQ`KGHAp zbmL~YNU*qE-w!Xy*Z0+0uzo)AbHP58%!#?&_+z+#K>L{Z0sV#1Kf(9Vi+~q_>_-64 zC*XSE)gad)*e}L)1on&Zo|5dB;KUXv7p`MW?QrLhdf<9$Yo4ll!W%y3V|}7ZSx{RS z;aJrgj-vFA=kwT<)WGwyLpcC(@3G37n=H7uD(?)WMKJE0b-gY}fF7e9A$gpR|Y=XriJ{qFp7 zlP>?(zc~KeYp~w7H$t0llkT`&yPPKVnQhcdW!|6mU<v?a&y%V@?*(-~(Rnxyjwj%^xRoTsn6 zT4(u@fu9K-tUPC0j&^>xGneFZqaOq-AMAnXC%|C#&;th34<<){9P08O$6)rJ+>3x0 zf&Wef9+i3eW5J?w9=bOlpNE1)V)Gha7vj4ervCj~oa^a)q!1(bYU2p6AzGoA!W)4O z7vG{Ce%T^k<7**azdkGLwQ5TOUoVEMl=nn&xqffWX!09*rX|{9w)oQj;vtef4S}sl>4~k*CuJT+{@9KR0b@4mAPtm>G2yNI+!sDV#^RsCQj$ajXa9MB_T-&%Vqr(2p^yC*z)7a^q6+%jx^3n;F;B$txnX z$A$)9vT=p{OVmtgSzk|ac9<)tI%!u2ITX09Gv6|@#d;QKX6NPydP})_Ag$}w;IpMX zZYK=Ud2b%?6T)QAVkiBUJfMhmE)aa!4Kdci=szz@Kbc*L_aUbWZkK-Yp8UhMNu+pkp~ z5xh|L1IItd`+}dz{0u+eCGdLSXF>2!7v3uMzwIn}UT*v{K9BByI`H}QeQD1pjX0jJ zc%7+y?rH3Nu4G9QrXQ7lcu@M!Wzx?(8T;3~yY4T2qrUvRhR0evPUF^1a(+v@nSZzN zo0ya`@~_a;Ssr-KKc0U#@$EQ9@I%F8132FV`Q1b?yB~+LeuUsl)L3K`KwA z2Lw&|-Ure(Yq(%)Bd%uyqyDB{xab{{pXqP9w71DWQ*_1jAv)_3vR=D%9G@4qH{tc# zlX8CF^H1RQs#)%aKk|Cx@EYFV#(E35sQor=vSTuTxACtf+@n(yZ+CRygC9B{@ECG8 z(*>`4L+kw}-)k)N%}&Qc^Xu{NX?pL%?vA2i9oTmf=NBu8`oEFMakx)#-$ zK6j3acz~PF`d#pd;Kzcd(|(d~TfrZrIp1x9rhZ@1Gx;Xn-dJCma?p2)-kcEiV+=jq z=l@Zjr-xr4o;&!)MGn1Scp!*<$N$Llo8u(@HyZ5_EPkQN^9HAfEfi^VXp8e(1mYaQoGccD5thN1Z1hqw%m= z^b!lO{|}{oSMTTXjNhS}B^q`U`yJPp5$%lk44C>RjoWMmzbytHGsS|$b38;T#Exa&`l9?%$g>uZ0r+)DFJZSa_E_eev(tne5rswk=HA2X-skX zu5+k*s5j6oFZe)H-sB)Z7ZTl(v1kuQH~!z5d>?D;a7L55~C^aT$}mNM`zD((Ryq*y}pazDqQZ1 zYcV2UxAH#mHpjDQLH1W>htqD}TYL5N*W+^LU)k3wM>`^)P%)==J^n1GvoP1&r<%t8 zp^0w!4uACzeM+cNx1cWXu13x~)Fur-h@Tt%XFJw_9CRLee_xZiEKgYJd_QW&v#YeA zcYaH*U>f!g7_-@HE5B%dg2>RgT`ULI$i`>run)>jcdmQ(>Pj6-1B6y2D zX8;@{^g?-F#Y8jTpCy0r@g6PQ_bZVVaOVN^aX)6i;pcmyao-2+7b-e)V@gkqbO;Qj zb2ji$KA^o!eGgozC!`ta{Jc$S9AnHU{JXo+F>G~hU&c0l_&p92WZew}|Dj&2cbni> zRZ}%1{b}ZJevI*C;;zcCyx6CdaQNer*`ve9s;YN97pcW+*{&jFrzI4uOn=pgxU=t; zEPYirJFVp3UgV>{mbL6yyhf#5i2eoQt26J$iTr%?z7Avg%HauquKDh#7`2mZ=zSpX zvozn+nJ(u~;DJi)d7J2ikNiLbp+7_JXO`yEqV>)^wg4aY<$Lpm(kmZv^fw=5OcAU& zNG_CMAea=-{FqoqWhGfUG~4fbSkq1Uv2SaeT(=z=%M~lNp5?_wzEtERjQ5S$ovIL_ z{XhGQ*PAN;qAq+1sojM>E_NJ;8H$hh*R{NgdCLEl^bg}~c~2gPim&Fxds;I6x4Z{M z@x_#moT(&{7~i?S;&rgnzuJYb^wXpgNrFmWPW^P`Pqp$m*VT|s_uh)8JTpy<8spa>S2yghQw!iBtI7z z^C^#~A3tHdE|D7o{9PY2ALYy=&#~^7=c+#ygj`_-`UiVut%jJLR&uN}@Vz)<>Ey;w z$oH8(Z~c7iXWi}#UcFGS_`mvyf7pDLLwkH`Jmn8Sf{(mgW3Pr-P7J z;1$wWeA=<{3Ox_c&$i`#gPD_feSvk#OgZ0vvcS1sSuXQ@l$=*F|KqjT)kF^eUFH3- z?L>d*m%LE<-y?jYDz+vGq1L$bix+gxA01@G+4SM1o3yM)Pt3!9zg%O_DRXo=Z>&3AWB-I{8pnLgzav0B3(R8q?ggu~>%_*3wI1>rcZh!% z0(#i+G=2D23M1B2xn$jj^$!s1ou0Dp!TP3%taI@DRlFAp>yKu#&cS*DSp9ikvE6p` zZ$~*Z7QU>@HG5$f&Hrp~PrW|q_IJAGj~vDAnecB>9UrVCa(&I~2-FYjiDlPT()Ier<Z_O^V-Wt zIih%9zWKg-+&@||Vuv2Dlb(M?XH`HD`HsA$p&obTeygu>>`^{X0RO&~-y8D8?|k34 zV1C))6bFNE?CBVPowOIr0yg=%heM8hfu;ZIuI0&nKGK%ozr`IG2tC~YikQvwEchmd zs%P4rE*kBJ>mhS}X_h0q`sRKU?kgOW`vCACs$SjhZ1K}p#yUa+;SbpBw{5xs4;+45 zHN}+E6OnbxtQO>wk3_R?{LPUa)0;IS*+J@4x_b~-zPA3 zH{&h47*Q|8Jqxp|pJixSH_&lBFBdJf7>h7yCFfliKdQl7-GyI`_<<7dz#K18{3atR z>j_BxX;1%fe^!-8$zR*b`+G`Wi~b=#idIjqqTi@;X-$cTlB*|Drq|?sS4FEW`icE` zw^-3F=-&bPj*ZH%75%gOZ$qTr|BW`eI#kIg?ZlMruapCvV0yD}drXg;3wCmQ@BD-j z_9#~{y@oH!gq&r_2XD*wA+{LbiEvwYpub`Kz@6K9-Hi`Bg`I>@2z_B42f^g~T!ujy@Z+WixrvA)$_GR_-Ta*@{-z>B_4!x*Q9I}tNk3JzYL~}etfo7V zqC3;SvhtjYqPx&Pd+J8S&Ho##F9mNJeTznn7a+#-gYvuu?hhsn*{ob;nIm z(uc^uU|m^|ibB}|vQZ^;^4=0Kbs)ya)9v4O@GpAGX}|AY&d;Gl1jZxQqmW}h0qusc zf6jOg%}w9r<9_$bRdpPqGI#3BJO?ij0sGBSeBV5f&WU)iU(%QNbD(>{w~p=)_HhXN zFSqIKzASl-m}|3+)j`IUfuA1uYt#YV|2}BX2%}K@GuF4T1NM=1FK9O`Ea#(x#=PpL zZ}Q#d7o49=eUpZLsQt~&9_Kex9*jo*IR6FHH|4N5`6lh=KU90S`h{wb^?+HfQ)TZ~ z-%#@(hWiU*&^Pe#v0iV?PYac5#Ax~tM#Fx}SiVkn)4#+J;C|yjwI#W~Hy`AMDgMn>vpxzIw)Hovf$Y((x$aKS&7u zmUR4(@YfT%rTb)EJXR3)7^k3LGS*-4hkAnss&aly|4tPvmEq=dev@OpT|1q^B*o_%uF)Gk zqUpsO9@40+^4eAid2Zx=j2YK6t~;X5tWuFL>VrEAGb+39Sfu~L?&`{H_ng>`j$r+Q z2Ulu6W%qpmyJMBuF1`>KX7Pn=x9>N<-4B86s<2)ApIF$SA=^Ds%3sbOZFc7KzOl0D zQQ|Jn7pTf9F12;SvLCd<`a+C{US|C=Px9|s7aZf?w<^1Xw*0=opq{24IHYzvsGUAw zySJ*;(ezKn7?s`6n!|r^-GS$d=Tv2We<^3Qpt3t;v%`MLo7?#MXTcO6_e<(trBtn6 zS28N%7n>dWJMIr>a(A(MF5>b6mEE5K>@XkRC9=9QZfXiDyT5F9s2|p4n`*MK${n0v z1wmz(hgn(y{d&R+t9AeFx~{b5E4^|yJ*?XvlXY8ektw~4ZF)uI_soCG`>4ulZ#>^z zV$<9H3$K5+2|mHYP|r*GZF+d#vf?D(VO;y~6pI`1Q&J z(97)3dgE+*_ABN-6Q9>J2KLY9OzojZ2+C_>uKc>%S-*3t*6elt;4JKcJ-Z#$eAYhs zzbJ=VKeVNKs<{MT%o_{pFH|- zk4!2!Z@pfg^;eczqvhYuo2jwzT)wW4sKgmBew)uvQN7vY4no1;1H*=BdhT!KG|u*w(O9wppXX8juctcevsC7_gK!%zQ*w$dp%{#5)6p1;1&- zaU6*6pc(C%b^k*O^hOld8|6bh@{VQBfIoR12Kj6G`N86Hck%oNeaKDvnrmfW`T0XS zBis*d`3YNK-BDZ|45-cVZpO`JjTz!BUkQGlto7(nHo$rW{dU0er?ueGf;?gNllKW# z8qe+ZXt8viL6s$ZeTa3+YFVe0dG!Y!v8#(W(0-b|R9EY_-@*N5!)x<&ffo1Ve!4f; zYOR0Zm+Q3tgb%*g`soWJG>v#n=jp}5I%Z$oCN0N$g}qzW|2kIDeEiwah8KF<|K$FR zbxYztevYEwFlT&_UuqBb06Mw?>w$hk`Zv}q-`8U~)+MiqTdW(_e83$E=fN4jb3EY> z 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 diff --git a/src/distribution/Microsoft.Native.Quic.MsQuic.XDP.OpenSSL.nuspec b/src/distribution/Microsoft.Native.Quic.MsQuic.XDP.OpenSSL.nuspec deleted file mode 100644 index 4aa0b5317c..0000000000 --- a/src/distribution/Microsoft.Native.Quic.MsQuic.XDP.OpenSSL.nuspec +++ /dev/null @@ -1,18 +0,0 @@ - - - - Microsoft.Native.Quic.MsQuic.XDP.OpenSSL - 0.0.0 - MsQuic XDP (OpenSSL) - Microsoft - MIT - pkgicon.png - README.md - https://github.com/microsoft/msquic - true - © Microsoft Corporation. All rights reserved. - MsQuic native library for x64, x86 and arm64 using openssl for TLS and XDP datapath - - native quic msquic openssl xdp - - diff --git a/src/distribution/Microsoft.Native.Quic.MsQuic.XDP.OpenSSL.targets b/src/distribution/Microsoft.Native.Quic.MsQuic.XDP.OpenSSL.targets deleted file mode 100644 index e877f662b6..0000000000 --- a/src/distribution/Microsoft.Native.Quic.MsQuic.XDP.OpenSSL.targets +++ /dev/null @@ -1,21 +0,0 @@ - - - - - $(MSBuildThisFileDirectory)include/;%(AdditionalIncludeDirectories) - - - $(MSBuildThisFileDirectory)lib/x64/msquic.lib;%(AdditionalDependencies) - - - - - - - - - diff --git a/src/distribution/Microsoft.Native.Quic.MsQuic.XDP.Schannel.nuspec b/src/distribution/Microsoft.Native.Quic.MsQuic.XDP.Schannel.nuspec deleted file mode 100644 index 30a253ca4e..0000000000 --- a/src/distribution/Microsoft.Native.Quic.MsQuic.XDP.Schannel.nuspec +++ /dev/null @@ -1,18 +0,0 @@ - - - - Microsoft.Native.Quic.MsQuic.XDP.Schannel - 0.0.0 - MsQuic XDP (Schannel) - Microsoft - MIT - pkgicon.png - README.md - https://github.com/microsoft/msquic - true - © Microsoft Corporation. All rights reserved. - MsQuic native library for x64, x86 and arm64 using schannel for TLS and XDP datapath - - native quic msquic schannel xdp - - diff --git a/src/distribution/Microsoft.Native.Quic.MsQuic.XDP.Schannel.targets b/src/distribution/Microsoft.Native.Quic.MsQuic.XDP.Schannel.targets deleted file mode 100644 index e877f662b6..0000000000 --- a/src/distribution/Microsoft.Native.Quic.MsQuic.XDP.Schannel.targets +++ /dev/null @@ -1,21 +0,0 @@ - - - - - $(MSBuildThisFileDirectory)include/;%(AdditionalIncludeDirectories) - - - $(MSBuildThisFileDirectory)lib/x64/msquic.lib;%(AdditionalDependencies) - - - - - - - - -