Skip to content

Commit 486d9eb

Browse files
committed
tests: cover the forward cleanup callback
WOLFSSH_FWD_LOCAL_CLEANUP now fires, and what must not fire matters as much as what must: the gate is the channel's own setup record, not its type. - a peer-opened direct-tcpip channel reports SETUP, CHANNEL_ID, then CLEANUP when it closes - a forwarding channel this side opened reports no cleanup, and a session channel no forwarding action at all - an open that fails after a successful setup still reports the cleanup, and a setup that reported failure draws none - freeing the session, or the channel with wolfSSH_ChannelFree(), reports it, and the session free after a channel free does not report it again
1 parent b7e44e2 commit 486d9eb

1 file changed

Lines changed: 272 additions & 0 deletions

File tree

tests/regress.c

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,6 +1966,51 @@ static int RejectDirectTcpipSetup(WS_FwdCbAction action, void* ctx,
19661966
return WS_SUCCESS;
19671967
}
19681968

1969+
#define REGRESS_FWD_ACTION_MAX 8
1970+
static WS_FwdCbAction fwdActions[REGRESS_FWD_ACTION_MAX];
1971+
static word32 fwdActionCount;
1972+
1973+
/* Records the actions the library asked for, so a test can assert the
1974+
* sequence a forwarding channel produces. */
1975+
static int RecordingFwdCb(WS_FwdCbAction action, void* ctx,
1976+
const char* host, word32 port)
1977+
{
1978+
(void)ctx;
1979+
(void)host;
1980+
(void)port;
1981+
1982+
if (fwdActionCount < REGRESS_FWD_ACTION_MAX)
1983+
fwdActions[fwdActionCount] = action;
1984+
fwdActionCount++;
1985+
1986+
return WS_SUCCESS;
1987+
}
1988+
1989+
/* As RecordingFwdCb, but refuses the channel-id handoff so the open fails
1990+
* after a successful setup. */
1991+
static int RecordingRejectChannelIdFwdCb(WS_FwdCbAction action, void* ctx,
1992+
const char* host, word32 port)
1993+
{
1994+
int ret = RecordingFwdCb(action, ctx, host, port);
1995+
1996+
if (action == WOLFSSH_FWD_CHANNEL_ID)
1997+
return WS_FWD_NOT_AVAILABLE;
1998+
1999+
return ret;
2000+
}
2001+
2002+
/* As RecordingFwdCb, but refuses the setup itself. */
2003+
static int RecordingRejectSetupFwdCb(WS_FwdCbAction action, void* ctx,
2004+
const char* host, word32 port)
2005+
{
2006+
int ret = RecordingFwdCb(action, ctx, host, port);
2007+
2008+
if (action == WOLFSSH_FWD_LOCAL_SETUP)
2009+
return WS_FWD_SETUP_E;
2010+
2011+
return ret;
2012+
}
2013+
19692014
/* Counts every action the library asks for. File scope rather than reached
19702015
* through the callback ctx, so a zero reading means the hook did not run and
19712016
* cannot instead mean the ctx stopped being delivered. */
@@ -3806,6 +3851,226 @@ static void TestDirectTcpipFwdCbRejectsChannelId(void)
38063851
FreeChannelOpenHarness(&harness);
38073852
}
38083853

