Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor of RpcapdServerInitializer #1410

Merged
merged 16 commits into from
May 30, 2024
Merged
111 changes: 87 additions & 24 deletions Tests/Pcap++Test/Tests/LiveDeviceTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
#include "../Common/GlobalTestArgs.h"
#include "../Common/TestUtils.h"
#include "../Common/PcapFileNamesDef.h"
#include <sstream>
#include <array>
#include <cstdio>
#if defined(_WIN32)
#include "PcapRemoteDevice.h"
#include "PcapRemoteDeviceList.h"
Expand Down Expand Up @@ -97,54 +98,115 @@ class RpcapdServerInitializer
{
private:
HANDLE m_ProcessHandle;
HANDLE m_JobHandle;

public:
void killProcessAndCloseHandles()
{
if (m_ProcessHandle != nullptr)
{
TerminateProcess(m_ProcessHandle, 0);
CloseHandle(m_ProcessHandle);
m_ProcessHandle = nullptr;
}

RpcapdServerInitializer(bool activateRemoteDevice, const std::string &ip, uint16_t port) : m_ProcessHandle(nullptr)
if (m_JobHandle != nullptr)
{
CloseHandle(m_JobHandle);
m_JobHandle = nullptr;
}
}
public:
RpcapdServerInitializer(bool activateRemoteDevice, const std::string& ip, uint16_t port)
: m_ProcessHandle(nullptr), m_JobHandle(nullptr)
{
if (!activateRemoteDevice)
return;

std::string cmd = "rpcapd\\rpcapd.exe";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can move this line to around L163 (new)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, tho IMO I prefer the variables being initialized in the sequence they are used in the CreateProcess call.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I don't remember why I defined this string twice (here and in line 127). Maybe we can use cmd when we write args?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added 519b35a

std::ostringstream args;
args << "rpcapd\\rpcapd.exe -b " << ip << " -p " << port << " -n";
std::array<char, 256> args;
int res = std::snprintf(args.data(), args.size(), "%s -b %s -p %d -n", cmd.c_str(), ip.c_str(), port);
if (res < 0)
{
throw std::runtime_error("Error during string formatting");
}
else if (res >= args.size())
{
// We should never get here with reasonable input but you never know.
throw std::runtime_error("Buffer overflow. Are you sure the IP is a valid string?");
}

m_JobHandle = CreateJobObject(nullptr, nullptr);
if (m_JobHandle == nullptr)
{
throw std::runtime_error("Failed to create a job object with error code: " + std::to_string(GetLastError()));
}

// Sets up the job limits so closing the job will automatically kill all processes assigned to the job.
// This will prevent the subprocess continuing to live if the current process is killed without unwinding the stack (i.e. std::terminate is called),
// as the OS itself will kill the subprocesses when the last job handle is closed.
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobLimitInfo{};
jobLimitInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (!SetInformationJobObject(m_JobHandle, JobObjectExtendedLimitInformation, &jobLimitInfo, sizeof(jobLimitInfo)))
{
DWORD errCode = GetLastError();
CloseHandle(m_JobHandle);
throw std::runtime_error("Failed to set settings to job object with error code: " + std::to_string(errCode));
}

STARTUPINFO si;
PROCESS_INFORMATION pi;

ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
if (!CreateProcess
(
if (!CreateProcess (
TEXT(cmd.c_str()),
const_cast<char*>(TEXT(args.str().c_str())), // TODO: This can potentially cause access violation if Unicode version CreateProcessW is chosen.
NULL,NULL,FALSE,
// CreateProcessW (Unicode version) modifies the argument string inplace during internal processing.
TEXT(args.data()),
nullptr, nullptr, false,
CREATE_NEW_CONSOLE,
NULL,NULL,
nullptr, nullptr,
&si,
&pi
)
)
{
m_ProcessHandle = NULL;
PCPP_LOG_ERROR("Create process failed " << static_cast<int>(GetLastError()));
return;
}
))
{
DWORD errCode = GetLastError();
CloseHandle(m_JobHandle);
throw std::runtime_error("Create process failed with error code: " + std::to_string(errCode));
}

m_ProcessHandle = pi.hProcess;
}
CloseHandle(pi.hThread); // We don't need the thread handle, so we can close it.

~RpcapdServerInitializer()
{
if (m_ProcessHandle != NULL)
if (!AssignProcessToJobObject(m_JobHandle, m_ProcessHandle))
{
TerminateProcess(m_ProcessHandle, 0);
CloseHandle(m_ProcessHandle);
DWORD errCode = GetLastError();
killProcessAndCloseHandles();
throw std::runtime_error("Failed assigning process to job object with code: " + std::to_string(errCode));
}
}

HANDLE getHandle() { return m_ProcessHandle; }
RpcapdServerInitializer(const RpcapdServerInitializer&) = delete;
RpcapdServerInitializer(RpcapdServerInitializer&& other) noexcept
: m_ProcessHandle(other.m_ProcessHandle), m_JobHandle(other.m_JobHandle)
{
other.m_ProcessHandle = nullptr;
other.m_JobHandle = nullptr;
}
RpcapdServerInitializer& operator=(const RpcapdServerInitializer&) = delete;
RpcapdServerInitializer& operator=(RpcapdServerInitializer&& other) noexcept
{
killProcessAndCloseHandles();
m_ProcessHandle = other.m_ProcessHandle;
m_JobHandle = other.m_JobHandle;
other.m_ProcessHandle = nullptr;
other.m_JobHandle = nullptr;
return *this;
}
Comment on lines +190 to +205
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need all of these methods? We don't the copy c'tor or the assignment operator anywhere

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the class is not safe to copy, but since it only holds POD members, a copy ctor will be implicitly declared by the compiler if not explicitly deleted. As for the move ctor, @tigercosmos wanted them added as deleted too, but since the class is technically movable, I decided to write them out instead of declaring them deleted.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I have no specific objection. It's a helper class in our test suite so it doesn't have to be perfect 🙂


~RpcapdServerInitializer() { killProcessAndCloseHandles(); }

HANDLE getHandle() const { return m_ProcessHandle; }
};

#endif // defined(_WIN32)
Expand Down Expand Up @@ -868,6 +930,7 @@ PTF_TEST_CASE(TestRemoteCapture)
PTF_ASSERT_EQUAL(remoteDevices->getRemoteMachinePort(), remoteDevicePort);

pcpp::PcapRemoteDevice* remoteDevice = remoteDevices->getRemoteDeviceByIP(remoteDeviceIPAddr);
PTF_ASSERT_NOT_NULL(remoteDevice);
PTF_ASSERT_EQUAL(remoteDevice->getDeviceType(), pcpp::PcapLiveDevice::RemoteDevice, enum);
PTF_ASSERT_EQUAL(remoteDevice->getMtu(), 0);
pcpp::Logger::getInstance().suppressLogs();
Expand Down
Loading