@@ -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+
40044087static 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 . */
43824480void 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? */
44864595static 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