tunsafe-clang15/service_pipe_win32.cpp

374 lines
11 KiB
C++

// SPDX-License-Identifier: AGPL-1.0-only
// Copyright (C) 2018 Ludvig Strigeus <info@tunsafe.com>. All Rights Reserved.
#include "stdafx.h"
#include "service_pipe_win32.h"
#include "util.h"
#include "service_win32_constants.h"
///////////////////////////////////////////////////////////////////////////////////////
// PipeManager
///////////////////////////////////////////////////////////////////////////////////////
PipeManager::PipeManager(const char *pipe_name, bool is_server_pipe, Delegate *delegate) {
pipe_name_ = _strdup(pipe_name);
is_server_pipe_ = is_server_pipe;
for (size_t i = 0; i < kMaxConnections * 2 + 1; i++)
events_[i] = CreateEvent(NULL, i != 0, FALSE, NULL); // For Exit
delegate_ = delegate;
thread_ = NULL;
exit_thread_ = false;
thread_id_ = 0;
for (size_t i = 0; i != kMaxConnections; i++)
connections_[i].Configure(this, (int)i);
connections_[0].state_ = PipeConnection::kStateStarting;
}
PipeManager::~PipeManager() {
StopThread();
for (size_t i = 0; i < kMaxConnections * 2 + 1; i++)
CloseHandle(events_[i]);
free(pipe_name_);
}
bool PipeManager::StartThread() {
assert(thread_ == NULL);
thread_ = CreateThread(NULL, 0, &StaticThreadMain, this, 0, &thread_id_);
return thread_ != NULL;
}
void PipeManager::StopThread() {
if (thread_ != NULL) {
exit_thread_ = true;
SetEvent(events_[0]);
WaitForSingleObject(thread_, INFINITE);
CloseHandle(thread_);
thread_ = NULL;
}
}
bool PipeManager::VerifyThread() {
return thread_id_ == GetCurrentThreadId();
}
void PipeManager::TryStartNewListener() {
assert(VerifyThread());
assert(is_server_pipe_);
// Check if any thread is in the listener state, if not, start
PipeConnection *found_conn = NULL;
for (size_t i = 0; i < kMaxConnections; i++) {
PipeConnection *conn = &connections_[i];
if (conn->connection_established_)
continue;
if (conn->state_ == PipeConnection::kStateWaitConnect)
return;
if (conn->state_ == PipeConnection::kStateNone && found_conn == NULL)
found_conn = conn;
}
if (found_conn) {
found_conn->state_ = PipeConnection::kStateStarting;
found_conn->AdvanceStateMachine();
}
}
DWORD WINAPI PipeManager::StaticThreadMain(void *x) {
return ((PipeManager*)x)->ThreadMain();
}
DWORD PipeManager::ThreadMain() {
assert(VerifyThread());
for (size_t i = 0; i < kMaxConnections; i++)
connections_[i].AdvanceStateMachine();
for (;;) {
DWORD rv = WaitForMultipleObjects(1 + kMaxConnections * 2, events_, FALSE, INFINITE);
// notify?
if (rv == WAIT_OBJECT_0) {
if (exit_thread_)
break;
delegate_->HandleNotify();
// The notification event is set when there might be new messages to send,
// so try to send them.
for (size_t i = 0; i != kMaxConnections; i++)
connections_[i].TrySendNextQueuedWrite();
} else if (rv >= WAIT_OBJECT_0 + 1 && rv < WAIT_OBJECT_0 + 1 + kMaxConnections * 2) {
PipeConnection *conn = &connections_[(rv - 1) >> 1];
if (rv & 1) {
// read finished
conn->AdvanceStateMachine();
} else {
// is the write event
conn->HandleWriteComplete();
}
} else {
assert(0);
}
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////////////
// PipeConnection
///////////////////////////////////////////////////////////////////////////////////////
static void ClearPipeOverlapped(OVERLAPPED *ov) {
ov->Internal = 0;
ov->InternalHigh = 0;
ov->Offset = 0;
ov->OffsetHigh = 0;
}
PipeConnection::PipeConnection() {
pipe_ = INVALID_HANDLE_VALUE;
packets_ = NULL;
packets_end_ = &packets_;
write_overlapped_active_ = false;
connection_established_ = false;
state_ = kStateNone;
tmp_packet_buf_ = NULL;
tmp_packet_size_ = 0;
manager_ = NULL;
delegate_ = NULL;
}
PipeConnection::~PipeConnection() {
}
void PipeConnection::Configure(PipeManager *manager, int slot) {
manager_ = manager;
read_overlapped_.hEvent = manager->events_[1 + slot * 2];
write_overlapped_.hEvent = manager->events_[1 + slot * 2 + 1];
}
int PipeConnection::InitializeServerPipeAndConnect() {
int BUFSIZE = 8192;
SECURITY_ATTRIBUTES saPipeSecurity = {0};
uint8 buf[SECURITY_DESCRIPTOR_MIN_LENGTH];
PSECURITY_DESCRIPTOR pPipeSD = (PSECURITY_DESCRIPTOR)buf;
if (!InitializeSecurityDescriptor(pPipeSD, SECURITY_DESCRIPTOR_REVISION))
return -1;
// set NULL DACL on the SD
if (!SetSecurityDescriptorDacl(pPipeSD, TRUE, (PACL)NULL, FALSE))
return -1;
// now set up the security attributes
saPipeSecurity.nLength = sizeof(SECURITY_ATTRIBUTES);
saPipeSecurity.bInheritHandle = TRUE;
saPipeSecurity.lpSecurityDescriptor = pPipeSD;
pipe_ = CreateNamedPipe(manager_->pipe_name_,
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
BUFSIZE, BUFSIZE, 0, &saPipeSecurity);
if (pipe_ == INVALID_HANDLE_VALUE)
return -1;
ClearPipeOverlapped(&read_overlapped_);
// It seems like ConnectNamedPipe never sets the event object if it completes
// right away.
if (!ConnectNamedPipe(pipe_, &read_overlapped_)) {
DWORD rv = GetLastError();
return (rv == ERROR_IO_PENDING) ? 0 : (rv == ERROR_PIPE_CONNECTED) ? 1 : -1;
} else {
return 1;
}
}
bool PipeConnection::InitializeClientPipe() {
assert(pipe_ == INVALID_HANDLE_VALUE);
pipe_ = CreateFile(manager_->pipe_name_, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
return (pipe_ != INVALID_HANDLE_VALUE);
}
void PipeConnection::ClosePipe() {
if (pipe_ != INVALID_HANDLE_VALUE) {
CancelIo(pipe_);
CloseHandle(pipe_);
pipe_ = INVALID_HANDLE_VALUE;
}
connection_established_ = false;
write_overlapped_active_ = false;
state_ = kStateNone;
free(tmp_packet_buf_);
tmp_packet_buf_ = NULL;
tmp_packet_size_ = 0;
ResetEvent(read_overlapped_.hEvent);
ResetEvent(write_overlapped_.hEvent);
packets_mutex_.Acquire();
OutgoingPacket *packets = packets_;
packets_ = NULL;
packets_end_ = &packets_;
packets_mutex_.Release();
while (packets) {
OutgoingPacket *p = packets;
packets = p->next;
free(p);
}
}
void PipeConnection::HandleWriteComplete() {
assert(write_overlapped_active_);
write_overlapped_active_ = false;
// Remove the packet from the front of the queue, now that it was sent.
packets_mutex_.Acquire();
OutgoingPacket *p = packets_;
if ((packets_ = p->next) == NULL)
packets_end_ = &packets_;
packets_mutex_.Release();
free(p);
if (packets_ == NULL && state_ == kStateWaitTimeout)
AdvanceStateMachine();
else
TrySendNextQueuedWrite();
}
bool PipeConnection::WritePacket(int type, const uint8 *data, size_t data_size) {
OutgoingPacket *packet = (OutgoingPacket *)malloc(offsetof(OutgoingPacket, data[data_size + 1]));
if (packet) {
packet->size = (uint32)(data_size + 1);
packet->data[0] = type;
memcpy(packet->data + 1, data, data_size);
packet->next = NULL;
packets_mutex_.Acquire();
OutgoingPacket *was_empty = packets_;
// login messages are always queued up front
if (type == TS_SERVICE_REQ_LOGIN) {
packet->next = packets_;
if (packet->next == NULL)
packets_end_ = &packet->next;
packets_ = packet;
} else {
*packets_end_ = packet;
packets_end_ = &packet->next;
}
packets_mutex_.Release();
if (was_empty == NULL) {
// Only allow the pipe thread to invoke the send
if (GetCurrentThreadId() == manager_->thread_id_) {
TrySendNextQueuedWrite();
} else {
SetEvent(manager_->notify_handle());
}
}
}
return true;
}
bool PipeConnection::VerifyThread() {
return manager_->VerifyThread();
}
void PipeConnection::TrySendNextQueuedWrite() {
assert(manager_->VerifyThread());
if (!write_overlapped_active_) {
OutgoingPacket *p = packets_;
if (p && connection_established_) {
ClearPipeOverlapped(&write_overlapped_);
if (WriteFile(pipe_, &p->size, p->size + 4, NULL, &write_overlapped_) || GetLastError() == ERROR_IO_PENDING)
write_overlapped_active_ = true;
} else {
ResetEvent(write_overlapped_.hEvent);
}
}
}
#define TS_WAIT_BEGIN(t) switch(state_) { case t:
#define TS_WAIT_POINT(t) state_ = (t); return; case t:
#define TS_WAIT_END() }
void PipeConnection::AdvanceStateMachine() {
DWORD rv;
int srv;
TS_WAIT_BEGIN(kStateStarting)
// Create a named pipe and wait for connections from the UI process
if (manager_->is_server_pipe_) {
srv = InitializeServerPipeAndConnect();
if (srv < 0) {
if (!manager_->exit_thread_)
ExitProcess(1);
ClosePipe();
return;
}
if (srv == 0) {
TS_WAIT_POINT(kStateWaitConnect);
}
} else {
if (!InitializeClientPipe()) {
RINFO("Unable to connect to the TunSafe Service. Please make sure it's running.");
ClosePipe();
return;
}
}
connection_established_ = true;
delegate_ = manager_->delegate_->HandleNewConnection(this);
TrySendNextQueuedWrite();
for (;;) {
// Read the packet length
read_pos_ = 0;
do {
ClearPipeOverlapped(&read_overlapped_);
if (!ReadFile(pipe_, (uint8*)&packet_size_ + read_pos_, 4 - read_pos_, NULL, &read_overlapped_)) {
if ((rv = GetLastError()) != ERROR_IO_PENDING)
goto fail;
TS_WAIT_POINT(kStateWaitReadLength);
}
if ((uint32)read_overlapped_.InternalHigh == 0)
goto fail;
read_pos_ += (uint32)read_overlapped_.InternalHigh;
} while (read_pos_ != 4);
assert(packet_size_ != 0 && packet_size_ < 0x1000000);
if (packet_size_ == 0 || packet_size_ >= 0x1000000)
break;
free(tmp_packet_buf_);
tmp_packet_buf_ = (uint8*)malloc(packet_size_);
if (!tmp_packet_buf_)
break;
// Read the packet payload
read_pos_ = 0;
do {
ClearPipeOverlapped(&read_overlapped_);
if (!ReadFile(pipe_, tmp_packet_buf_ + read_pos_, packet_size_ - read_pos_, NULL, &read_overlapped_)) {
if ((rv = GetLastError()) != ERROR_IO_PENDING)
goto fail;
TS_WAIT_POINT(kStateWaitReadPayload);
}
if ((uint32)read_overlapped_.InternalHigh == 0)
goto fail;
read_pos_ += (uint32)read_overlapped_.InternalHigh;
} while (read_pos_ != packet_size_);
if (!delegate_->HandleMessage(tmp_packet_buf_[0], tmp_packet_buf_ + 1, packet_size_ - 1)) {
ResetEvent(read_overlapped_.hEvent);
if (packets_ != NULL) {
TS_WAIT_POINT(kStateWaitTimeout);
}
break;
}
}
fail:
ClosePipe();
if (!manager_->exit_thread_) {
delegate_->HandleDisconnect();
if (manager_->is_server_pipe_)
manager_->TryStartNewListener();
}
TS_WAIT_END()
}