Skip to content

Commit 2a720f9

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 c4b3242 commit 2a720f9

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
@@ -1951,6 +1951,51 @@ static int RejectDirectTcpipSetup(WS_FwdCbAction action, void* ctx,
19511951
return WS_SUCCESS;
19521952
}
19531953

1954+
#define REGRESS_FWD_ACTION_MAX 8
1955+
static WS_FwdCbAction fwdActions[REGRESS_FWD_ACTION_MAX];
1956+
static word32 fwdActionCount;
1957+
1958+
/* Records the actions the library asked for, so a test can assert the
1959+
* sequence a forwarding channel produces. */
1960+
static int RecordingFwdCb(WS_FwdCbAction action, void* ctx,
1961+
const char* host, word32 port)
1962+
{
1963+
(void)ctx;
1964+
(void)host;
1965+
(void)port;
1966+
1967+
if (fwdActionCount < REGRESS_FWD_ACTION_MAX)
1968+
fwdActions[fwdActionCount] = action;
1969+
fwdActionCount++;
1970+
1971+
return WS_SUCCESS;
1972+
}
1973+
1974+
/* As RecordingFwdCb, but refuses the channel-id handoff so the open fails
1975+
* after a successful setup. */
1976+
static int RecordingRejectChannelIdFwdCb(WS_FwdCbAction action, void* ctx,
1977+
const char* host, word32 port)
1978+
{
1979+
int ret = RecordingFwdCb(action, ctx, host, port);
1980+
1981+
if (action == WOLFSSH_FWD_CHANNEL_ID)
1982+
return WS_FWD_NOT_AVAILABLE;
1983+
1984+
return ret;
1985+
}
1986+
1987+
/* As RecordingFwdCb, but refuses the setup itself. */
1988+
static int RecordingRejectSetupFwdCb(WS_FwdCbAction action, void* ctx,
1989+
const char* host, word32 port)
1990+
{
1991+
int ret = RecordingFwdCb(action, ctx, host, port);
1992+
1993+
if (action == WOLFSSH_FWD_LOCAL_SETUP)
1994+
return WS_FWD_SETUP_E;
1995+
1996+
return ret;
1997+
}
1998+
19541999
/* Counts every action the library asks for. File scope rather than reached
19552000
* through the callback ctx, so a zero reading means the hook did not run and
19562001
* cannot instead mean the ctx stopped being delivered. */
@@ -3249,6 +3294,226 @@ static void TestDirectTcpipFwdCbRejectsChannelId(void)
32493294
FreeChannelOpenHarness(&harness);
32503295
}
32513296

