Skip to content

Commit 7621086

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 f5880c0 commit 7621086

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

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

3375+
/* A forwarding channel that opened with a LOCAL_SETUP has to report a
3376+
* LOCAL_CLEANUP when it closes, or an application has no hook to release what
3377+
* it set up. */
3378+
static void TestDirectTcpipCloseSendsLocalCleanup(void)
3379+
{
3380+
ChannelOpenHarness harness;
3381+
byte extra[128];
3382+
byte in[192];
3383+
byte in2[64];
3384+
word32 extraSz;
3385+
word32 inSz;
3386+
word32 in2Sz;
3387+
word32 selfChannelId;
3388+
int ret;
3389+
3390+
fwdActionCount = 0;
3391+
3392+
extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222,
3393+
extra, sizeof(extra));
3394+
inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000,
3395+
extra, extraSz, in, sizeof(in));
3396+
3397+
InitChannelOpenHarness(&harness, in, inSz);
3398+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, RecordingFwdCb, NULL),
3399+
WS_SUCCESS);
3400+
3401+
ret = DoReceive(harness.ssh);
3402+
AssertIntEQ(ret, WS_SUCCESS);
3403+
AssertIntEQ(ParseMsgId(harness.io.out, harness.io.outSz),
3404+
MSGID_CHANNEL_OPEN_CONF);
3405+
AssertIntEQ(harness.ssh->channelListSz, 1);
3406+
AssertIntEQ(fwdActionCount, 2);
3407+
AssertIntEQ(fwdActions[0], WOLFSSH_FWD_LOCAL_SETUP);
3408+
AssertIntEQ(fwdActions[1], WOLFSSH_FWD_CHANNEL_ID);
3409+
3410+
selfChannelId = harness.ssh->channelList->channel;
3411+
in2Sz = BuildChannelClosePacket(selfChannelId, in2, sizeof(in2));
3412+
RepointHarnessInput(&harness, in2, in2Sz);
3413+
3414+
ret = DoReceive(harness.ssh);
3415+
AssertIntEQ(ret, WS_CHANNEL_CLOSED);
3416+
AssertIntEQ(harness.ssh->channelListSz, 0);
3417+
AssertIntEQ(fwdActionCount, 3);
3418+
AssertIntEQ(fwdActions[2], WOLFSSH_FWD_LOCAL_CLEANUP);
3419+
3420+
FreeChannelOpenHarness(&harness);
3421+
}
3422+
3423+
/* Closes a channel this side created itself, which never saw a
3424+
* LOCAL_SETUP, and reports how many forwarding actions that produced. */
3425+
static word32 CloseSelfOpenedChannel(byte channelType)
3426+
{
3427+
ChannelOpenHarness harness;
3428+
WOLFSSH_CHANNEL* channel;
3429+
byte in[64];
3430+
word32 inSz;
3431+
word32 selfChannelId;
3432+
3433+
fwdActionCount = 0;
3434+
3435+
InitChannelOpenHarness(&harness, NULL, 0);
3436+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, RecordingFwdCb, NULL),
3437+
WS_SUCCESS);
3438+
3439+
channel = ChannelNew(harness.ssh, channelType, 1024, 1024);
3440+
AssertNotNull(channel);
3441+
AssertIntEQ(ChannelUpdatePeer(channel, 5, 1024, 1024), WS_SUCCESS);
3442+
AssertIntEQ(ChannelAppend(harness.ssh, channel), WS_SUCCESS);
3443+
channel->openConfirmed = 1;
3444+
selfChannelId = channel->channel;
3445+
3446+
inSz = BuildChannelClosePacket(selfChannelId, in, sizeof(in));
3447+
RepointHarnessInput(&harness, in, inSz);
3448+
3449+
AssertIntEQ(DoReceive(harness.ssh), WS_CHANNEL_CLOSED);
3450+
AssertIntEQ(harness.ssh->channelListSz, 0);
3451+
3452+
FreeChannelOpenHarness(&harness);
3453+
3454+
return fwdActionCount;
3455+
}
3456+
3457+
/* wolfSSH_ChannelFwdNewLocal() and wolfSSH_ChannelFwdNewRemote() build a
3458+
* forwarding channel without a LOCAL_SETUP, so its close owes no cleanup:
3459+
* the application never armed one and would be freeing what it does not
3460+
* own. This is why the emission is gated on the channel, not its type. */
3461+
static void TestLocalForwardCloseSendsNoCleanup(void)
3462+
{
3463+
AssertIntEQ(CloseSelfOpenedChannel(ID_CHANTYPE_TCPIP_DIRECT), 0);
3464+
}
3465+
3466+
/* And a session channel produces no forwarding action at all. */
3467+
static void TestSessionCloseSendsNoFwdAction(void)
3468+
{
3469+
AssertIntEQ(CloseSelfOpenedChannel(ID_CHANTYPE_SESSION), 0);
3470+
}
3471+
3472+
/* A setup that succeeded is owed its cleanup even when the open goes on to
3473+
* fail, or the application keeps a socket for a channel that never existed.
3474+
* The channel-id handoff rejecting is the reachable way in. */
3475+
static void TestFailedOpenAfterSetupSendsCleanup(void)
3476+
{
3477+
ChannelOpenHarness harness;
3478+
byte extra[128];
3479+
byte in[192];
3480+
word32 extraSz;
3481+
word32 inSz;
3482+
int ret;
3483+
3484+
fwdActionCount = 0;
3485+
3486+
extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222,
3487+
extra, sizeof(extra));
3488+
inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000,
3489+
extra, extraSz, in, sizeof(in));
3490+
3491+
InitChannelOpenHarness(&harness, in, inSz);
3492+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx,
3493+
RecordingRejectChannelIdFwdCb, NULL), WS_SUCCESS);
3494+
3495+
ret = DoReceive(harness.ssh);
3496+
AssertChannelOpenFailResponse(&harness, ret);
3497+
AssertIntEQ(harness.ssh->channelListSz, 0);
3498+
AssertIntEQ(fwdActionCount, 3);
3499+
AssertIntEQ(fwdActions[0], WOLFSSH_FWD_LOCAL_SETUP);
3500+
AssertIntEQ(fwdActions[1], WOLFSSH_FWD_CHANNEL_ID);
3501+
AssertIntEQ(fwdActions[2], WOLFSSH_FWD_LOCAL_CLEANUP);
3502+
3503+
FreeChannelOpenHarness(&harness);
3504+
}
3505+
3506+
/* A setup that reported failure set nothing up, so it is owed no cleanup;
3507+
* one would have the application release what it never acquired. */
3508+
static void TestRejectedSetupSendsNoCleanup(void)
3509+
{
3510+
ChannelOpenHarness harness;
3511+
byte extra[128];
3512+
byte in[192];
3513+
word32 extraSz;
3514+
word32 inSz;
3515+
int ret;
3516+
3517+
fwdActionCount = 0;
3518+
3519+
extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222,
3520+
extra, sizeof(extra));
3521+
inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000,
3522+
extra, extraSz, in, sizeof(in));
3523+
3524+
InitChannelOpenHarness(&harness, in, inSz);
3525+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, RecordingRejectSetupFwdCb,
3526+
NULL), WS_SUCCESS);
3527+
3528+
ret = DoReceive(harness.ssh);
3529+
AssertChannelOpenFailResponse(&harness, ret);
3530+
AssertIntEQ(harness.ssh->channelListSz, 0);
3531+
AssertIntEQ(fwdActionCount, 1);
3532+
AssertIntEQ(fwdActions[0], WOLFSSH_FWD_LOCAL_SETUP);
3533+
3534+
FreeChannelOpenHarness(&harness);
3535+
}
3536+
3537+
/* Opens a direct-tcpip forward through the recording callback and checks
3538+
* the setup and channel-id handoff went out, leaving the channel live. */
3539+
static void OpenRecordedDirectTcpip(ChannelOpenHarness* harness,
3540+
byte* in, word32 inSz)
3541+
{
3542+
byte extra[128];
3543+
word32 extraSz;
3544+
3545+
fwdActionCount = 0;
3546+
3547+
extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222,
3548+
extra, sizeof(extra));
3549+
inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000,
3550+
extra, extraSz, in, inSz);
3551+
3552+
InitChannelOpenHarness(harness, in, inSz);
3553+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness->ctx, RecordingFwdCb, NULL),
3554+
WS_SUCCESS);
3555+
3556+
AssertIntEQ(DoReceive(harness->ssh), WS_SUCCESS);
3557+
AssertIntEQ(harness->ssh->channelListSz, 1);
3558+
AssertIntEQ(fwdActionCount, 2);
3559+
AssertIntEQ(fwdActions[0], WOLFSSH_FWD_LOCAL_SETUP);
3560+
AssertIntEQ(fwdActions[1], WOLFSSH_FWD_CHANNEL_ID);
3561+
}
3562+
3563+
/* A dropped transport never delivers a CHANNEL_CLOSE, so freeing the session
3564+
* is the only chance the application gets to release a forward's socket. */
3565+
static void TestSessionFreeSendsLocalCleanup(void)
3566+
{
3567+
ChannelOpenHarness harness;
3568+
byte in[192];
3569+
3570+
OpenRecordedDirectTcpip(&harness, in, sizeof(in));
3571+
3572+
FreeChannelOpenHarness(&harness);
3573+
AssertIntEQ(fwdActionCount, 3);
3574+
AssertIntEQ(fwdActions[2], WOLFSSH_FWD_LOCAL_CLEANUP);
3575+
}
3576+
3577+
/* Same for a channel the application retires itself, and only once: the
3578+
* session free that follows must not report it again. */
3579+
static void TestChannelFreeSendsLocalCleanup(void)
3580+
{
3581+
ChannelOpenHarness harness;
3582+
byte in[192];
3583+
3584+
OpenRecordedDirectTcpip(&harness, in, sizeof(in));
3585+
3586+
AssertIntEQ(wolfSSH_ChannelFree(harness.ssh->channelList), WS_SUCCESS);
3587+
AssertIntEQ(harness.ssh->channelListSz, 0);
3588+
AssertIntEQ(fwdActionCount, 3);
3589+
AssertIntEQ(fwdActions[2], WOLFSSH_FWD_LOCAL_CLEANUP);
3590+
3591+
FreeChannelOpenHarness(&harness);
3592+
AssertIntEQ(fwdActionCount, 3);
3593+
}
3594+
33303595
static void TestForwardedTcpipOnServerSendsOpenFail(void)
33313596
{
33323597
ChannelOpenHarness harness;
@@ -12219,6 +12484,13 @@ int main(int argc, char** argv)
1221912484
TestDirectTcpipOpenCbRejectBeatsFwdCb();
1222012485
TestDirectTcpipFwdCbRejectAfterOpenCbAccept();
1222112486
TestDirectTcpipFwdCbRejectsChannelId();
12487+
TestDirectTcpipCloseSendsLocalCleanup();
12488+
TestLocalForwardCloseSendsNoCleanup();
12489+
TestSessionCloseSendsNoFwdAction();
12490+
TestFailedOpenAfterSetupSendsCleanup();
12491+
TestRejectedSetupSendsNoCleanup();
12492+
TestSessionFreeSendsLocalCleanup();
12493+
TestChannelFreeSendsLocalCleanup();
1222212494
TestForwardedTcpipOnServerSendsOpenFail();
1222312495
TestGlobalRequestFwdNoCbSendsFailure();
1222412496
#ifndef NO_WOLFSSH_CLIENT

0 commit comments

Comments
 (0)