Skip to content

Commit a9b3e11

Browse files
committed
Resolve a forward request once, before its send
A request looked its registration up again at commit, after the send had run the application's send and highwater callbacks. A callback that reenters the library sends behind the request that ran it, so the lookup found what the callback did rather than what the request asked for: a cancel from a first setup's send found nothing to take, and that setup's commit then registered the forward the peer had just been told to drop. - Requests in flight hang off ssh->fwdPendingHead for the length of their send, so a reentrant request resolves to the forward the request it interrupted is registering, not to nothing. - FwdRemotePrepare() settles what the request names and holds it on the pending; FwdPendingCommit() uses that instead of a fresh lookup, which also drops the dedupe branch the double lookup needed. - FwdRemoteUnlink() voids the pending pointers along with the queued slots, so freeing a forward mid-send leaves no commit naming it, and FwdPendingDiscard() goes through it rather than freeing raw. - FwdRemoteMatch() reads the pending list under the rules the scan over registered forwards uses: a setup mid-send is on the wire and can feed channels; a cancel behind it or a refusal on its slot stops matching. - Drop pend->bindAddr and pend->bindPort, which nothing reads now. - Tests drive a cancel, a setup, an inbound forwarded-tcpip open and a mid-send REQUEST_FAILURE from the IO send callback, the window the highwater tests no longer reach. Issue: ZD-22195
1 parent cb45c12 commit a9b3e11

3 files changed

Lines changed: 475 additions & 36 deletions

File tree

src/internal.c

Lines changed: 165 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4001,6 +4001,89 @@ static void FwdReplyRebind(WOLFSSH* ssh, const WOLFSSH_FWD_REMOTE* from,
40014001
}
40024002

40034003

