Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Client Migration (Client Side) #4218

Open
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

masa-koz
Copy link
Contributor

@masa-koz masa-koz commented Mar 29, 2024

Description

This PR adds the support of Client Migration (Client SIde). Fix #1946.

This pull request includes significant changes to the QUIC connection ID management and introduces a new feature for manual connection ID generation for testing purposes. The most important changes include the replacement of QUIC_CID_HASH_ENTRY with QUIC_CID_SLIST_ENTRY to register connection IDs into more than one bindings, the addition of new APIs to add/remove local address in client, and the introduction of a conditional compilation feature for manual connection ID generation.

Added QuicConnOpenNewPath and QuicConnOpenNewPaths are to manage opening new paths for a connection. These functions will be called when Destination Connection ID is available for sending PATH_CHALLENGE.

Testing

I've added two test.

  • Basic/WithProbePathArgs.ProbePath/*
  • Basic/WithMigrationArgs.Migration/*

Documentation

In this PR, the following two Connection level Parameters will be added.

  • QUIC_PARAM_CONN_ADD_LOCAL_ADDRESS
  • QUIC_PARAM_CONN_REMOVE_LOCAL_ADDRESS

@masa-koz masa-koz requested a review from a team as a code owner March 29, 2024 10:21
@masa-koz
Copy link
Contributor Author

@microsoft-github-policy-service agree

src/inc/msquic.h Outdated Show resolved Hide resolved
src/inc/quic_platform.h Outdated Show resolved Hide resolved
src/inc/quic_platform.h Outdated Show resolved Hide resolved
@@ -2523,7 +2523,6 @@ CxPlatRecvDataReturn(
DATAPATH_RX_IO_BLOCK* IoBlock =
CXPLAT_CONTAINING_RECORD(Datagram, DATAPATH_RX_PACKET, Data)->IoBlock;

CXPLAT_DBG_ASSERT(Binding == NULL || Binding == IoBlock->Binding);
Copy link
Member

Choose a reason for hiding this comment

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

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When a connection binds more than one bindings (i.e. after adding a new local address on the client side), we cannot assure that the Datagrams in RecvDataChain come from the same binding.

src/test/lib/PathTest.cpp Outdated Show resolved Hide resolved
QUIC_PARAM_CONN_STATISTICS_V2_PLAT,
&Size,
&Stats));
TEST_EQUAL(Stats.RecvDroppedPackets, 0);
Copy link
Member

Choose a reason for hiding this comment

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

How does this line help? Why would the client drop a packet? Also, are there other test conditions we can add here to determine if the new path worked?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The intention is that I would like to check whether the client succeeded in adding the source connection IDs to a newly opened bindings (if it failed, it will drop a packet whose destination is a newly added local address).
And I think that an event notification about the validation of a path is helpful for both testing and real application.

src/test/lib/PathTest.cpp Outdated Show resolved Hide resolved
src/core/binding.h Outdated Show resolved Hide resolved
src/core/path.h Outdated Show resolved Hide resolved
@@ -746,8 +768,24 @@ QuicLookupAddLocalCid(
Hash);

if (ExistingConnection == NULL) {
Result =
QuicLookupInsertLocalCid(Lookup, Hash, SourceCid, TRUE);
QUIC_CID_HASH_ENTRY* CID =
Copy link
Member

Choose a reason for hiding this comment

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

Just a thought, but we might need to start using pools for allocating and freeing all these entries so they don't hurt perf.

src/core/connection.c Outdated Show resolved Hide resolved

BOOLEAN Collision = FALSE;

CxPlatDispatchLockAcquire(&MsQuicLib.DatapathLock);
Copy link
Member

Choose a reason for hiding this comment

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

Why do we have to grab a global lock here? That's not great, and definitely could cause perf issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree with you. I've tried to remove a global lock in masa-koz#5.
In this change, we will replace all the existing source connection IDs if we find the collision in adding a source connection ID to a newly opened bindings.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I`ve committed it in 5da50d7

src/core/connection.c Outdated Show resolved Hide resolved
Comment on lines 6675 to 6678
if (!Connection->State.Started) {
Status = QUIC_STATUS_INVALID_STATE;
break;
}
Copy link
Member

Choose a reason for hiding this comment

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

Why not? It seems kind of intuitive to me that a client could create a connection, queue all paths it wants to use and then start it. No?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree with you. So, I've also implemented this. masa-koz#4

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now, we can add local address before connection start. And I've added tests for adding/removing address.
2b3c6fd

src/core/connection.c Outdated Show resolved Hide resolved
src/core/connection.c Outdated Show resolved Hide resolved
src/core/connection.c Outdated Show resolved Hide resolved

QUIC_CID_LIST_ENTRY* NewDestCid = QuicConnGetUnusedDestCid(Connection);
if (NewDestCid == NULL) {
Status = QUIC_STATUS_INVALID_STATE;
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we can support a model where we always queue the path (somehow) even if we don't have a CID to use for it, yet...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Thanks. The hardest part with this change overall will likely be that we need to make sure we have lots of tests for all the different scenarios.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now, we defer sending a path challange if there is no unused ConnID. So, I've removed QUIC_PARAM_CONN_LOCAL_UNUSED_DEST_CID_COUNT.
And I've modified Basic/WithProbePathArgs.ProbePath for testing wheter a path challenge will be sent when the peer sends a new ConnID.

c6f7e3d

src/core/connection.c Outdated Show resolved Hide resolved
src/core/connection.c Outdated Show resolved Hide resolved
Copy link

codecov bot commented Mar 29, 2024

Codecov Report

Attention: Patch coverage is 78.62069% with 93 lines in your changes missing coverage. Please review.

Project coverage is 86.33%. Comparing base (f16a46b) to head (e5e4c7b).
Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
src/core/connection.c 75.42% 72 Missing ⚠️
src/core/configuration.c 28.57% 10 Missing ⚠️
src/core/loss_detection.c 40.00% 3 Missing ⚠️
src/core/binding.c 94.28% 2 Missing ⚠️
src/core/lookup.c 94.44% 2 Missing ⚠️
src/core/path.c 81.81% 2 Missing ⚠️
src/core/crypto.c 88.88% 1 Missing ⚠️
src/core/settings.c 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4218      +/-   ##
==========================================
- Coverage   87.20%   86.33%   -0.87%     
==========================================
  Files          56       56              
  Lines       17363    17696     +333     
==========================================
+ Hits        15141    15278     +137     
- Misses       2222     2418     +196     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.


QUIC_BINDING* Binding =
CXPLAT_CONTAINING_RECORD(Link, QUIC_BINDING, Link);
QUIC_CONNECTION* Connection1 =
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use a more descriptive name instead of Connection1? Like SearchConnection or CollisionConnection

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. I will change this.

_IRQL_requires_max_(PASSIVE_LEVEL)
static
QUIC_STATUS
QuicConnRemoveLocalAddress(
Copy link
Contributor

Choose a reason for hiding this comment

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

There doesn't appear to be any test coverage for the Remove address function here. Could you add a test for that?

@@ -221,6 +243,7 @@ QuicConnGetPathForPacket(
&& QuicAddrGetFamily(&Packet->Route->RemoteAddress) == QuicAddrGetFamily(&Connection->Paths[i].Route.RemoteAddress)
&& QuicAddrCompareIp(&Packet->Route->RemoteAddress, &Connection->Paths[i].Route.RemoteAddress)
&& QuicAddrCompare(&Packet->Route->LocalAddress, &Connection->Paths[i].Route.LocalAddress)) {
QuicLibraryReleaseBinding(Connection->Paths[i].Binding);
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is this reference added?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the case of server, QuicLibraryTryAddRefBinding() was called in QuicConnGetPathForPacket(). In the case of client, QuicBindingGetLocalAddress() was called in QuicConnAddLocalAddress().
By the way, the non-null check should be added.

Copy link
Contributor

@anrossi anrossi left a comment

Choose a reason for hiding this comment

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

First of all, your changes look great, thanks for doing this!
I'd like to see some more tests. Since you're adding new APIs, could you add some negative test cases for SetParam(QUIC_CONN_ADD_LOCAL_ADDRESS)/SetParam(QUIC_CONN_REMOVE_LOCAL_ADDRESS). For example, tests that add/remove an address that is not valid, add/remove an address before the connection is started, add the same address twice/remove the same address twice, a test that adds more addresses than the maximum path count, and one that adds an address that isn't on the system, but is valid, e.g. 100.64.10.20.

In the Path tests, I'd appreciate a test that adds an address and removes the old one and verifies no traffic drops. If you have time, also a test that removes all addresses from a connection.

Thanks again!

@anrossi
Copy link
Contributor

anrossi commented Jun 18, 2024

@masa-koz Are you going to have time to add the requested extra tests? If not, do you mind if we add them later?

@masa-koz
Copy link
Contributor Author

@anrossi I'm sorry for my TOO late response.
I've added some tests for SetParam(QUIC_CONN_ADD_LOCAL_ADDRESS)/SetParam(QUIC_CONN_REMOVE_LOCAL_ADDRESS) in
2b3c6fd. Currently, not all the cases are covered. I will add them later.

src/core/connection.c Dismissed Show dismissed Hide dismissed
Copy link
Member

@nibanks nibanks left a comment

Choose a reason for hiding this comment

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

A few comments on the test code

src/test/bin/quic_gtest.h Outdated Show resolved Hide resolved
QuicAddr ClientLocalAddr;
TEST_QUIC_SUCCEEDED(Connection.GetLocalAddr(ClientLocalAddr));
TEST_QUIC_STATUS(
QUIC_STATUS_INVALID_STATE,
Copy link
Member

Choose a reason for hiding this comment

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

Can/should we really block removal of current active path? In the future, removal can/should be in response to the address being removed from the system itself (i.e. Wi-Fi gets disconnected). You can't say 'no, you're not allowed because I'm using it'. So, really, I think we need to allow removal, but if that's the last one, the connection just dies.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree with you. How should we handle the challenge? Separate PR?

Copy link
Member

Choose a reason for hiding this comment

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

Ideally I think we need to have it in this PR.

src/test/lib/PathTest.cpp Show resolved Hide resolved
src/test/lib/PathTest.cpp Outdated Show resolved Hide resolved
src/inc/msquic.h Show resolved Hide resolved
@nibanks
Copy link
Member

nibanks commented Dec 27, 2024

This PR is big, so would it be possible to add more detailed description of what exactly is changed and why?

@nibanks nibanks requested a review from Copilot December 27, 2024 16:25

Choose a reason for hiding this comment

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

Copilot reviewed 17 out of 37 changed files in this pull request and generated no comments.

Files not reviewed (20)
  • src/core/binding.c: Language not supported
  • src/core/binding.h: Language not supported
  • src/core/cid.h: Language not supported
  • src/core/configuration.c: Language not supported
  • src/core/connection.h: Language not supported
  • src/core/crypto.c: Language not supported
  • src/core/inline.c: Language not supported
  • src/core/library.h: Language not supported
  • src/core/lookup.c: Language not supported
  • src/core/lookup.h: Language not supported
  • src/core/loss_detection.c: Language not supported
  • src/core/packet_builder.c: Language not supported
  • src/core/packet_builder.h: Language not supported
  • src/core/path.c: Language not supported
  • src/core/path.h: Language not supported
  • src/core/quicdef.h: Language not supported
  • src/core/send.c: Language not supported
  • src/core/settings.c: Language not supported
  • src/core/settings.h: Language not supported
  • src/inc/msquic.h: Language not supported
@masa-koz
Copy link
Contributor Author

@nibanks I've updated the description. If any missing, please notify me.

QUIC_CID_SLIST_ENTRY,
Link);

CXPLAT_SLIST_ENTRY** Link1 = &Entry->HashEntries.Next;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
CXPLAT_SLIST_ENTRY** Link1 = &Entry->HashEntries.Next;
CXPLAT_SLIST_ENTRY** HashLink = &Entry->HashEntries.Next;

Let's use HashLink here instead.


CXPLAT_SLIST_ENTRY** Link1 = &Entry->HashEntries.Next;
while (*Link1 != NULL) {
QUIC_CID_HASH_ENTRY* Entry1 =
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
QUIC_CID_HASH_ENTRY* Entry1 =
QUIC_CID_HASH_ENTRY* HashEntry =

typedef struct QUIC_CID_SLIST_ENTRY {

CXPLAT_SLIST_ENTRY Link;
QUIC_CONNECTION* Connection;
Copy link
Member

Choose a reason for hiding this comment

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

Do we really need the Connection pointer since all the hash entries already have it?

if (Entry1->Binding == Binding) {
QuicBindingRemoveSourceConnectionID(Binding, Entry1);
*Link1 = (*Link1)->Next;
CxPlatListPushEntry(&EntriesToFree, &Entry1->Link);
Copy link
Member

Choose a reason for hiding this comment

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

Why have a separate list that you have to iterate again later to free? Just free here directly.

Comment on lines +6369 to +6370

QUIC_CID_LIST_ENTRY* NewDestCid = QuicConnGetUnusedDestCid(Connection);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
QUIC_CID_LIST_ENTRY* NewDestCid = QuicConnGetUnusedDestCid(Connection);
QUIC_CID_LIST_ENTRY* NewDestCid = QuicConnGetUnusedDestCid(Connection);


case QUIC_PARAM_CONN_REMOVE_LOCAL_ADDRESS: {

if (BufferLength != sizeof(QUIC_ADDR)) {
Copy link
Member

Choose a reason for hiding this comment

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

Let's move QuicAddrIsValid(LocalAddress) to here, instead of in QuicConnRemoveLocalAddress.

CxPlatHashSimple(CID->CID.Length, CID->CID.Data),
CID,
FALSE);
CXPLAT_SLIST_ENTRY* Entry1 = CID->HashEntries.Next;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
CXPLAT_SLIST_ENTRY* Entry1 = CID->HashEntries.Next;
CXPLAT_SLIST_ENTRY* HashEntry = CID->HashEntries.Next;

CXPLAT_CONTAINING_RECORD(
Entry,
QUIC_CID_HASH_ENTRY,
Entry);
(void)QuicLookupInsertLocalCid(
Lookup,
CxPlatHashSimple(CID->CID.Length, CID->CID.Data),
CxPlatHashSimple(CID->CID->CID.Length, CID->CID->CID.Data),
Copy link
Member

Choose a reason for hiding this comment

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

CID->CID->CID is a bit ridiculous. 😄 We should figure out how to get some more descriptive names.

Link1 != NULL;
Link1 = Link1->Next) {

const QUIC_CID_HASH_ENTRY* const Entry1 =
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const QUIC_CID_HASH_ENTRY* const Entry1 =
const QUIC_CID_HASH_ENTRY* const HashEntry =

QuicAddr ClientLocalAddr;
TEST_QUIC_SUCCEEDED(Connection.GetLocalAddr(ClientLocalAddr));
TEST_QUIC_STATUS(
QUIC_STATUS_INVALID_STATE,
Copy link
Member

Choose a reason for hiding this comment

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

Ideally I think we need to have it in this PR.

@nibanks nibanks added Area: Protocol Updates Changes for new protocol changes external Proposed by non-MSFT labels Jan 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Protocol Updates Changes for new protocol changes external Proposed by non-MSFT
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support Client Migration (Client Side)
3 participants