3854+
/* A forwarding channel that opened with a LOCAL_SETUP has to report a
3855+
* LOCAL_CLEANUP when it closes, or an application has no hook to release what
3856+
* it set up. */
3857+
static void TestDirectTcpipCloseSendsLocalCleanup(void)
3858+
{
3859+
ChannelOpenHarness harness;
3860+
byte extra[128];
3861+
byte in[192];
3862+
byte in2[64];
3863+
word32 extraSz;
3864+
word32 inSz;
3865+
word32 in2Sz;
3866+
word32 selfChannelId;
3867+
int ret;
3868+
3869+
fwdActionCount = 0;
3870+
3871+
extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222,
3872+
extra, sizeof(extra));
3873+
inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000,
3874+
extra, extraSz, in, sizeof(in));
3875+
3876+
InitChannelOpenHarness(&harness, in, inSz);
3877+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, RecordingFwdCb, NULL),
3878+
WS_SUCCESS);
3879+
3880+
ret = DoReceive(harness.ssh);
3881+
AssertIntEQ(ret, WS_SUCCESS);
3882+
AssertIntEQ(ParseMsgId(harness.io.out, harness.io.outSz),
3883+
MSGID_CHANNEL_OPEN_CONF);
3884+
AssertIntEQ(harness.ssh->channelListSz, 1);
3885+
AssertIntEQ(fwdActionCount, 2);
3886+
AssertIntEQ(fwdActions[0], WOLFSSH_FWD_LOCAL_SETUP);
3887+
AssertIntEQ(fwdActions[1], WOLFSSH_FWD_CHANNEL_ID);
3888+
3889+
selfChannelId = harness.ssh->channelList->channel;
3890+
in2Sz = BuildChannelClosePacket(selfChannelId, in2, sizeof(in2));
3891+
RepointHarnessInput(&harness, in2, in2Sz);
3892+
3893+
ret = DoReceive(harness.ssh);
3894+
AssertIntEQ(ret, WS_CHANNEL_CLOSED);
3895+
AssertIntEQ(harness.ssh->channelListSz, 0);
3896+
AssertIntEQ(fwdActionCount, 3);
3897+
AssertIntEQ(fwdActions[2], WOLFSSH_FWD_LOCAL_CLEANUP);
3898+
3899+
FreeChannelOpenHarness(&harness);
3900+
}
3901+
3902+
/* Closes a channel this side created itself, which never saw a
3903+
* LOCAL_SETUP, and reports how many forwarding actions that produced. */
3904+
static word32 CloseSelfOpenedChannel(byte channelType)
3905+
{
3906+
ChannelOpenHarness harness;
3907+
WOLFSSH_CHANNEL* channel;
3908+
byte in[64];
3909+
word32 inSz;
3910+
word32 selfChannelId;
3911+
3912+
fwdActionCount = 0;
3913+
3914+
InitChannelOpenHarness(&harness, NULL, 0);
3915+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, RecordingFwdCb, NULL),
3916+
WS_SUCCESS);
3917+
3918+
channel = ChannelNew(harness.ssh, channelType, 1024, 1024);
3919+
AssertNotNull(channel);
3920+
AssertIntEQ(ChannelUpdatePeer(channel, 5, 1024, 1024), WS_SUCCESS);
3921+
AssertIntEQ(ChannelAppend(harness.ssh, channel), WS_SUCCESS);
3922+
channel->openConfirmed = 1;
3923+
selfChannelId = channel->channel;
3924+
3925+
inSz = BuildChannelClosePacket(selfChannelId, in, sizeof(in));
3926+
RepointHarnessInput(&harness, in, inSz);
3927+
3928+
AssertIntEQ(DoReceive(harness.ssh), WS_CHANNEL_CLOSED);
3929+
AssertIntEQ(harness.ssh->channelListSz, 0);
3930+
3931+
FreeChannelOpenHarness(&harness);
3932+
3933+
return fwdActionCount;
3934+
}
3935+
3936+
/* wolfSSH_ChannelFwdNewLocal() and wolfSSH_ChannelFwdNewRemote() build a
3937+
* forwarding channel without a LOCAL_SETUP, so its close owes no cleanup:
3938+
* the application never armed one and would be freeing what it does not
3939+
* own. This is why the emission is gated on the channel, not its type. */
3940+
static void TestLocalForwardCloseSendsNoCleanup(void)
3941+
{
3942+
AssertIntEQ(CloseSelfOpenedChannel(ID_CHANTYPE_TCPIP_DIRECT), 0);
3943+
}
3944+
3945+
/* And a session channel produces no forwarding action at all. */
3946+
static void TestSessionCloseSendsNoFwdAction(void)
3947+
{
3948+
AssertIntEQ(CloseSelfOpenedChannel(ID_CHANTYPE_SESSION), 0);
3949+
}
3950+
3951+
/* A setup that succeeded is owed its cleanup even when the open goes on to
3952+
* fail, or the application keeps a socket for a channel that never existed.
3953+
* The channel-id handoff rejecting is the reachable way in. */
3954+
static void TestFailedOpenAfterSetupSendsCleanup(void)
3955+
{
3956+
ChannelOpenHarness harness;
3957+
byte extra[128];
3958+
byte in[192];
3959+
word32 extraSz;
3960+
word32 inSz;
3961+
int ret;
3962+
3963+
fwdActionCount = 0;
3964+
3965+
extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222,
3966+
extra, sizeof(extra));
3967+
inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000,
3968+
extra, extraSz, in, sizeof(in));
3969+
3970+
InitChannelOpenHarness(&harness, in, inSz);
3971+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx,
3972+
RecordingRejectChannelIdFwdCb, NULL), WS_SUCCESS);
3973+
3974+
ret = DoReceive(harness.ssh);
3975+
AssertChannelOpenFailResponse(&harness, ret);
3976+
AssertIntEQ(harness.ssh->channelListSz, 0);
3977+
AssertIntEQ(fwdActionCount, 3);
3978+
AssertIntEQ(fwdActions[0], WOLFSSH_FWD_LOCAL_SETUP);
3979+
AssertIntEQ(fwdActions[1], WOLFSSH_FWD_CHANNEL_ID);
3980+
AssertIntEQ(fwdActions[2], WOLFSSH_FWD_LOCAL_CLEANUP);
3981+
3982+
FreeChannelOpenHarness(&harness);
3983+
}
3984+
3985+
/* A setup that reported failure set nothing up, so it is owed no cleanup;
3986+
* one would have the application release what it never acquired. */
3987+
static void TestRejectedSetupSendsNoCleanup(void)
3988+
{
3989+
ChannelOpenHarness harness;
3990+
byte extra[128];
3991+
byte in[192];
3992+
word32 extraSz;
3993+
word32 inSz;
3994+
int ret;
3995+
3996+
fwdActionCount = 0;
3997+
3998+
extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222,
3999+
extra, sizeof(extra));
4000+
inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000,
4001+
extra, extraSz, in, sizeof(in));
4002+
4003+
InitChannelOpenHarness(&harness, in, inSz);
4004+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, RecordingRejectSetupFwdCb,
4005+
NULL), WS_SUCCESS);
4006+
4007+
ret = DoReceive(harness.ssh);
4008+
AssertChannelOpenFailResponse(&harness, ret);
4009+
AssertIntEQ(harness.ssh->channelListSz, 0);
4010+
AssertIntEQ(fwdActionCount, 1);
4011+
AssertIntEQ(fwdActions[0], WOLFSSH_FWD_LOCAL_SETUP);
4012+
4013+
FreeChannelOpenHarness(&harness);
4014+
}
4015+
4016+
/* Opens a direct-tcpip forward through the recording callback and checks
4017+
* the setup and channel-id handoff went out, leaving the channel live. */
4018+
static void OpenRecordedDirectTcpip(ChannelOpenHarness* harness,
4019+
byte* in, word32 inSz)
4020+
{
4021+
byte extra[128];
4022+
word32 extraSz;
4023+
4024+
fwdActionCount = 0;
4025+
4026+
extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222,
4027+
extra, sizeof(extra));
4028+
inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000,
4029+
extra, extraSz, in, inSz);
4030+
4031+
InitChannelOpenHarness(harness, in, inSz);
4032+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness->ctx, RecordingFwdCb, NULL),
4033+
WS_SUCCESS);
4034+
4035+
AssertIntEQ(DoReceive(harness->ssh), WS_SUCCESS);
4036+
AssertIntEQ(harness->ssh->channelListSz, 1);
4037+
AssertIntEQ(fwdActionCount, 2);
4038+
AssertIntEQ(fwdActions[0], WOLFSSH_FWD_LOCAL_SETUP);
4039+
AssertIntEQ(fwdActions[1], WOLFSSH_FWD_CHANNEL_ID);
4040+
}
4041+
4042+
/* A dropped transport never delivers a CHANNEL_CLOSE, so freeing the session
4043+
* is the only chance the application gets to release a forward's socket. */
4044+
static void TestSessionFreeSendsLocalCleanup(void)
4045+
{
4046+
ChannelOpenHarness harness;
4047+
byte in[192];
4048+
4049+
OpenRecordedDirectTcpip(&harness, in, sizeof(in));
4050+
4051+
FreeChannelOpenHarness(&harness);
4052+
AssertIntEQ(fwdActionCount, 3);
4053+
AssertIntEQ(fwdActions[2], WOLFSSH_FWD_LOCAL_CLEANUP);
4054+
}
4055+
4056+
/* Same for a channel the application retires itself, and only once: the
4057+
* session free that follows must not report it again. */
4058+
static void TestChannelFreeSendsLocalCleanup(void)
4059+
{
4060+
ChannelOpenHarness harness;
4061+
byte in[192];
4062+
4063+
OpenRecordedDirectTcpip(&harness, in, sizeof(in));
4064+
4065+
AssertIntEQ(wolfSSH_ChannelFree(harness.ssh->channelList), WS_SUCCESS);
4066+
AssertIntEQ(harness.ssh->channelListSz, 0);
4067+
AssertIntEQ(fwdActionCount, 3);
4068+
AssertIntEQ(fwdActions[2], WOLFSSH_FWD_LOCAL_CLEANUP);
4069+
4070+
FreeChannelOpenHarness(&harness);
4071+
AssertIntEQ(fwdActionCount, 3);
4072+
}
4073+
38094074
static void TestForwardedTcpipOnServerSendsOpenFail(void)
38104075
{
38114076
ChannelOpenHarness harness;
@@ -13135,6 +13400,13 @@ int main(int argc, char** argv)
1313513400
TestDirectTcpipOpenCbRejectBeatsFwdCb();
1313613401
TestDirectTcpipFwdCbRejectAfterOpenCbAccept();
1313713402
TestDirectTcpipFwdCbRejectsChannelId();
13403+
TestDirectTcpipCloseSendsLocalCleanup();
13404+
TestLocalForwardCloseSendsNoCleanup();
13405+
TestSessionCloseSendsNoFwdAction();
13406+
TestFailedOpenAfterSetupSendsCleanup();
13407+
TestRejectedSetupSendsNoCleanup();
13408+
TestSessionFreeSendsLocalCleanup();
13409+
TestChannelFreeSendsLocalCleanup();
1313813410
TestForwardedTcpipOnServerSendsOpenFail();
1313913411
TestGlobalRequestFwdNoCbSendsFailure();
1314013412
#ifndef NO_WOLFSSH_CLIENT

0 commit comments

Comments
 (0)