374 lines
11 KiB
C++
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()
|
|
|
|
|
|
}
|
|
|