4004+
/* Put a request on the session's list of requests in flight, where anything
4005+
* its own send reaches can see it. */
4006+
static void FwdPendingPush(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
4007+
{
4008+
pend->next = ssh->fwdPendingHead;
4009+
ssh->fwdPendingHead = pend;
4010+
}
4011+
4012+
4013+
/* Take it back off, its send being over. Requests nest, so this is not always
4014+
* the head: a callback's request commits inside the one that ran it. */
4015+
static void FwdPendingPop(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
4016+
{
4017+
WOLFSSH_FWD_PENDING* cur;
4018+
4019+
if (ssh->fwdPendingHead == pend) {
4020+
ssh->fwdPendingHead = pend->next;
4021+
}
4022+
else {
4023+
for (cur = ssh->fwdPendingHead; cur != NULL; cur = cur->next) {
4024+
if (cur->next == pend) {
4025+
cur->next = pend->next;
4026+
break;
4027+
}
4028+
}
4029+
}
4030+
4031+
pend->next = NULL;
4032+
}
4033+
4034+
4035+
/* Forget a forward every request still in its send window held a pointer to.
4036+
* Those requests resolved it before the send; freeing it without this leaves
4037+
* their commits naming memory that is gone. */
4038+
static void FwdPendingVoid(WOLFSSH* ssh, const WOLFSSH_FWD_REMOTE* entry)
4039+
{
4040+
WOLFSSH_FWD_PENDING* pend;
4041+
4042+
for (pend = ssh->fwdPendingHead; pend != NULL; pend = pend->next) {
4043+
if (pend->entry == entry)
4044+
pend->entry = NULL;
4045+
if (pend->found == entry)
4046+
pend->found = NULL;
4047+
}
4048+
}
4049+
4050+
4051+
/* A forward a request in flight is registering, or NULL. It is not on the
4052+
* session's list until that request commits, but it is what the peer is being
4053+
* asked for, so a request a callback sends meanwhile names the same one. */
4054+
static WOLFSSH_FWD_REMOTE* FwdPendingFind(WOLFSSH* ssh, const char* bindAddr,
4055+
word32 bindPort)
4056+
{
4057+
WOLFSSH_FWD_PENDING* pend;
4058+
4059+
for (pend = ssh->fwdPendingHead; pend != NULL; pend = pend->next) {
4060+
if (pend->entry == NULL || pend->entry->portPending ||
4061+
pend->entry->bindPort != bindPort)
4062+
continue;
4063+
if (WSTRCMP(pend->entry->bindAddr, bindAddr) == 0)
4064+
return pend->entry;
4065+
}
4066+
4067+
return NULL;
4068+
}
4069+
4070+
4071+
/* Is a cancel for this forward inside its own send window? It is on the wire
4072+
* ahead of anything a callback could send from there, so it is already the
4073+
* last word on the forward. */
4074+
static int FwdPendingHasCancel(WOLFSSH* ssh, const WOLFSSH_FWD_REMOTE* entry)
4075+
{
4076+
WOLFSSH_FWD_PENDING* pend;
4077+
4078+
for (pend = ssh->fwdPendingHead; pend != NULL; pend = pend->next) {
4079+
if (pend->isCancel && pend->found == entry)
4080+
return 1;
4081+
}
4082+
4083+
return 0;
4084+
}
4085+
4086+
40044087
static void FwdRemoteUnlink(WOLFSSH* ssh, void* heap,
40054088
WOLFSSH_FWD_REMOTE* entry)
40064089
{
@@ -4019,6 +4102,7 @@ static void FwdRemoteUnlink(WOLFSSH* ssh, void* heap,
40194102
}
40204103

40214104
FwdReplyVoid(ssh, entry);
4105+
FwdPendingVoid(ssh, entry);
40224106

40234107
WFREE(entry->bindAddr, heap, DYNTYPE_STRING);
40244108
WFREE(entry, heap, DYNTYPE_FWD);
@@ -4267,10 +4351,13 @@ int FwdRemotePrepare(WOLFSSH* ssh, const char* bindAddr, word32 bindPort,
42674351

42684352
heap = ssh->ctx->heap;
42694353
pend->isCancel = (byte)(isCancel != 0);
4270-
pend->bindAddr = bindAddr;
4271-
pend->bindPort = bindPort;
42724354
found = FwdRemoteFind(ssh, bindAddr, bindPort);
42734355

4356+
/* A request a send callback is making names the forward the request that
4357+
* ran it is registering, which is on the wire but not on the list yet. */
4358+
if (found == NULL)
4359+
found = FwdPendingFind(ssh, bindAddr, bindPort);
4360+
42744361
if (isCancel) {
42754362
if (found == NULL) {
42764363
WOLFSSH_FWD_REMOTE* cur;
@@ -4339,6 +4426,13 @@ int FwdRemotePrepare(WOLFSSH* ssh, const char* bindAddr, word32 bindPort,
43394426
/* An error leaves nothing to commit and nothing to give back. */
43404427
if (ret != WS_SUCCESS)
43414428
WMEMSET(pend, 0, sizeof(*pend));
4429+
else {
4430+
/* What this request resolved to is settled here rather than looked up
4431+
* again at commit: by then a callback the send ran may have registered
4432+
* the same bind anew, and this request went out ahead of it. */
4433+
pend->found = found;
4434+
FwdPendingPush(ssh, pend);
4435+
}
43424436

43434437
WLOG(WS_LOG_DEBUG, "Leaving FwdRemotePrepare(), ret = %d", ret);
43444438
return ret;
@@ -4370,18 +4464,22 @@ int FwdReplyPrepare(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
43704464
pend->reply = FwdReplyNew(ssh, 0, NULL, 0);
43714465
ret = pend->reply == NULL ? WS_MEMORY_E : WS_SUCCESS;
43724466

4467+
/* It names no forward, but every request in flight is on the list. */
4468+
if (ret == WS_SUCCESS)
4469+
FwdPendingPush(ssh, pend);
4470+
43734471
WLOG(WS_LOG_DEBUG, "Leaving FwdReplyPrepare(), ret = %d", ret);
43744472
return ret;
43754473
}
43764474

43774475

4378-
/* The request reached the wire, so link what was prepared. The registration it
4379-
* names is looked up again here: the send runs the application's send and
4380-
* highwater callbacks, which can reenter the library and free the entry a
4381-
* pointer held across the send would name. */
4476+
/* The request reached the wire, so link what was prepared. The forward it names
4477+
* was resolved before the send and held on the pending: the send runs the
4478+
* application's send and highwater callbacks, which can reenter the library,
4479+
* and a lookup from here would find what those did afterwards. */
43824480
void FwdPendingCommit(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
43834481
{
4384-
WOLFSSH_FWD_REMOTE* target = NULL;
4482+
WOLFSSH_FWD_REMOTE* target;
43854483
WOLFSSH_FWD_REMOTE* cur;
43864484
void* heap;
43874485

@@ -4392,16 +4490,10 @@ void FwdPendingCommit(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
43924490

43934491
heap = ssh->ctx->heap;
43944492

4395-
if (pend->bindAddr != NULL)
4396-
target = FwdRemoteFind(ssh, pend->bindAddr, pend->bindPort);
4493+
/* Its send is over, so nothing a later request sends can name it. */
4494+
FwdPendingPop(ssh, pend);
43974495

4398-
if (pend->entry != NULL && target != NULL) {
4399-
/* A callback the send ran registered this bind first, so the entry
4400-
* built for it is one too many. */
4401-
WFREE(pend->entry->bindAddr, heap, DYNTYPE_STRING);
4402-
WFREE(pend->entry, heap, DYNTYPE_FWD);
4403-
pend->entry = NULL;
4404-
}
4496+
target = pend->entry != NULL ? pend->entry : pend->found;
44054497

44064498
if (pend->entry != NULL) {
44074499
for (cur = ssh->fwdRemoteList; cur != NULL && cur->next != NULL;
@@ -4412,8 +4504,6 @@ void FwdPendingCommit(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
44124504
ssh->fwdRemoteList = pend->entry;
44134505
else
44144506
cur->next = pend->entry;
4415-
4416-
target = pend->entry;
44174507
}
44184508

44194509
if (pend->reply != NULL && pend->reply->answered) {
@@ -4463,9 +4553,13 @@ void FwdPendingDiscard(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
44634553

44644554
heap = ssh->ctx->heap;
44654555

4556+
/* Its send is over, so nothing a later request sends can name it. */
4557+
FwdPendingPop(ssh, pend);
4558+
44664559
if (pend->entry != NULL) {
4467-
WFREE(pend->entry->bindAddr, heap, DYNTYPE_STRING);
4468-
WFREE(pend->entry, heap, DYNTYPE_FWD);
4560+
/* Nothing linked it, so there is no list to take it out of, but a
4561+
* request a callback sent may have named it and has to let go. */
4562+
FwdRemoteUnlink(ssh, heap, pend->entry);
44694563
}
44704564
if (pend->reply != NULL) {
44714565
/* An answer that arrived mid-send dequeued the slot already, and
@@ -4482,10 +4576,26 @@ void FwdPendingDiscard(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
44824576
}
44834577

44844578

4579+
/* Does the bind this open names reach that forward? A peer that rewrites the
4580+
* bind it echoes back can still be held to the port it was asked for. */
4581+
static int FwdRemoteAddrMatch(WOLFSSH* ssh, const WOLFSSH_FWD_REMOTE* entry,
4582+
const char* addr, word32 port)
4583+
{
4584+
/* No port to match on until the peer's reply names the one it bound. */
4585+
if (entry->portPending || entry->bindPort != port)
4586+
return 0;
4587+
4588+
return ssh->fwdRemoteMatch == WOLFSSH_FWD_MATCH_PORT ||
4589+
FwdRemoteAddrIsWild(entry->bindAddr) ||
4590+
WSTRCMP(entry->bindAddr, addr) == 0;
4591+
}
4592+
4593+
44854594
/* Does an inbound forwarded-tcpip name a forward this client registered? */
44864595
static int FwdRemoteMatch(WOLFSSH* ssh, const char* addr, word32 port)
44874596
{
44884597
WOLFSSH_FWD_REMOTE* cur;
4598+
WOLFSSH_FWD_PENDING* pend;
44894599

44904600
if (ssh == NULL || addr == NULL)
44914601
return 0;
@@ -4497,29 +4607,53 @@ static int FwdRemoteMatch(WOLFSSH* ssh, const char* addr, word32 port)
44974607
for (cur = ssh->fwdRemoteList; cur != NULL; cur = cur->next) {
44984608
WOLFSSH_FWD_REPLY* newest;
44994609

4500-
/* No port to match on until the peer's reply names the one it
4501-
* bound. */
4502-
if (cur->portPending || cur->bindPort != port)
4610+
if (!FwdRemoteAddrMatch(ssh, cur, addr, port))
45034611
continue;
45044612

45054613
/* The newest request governs: a cancel stops matching as it goes out,
45064614
* so revoking never waits on the peer, and the peer refusing it puts
4507-
* the forward back. */
4615+
* the forward back. A cancel still inside its own send counts, since
4616+
* the session can only be pumped from a callback that send ran. */
45084617
newest = FwdReplyNewest(ssh, cur);
4509-
if (newest != NULL && newest->isCancel)
4618+
if ((newest != NULL && newest->isCancel) ||
4619+
FwdPendingHasCancel(ssh, cur))
45104620
continue;
45114621

45124622
/* A forward stands on the peer having bound it, or on a request still
45134623
* owed an answer. With neither, nothing speaks for it. */
45144624
if (!cur->confirmed && newest == NULL)
45154625
continue;
45164626

4517-
/* A peer that rewrites the bind it echoes back can still be held to
4518-
* the port it was asked for. */
4519-
if (ssh->fwdRemoteMatch == WOLFSSH_FWD_MATCH_PORT ||
4520-
FwdRemoteAddrIsWild(cur->bindAddr) ||
4521-
WSTRCMP(cur->bindAddr, addr) == 0)
4522-
return 1;
4627+
return 1;
4628+
}
4629+
4630+
/* A setup inside its own send is already on the wire, so the listener it
4631+
* asks for can start feeding channels before the call returns. It is the
4632+
* newest request for its bind by construction, and needs no reply to speak
4633+
* for it. */
4634+
for (pend = ssh->fwdPendingHead; pend != NULL; pend = pend->next) {
4635+
WOLFSSH_FWD_REPLY* newest;
4636+
4637+
if (pend->entry == NULL ||
4638+
!FwdRemoteAddrMatch(ssh, pend->entry, addr, port))
4639+
continue;
4640+
4641+
/* A cancel a callback sent from this setup's send went out behind it
4642+
* and committed, so the newest request governs here too. */
4643+
newest = FwdReplyNewest(ssh, pend->entry);
4644+
if ((newest != NULL && newest->isCancel) ||
4645+
FwdPendingHasCancel(ssh, pend->entry))
4646+
continue;
4647+
4648+
/* An answer that arrived mid-send left the queue for the commit to
4649+
* apply, so no scan of it sees this request. The peer refused the
4650+
* bind, so nothing speaks for the forward and the commit is about to
4651+
* drop it. */
4652+
if (pend->reply != NULL && pend->reply->answered &&
4653+
!pend->reply->success)
4654+
continue;
4655+
4656+
return 1;
45234657
}
45244658

45254659
return 0;

0 commit comments

Comments
 (0)