3297+
/* A forwarding channel that opened with a LOCAL_SETUP has to report a
3298+
* LOCAL_CLEANUP when it closes, or an application has no hook to release what
3299+
* it set up. */
3300+
static void TestDirectTcpipCloseSendsLocalCleanup(void)
3301+
{
3302+
ChannelOpenHarness harness;
3303+
byte extra[128];
3304+
byte in[192];
3305+
byte in2[64];
3306+
word32 extraSz;
3307+
word32 inSz;
3308+
word32 in2Sz;
3309+
word32 selfChannelId;
3310+
int ret;
3311+
3312+
fwdActionCount = 0;
3313+
3314+
extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222,
3315+
extra, sizeof(extra));
3316+
inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000,
3317+
extra, extraSz, in, sizeof(in));
3318+
3319+
InitChannelOpenHarness(&harness, in, inSz);
3320+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, RecordingFwdCb, NULL),
3321+
WS_SUCCESS);
3322+
3323+
ret = DoReceive(harness.ssh);
3324+
AssertIntEQ(ret, WS_SUCCESS);
3325+
AssertIntEQ(ParseMsgId(harness.io.out, harness.io.outSz),
3326+
MSGID_CHANNEL_OPEN_CONF);
3327+
AssertIntEQ(harness.ssh->channelListSz, 1);
3328+
AssertIntEQ(fwdActionCount, 2);
3329+
AssertIntEQ(fwdActions[0], WOLFSSH_FWD_LOCAL_SETUP);
3330+
AssertIntEQ(fwdActions[1], WOLFSSH_FWD_CHANNEL_ID);
3331+
3332+
selfChannelId = harness.ssh->channelList->channel;
3333+
in2Sz = BuildChannelClosePacket(selfChannelId, in2, sizeof(in2));
3334+
RepointHarnessInput(&harness, in2, in2Sz);
3335+
3336+
ret = DoReceive(harness.ssh);
3337+
AssertIntEQ(ret, WS_CHANNEL_CLOSED);
3338+
AssertIntEQ(harness.ssh->channelListSz, 0);
3339+
AssertIntEQ(fwdActionCount, 3);
3340+
AssertIntEQ(fwdActions[2], WOLFSSH_FWD_LOCAL_CLEANUP);
3341+
3342+
FreeChannelOpenHarness(&harness);
3343+
}
3344+
3345+
/* Closes a channel this side created itself, which never saw a
3346+
* LOCAL_SETUP, and reports how many forwarding actions that produced. */
3347+
static word32 CloseSelfOpenedChannel(byte channelType)
3348+
{
3349+
ChannelOpenHarness harness;
3350+
WOLFSSH_CHANNEL* channel;
3351+
byte in[64];
3352+
word32 inSz;
3353+
word32 selfChannelId;
3354+
3355+
fwdActionCount = 0;
3356+
3357+
InitChannelOpenHarness(&harness, NULL, 0);
3358+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, RecordingFwdCb, NULL),
3359+
WS_SUCCESS);
3360+
3361+
channel = ChannelNew(harness.ssh, channelType, 1024, 1024);
3362+
AssertNotNull(channel);
3363+
AssertIntEQ(ChannelUpdatePeer(channel, 5, 1024, 1024), WS_SUCCESS);
3364+
AssertIntEQ(ChannelAppend(harness.ssh, channel), WS_SUCCESS);
3365+
channel->openConfirmed = 1;
3366+
selfChannelId = channel->channel;
3367+
3368+
inSz = BuildChannelClosePacket(selfChannelId, in, sizeof(in));
3369+
RepointHarnessInput(&harness, in, inSz);
3370+
3371+
AssertIntEQ(DoReceive(harness.ssh), WS_CHANNEL_CLOSED);
3372+
AssertIntEQ(harness.ssh->channelListSz, 0);
3373+
3374+
FreeChannelOpenHarness(&harness);
3375+
3376+
return fwdActionCount;
3377+
}
3378+
3379+
/* wolfSSH_ChannelFwdNewLocal() and wolfSSH_ChannelFwdNewRemote() build a
3380+
* forwarding channel without a LOCAL_SETUP, so its close owes no cleanup:
3381+
* the application never armed one and would be freeing what it does not
3382+
* own. This is why the emission is gated on the channel, not its type. */
3383+
static void TestLocalForwardCloseSendsNoCleanup(void)
3384+
{
3385+
AssertIntEQ(CloseSelfOpenedChannel(ID_CHANTYPE_TCPIP_DIRECT), 0);
3386+
}
3387+
3388+
/* And a session channel produces no forwarding action at all. */
3389+
static void TestSessionCloseSendsNoFwdAction(void)
3390+
{
3391+
AssertIntEQ(CloseSelfOpenedChannel(ID_CHANTYPE_SESSION), 0);
3392+
}
3393+
3394+
/* A setup that succeeded is owed its cleanup even when the open goes on to
3395+
* fail, or the application keeps a socket for a channel that never existed.
3396+
* The channel-id handoff rejecting is the reachable way in. */
3397+
static void TestFailedOpenAfterSetupSendsCleanup(void)
3398+
{
3399+
ChannelOpenHarness harness;
3400+
byte extra[128];
3401+
byte in[192];
3402+
word32 extraSz;
3403+
word32 inSz;
3404+
int ret;
3405+
3406+
fwdActionCount = 0;
3407+
3408+
extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222,
3409+
extra, sizeof(extra));
3410+
inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000,
3411+
extra, extraSz, in, sizeof(in));
3412+
3413+
InitChannelOpenHarness(&harness, in, inSz);
3414+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx,
3415+
RecordingRejectChannelIdFwdCb, NULL), WS_SUCCESS);
3416+
3417+
ret = DoReceive(harness.ssh);
3418+
AssertChannelOpenFailResponse(&harness, ret);
3419+
AssertIntEQ(harness.ssh->channelListSz, 0);
3420+
AssertIntEQ(fwdActionCount, 3);
3421+
AssertIntEQ(fwdActions[0], WOLFSSH_FWD_LOCAL_SETUP);
3422+
AssertIntEQ(fwdActions[1], WOLFSSH_FWD_CHANNEL_ID);
3423+
AssertIntEQ(fwdActions[2], WOLFSSH_FWD_LOCAL_CLEANUP);
3424+
3425+
FreeChannelOpenHarness(&harness);
3426+
}
3427+
3428+
/* A setup that reported failure set nothing up, so it is owed no cleanup;
3429+
* one would have the application release what it never acquired. */
3430+
static void TestRejectedSetupSendsNoCleanup(void)
3431+
{
3432+
ChannelOpenHarness harness;
3433+
byte extra[128];
3434+
byte in[192];
3435+
word32 extraSz;
3436+
word32 inSz;
3437+
int ret;
3438+
3439+
fwdActionCount = 0;
3440+
3441+
extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222,
3442+
extra, sizeof(extra));
3443+
inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000,
3444+
extra, extraSz, in, sizeof(in));
3445+
3446+
InitChannelOpenHarness(&harness, in, inSz);
3447+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, RecordingRejectSetupFwdCb,
3448+
NULL), WS_SUCCESS);
3449+
3450+
ret = DoReceive(harness.ssh);
3451+
AssertChannelOpenFailResponse(&harness, ret);
3452+
AssertIntEQ(harness.ssh->channelListSz, 0);
3453+
AssertIntEQ(fwdActionCount, 1);
3454+
AssertIntEQ(fwdActions[0], WOLFSSH_FWD_LOCAL_SETUP);
3455+
3456+
FreeChannelOpenHarness(&harness);
3457+
}
3458+
3459+
/* Opens a direct-tcpip forward through the recording callback and checks
3460+
* the setup and channel-id handoff went out, leaving the channel live. */
3461+
static void OpenRecordedDirectTcpip(ChannelOpenHarness* harness,
3462+
byte* in, word32 inSz)
3463+
{
3464+
byte extra[128];
3465+
word32 extraSz;
3466+
3467+
fwdActionCount = 0;
3468+
3469+
extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222,
3470+
extra, sizeof(extra));
3471+
inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000,
3472+
extra, extraSz, in, inSz);
3473+
3474+
InitChannelOpenHarness(harness, in, inSz);
3475+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness->ctx, RecordingFwdCb, NULL),
3476+
WS_SUCCESS);
3477+
3478+
AssertIntEQ(DoReceive(harness->ssh), WS_SUCCESS);
3479+
AssertIntEQ(harness->ssh->channelListSz, 1);
3480+
AssertIntEQ(fwdActionCount, 2);
3481+
AssertIntEQ(fwdActions[0], WOLFSSH_FWD_LOCAL_SETUP);
3482+
AssertIntEQ(fwdActions[1], WOLFSSH_FWD_CHANNEL_ID);
3483+
}
3484+
3485+
/* A dropped transport never delivers a CHANNEL_CLOSE, so freeing the session
3486+
* is the only chance the application gets to release a forward's socket. */
3487+
static void TestSessionFreeSendsLocalCleanup(void)
3488+
{
3489+
ChannelOpenHarness harness;
3490+
byte in[192];
3491+
3492+
OpenRecordedDirectTcpip(&harness, in, sizeof(in));
3493+
3494+
FreeChannelOpenHarness(&harness);
3495+
AssertIntEQ(fwdActionCount, 3);
3496+
AssertIntEQ(fwdActions[2], WOLFSSH_FWD_LOCAL_CLEANUP);
3497+
}
3498+
3499+
/* Same for a channel the application retires itself, and only once: the
3500+
* session free that follows must not report it again. */
3501+
static void TestChannelFreeSendsLocalCleanup(void)
3502+
{
3503+
ChannelOpenHarness harness;
3504+
byte in[192];
3505+
3506+
OpenRecordedDirectTcpip(&harness, in, sizeof(in));
3507+
3508+
AssertIntEQ(wolfSSH_ChannelFree(harness.ssh->channelList), WS_SUCCESS);
3509+
AssertIntEQ(harness.ssh->channelListSz, 0);
3510+
AssertIntEQ(fwdActionCount, 3);
3511+
AssertIntEQ(fwdActions[2], WOLFSSH_FWD_LOCAL_CLEANUP);
3512+
3513+
FreeChannelOpenHarness(&harness);
3514+
AssertIntEQ(fwdActionCount, 3);
3515+
}
3516+
32523517
static void TestForwardedTcpipOnServerSendsOpenFail(void)
32533518
{
32543519
ChannelOpenHarness harness;
@@ -12137,6 +12402,13 @@ int main(int argc, char** argv)
1213712402
TestDirectTcpipOpenCbRejectBeatsFwdCb();
1213812403
TestDirectTcpipFwdCbRejectAfterOpenCbAccept();
1213912404
TestDirectTcpipFwdCbRejectsChannelId();
12405+
TestDirectTcpipCloseSendsLocalCleanup();
12406+
TestLocalForwardCloseSendsNoCleanup();
12407+
TestSessionCloseSendsNoFwdAction();
12408+
TestFailedOpenAfterSetupSendsCleanup();
12409+
TestRejectedSetupSendsNoCleanup();
12410+
TestSessionFreeSendsLocalCleanup();
12411+
TestChannelFreeSendsLocalCleanup();
1214012412
TestForwardedTcpipOnServerSendsOpenFail();
1214112413
TestGlobalRequestFwdNoCbSendsFailure();
1214212414
#ifndef NO_WOLFSSH_CLIENT

0 commit comments

Comments
 (0)