1
0
mirror of https://github.com/SoftEtherVPN/SoftEtherVPN.git synced 2025-04-03 18:00:08 +03:00
SoftEtherVPN/src/Cedar/Proto.c
Davide Beatrici 1708998a11 Change IP structure so that IPv4 addresses are stored in RFC3493 format
In addition to saving 4 bytes for each instantiation, this change makes IP-related operations faster and clearer.

https://tools.ietf.org/html/rfc3493.html#section-3.7
2021-04-07 21:24:55 +02:00

763 lines
16 KiB
C

#include "Proto.h"
#include "Cedar.h"
#include "Logging.h"
#include "Proto_OpenVPN.h"
#include "Proto_SSTP.h"
#include "Proto_WireGuard.h"
#include "Server.h"
#include "Mayaqua/Internat.h"
#include "Mayaqua/Kernel.h"
#include "Mayaqua/Memory.h"
#include "Mayaqua/Object.h"
#include "Mayaqua/Str.h"
#include "Mayaqua/Table.h"
void ProtoLog(const PROTO *proto, const PROTO_SESSION *session, const char *name, ...)
{
wchar_t message[MAX_SIZE * 2];
if (proto == NULL)
{
return;
}
if (session != NULL)
{
wchar_t *proto_name;
UINT current_len;
va_list args;
proto_name = CopyStrToUni(session->Impl->Name());
UniFormat(message, sizeof(message), _UU("LP_PREFIX_SESSION"), proto_name, &session->SrcIp, session->SrcPort, &session->DstIp, session->DstPort, L"UDP");
Free(proto_name);
current_len = UniStrLen(message);
va_start(args, name);
UniFormatArgs(message + current_len, sizeof(message) - current_len, _UU(name), args);
va_end(args);
}
else
{
va_list args;
UniStrCpy(message, sizeof(message), _UU("LP_PREFIX_SESSION"));
UniStrCat(message, sizeof(message), _UU(name));
va_start(args, name);
UniFormatArgs(message, sizeof(message), message, args);
va_end(args);
}
WriteServerLog(proto->Cedar, message);
}
int ProtoOptionCompare(void *p1, void *p2)
{
PROTO_OPTION *option_1, *option_2;
if (p1 == NULL || p2 == NULL)
{
return (p1 == NULL && p2 == NULL ? 0 : (p1 == NULL ? -1 : 1));
}
option_1 = *(PROTO_OPTION **)p1;
option_2 = *(PROTO_OPTION **)p2;
return StrCmpi(option_1->Name, option_2->Name);
}
int ProtoContainerCompare(void *p1, void *p2)
{
PROTO_CONTAINER *container_1, *container_2;
if (p1 == NULL || p2 == NULL)
{
return (p1 == NULL && p2 == NULL ? 0 : (p1 == NULL ? -1 : 1));
}
container_1 = *(PROTO_CONTAINER **)p1;
container_2 = *(PROTO_CONTAINER **)p2;
return StrCmpi(container_1->Name, container_2->Name);
}
int ProtoSessionCompare(void *p1, void *p2)
{
int ret;
PROTO_SESSION *session_1, *session_2;
if (p1 == NULL || p2 == NULL)
{
return 0;
}
session_1 = *(PROTO_SESSION **)p1;
session_2 = *(PROTO_SESSION **)p2;
// The source port must match
ret = COMPARE_RET(session_1->SrcPort, session_2->SrcPort);
if (ret != 0)
{
return ret;
}
// The destination port must match
ret = COMPARE_RET(session_1->DstPort, session_2->DstPort);
if (ret != 0)
{
return ret;
}
// The source IP address must match
ret = CmpIpAddr(&session_1->SrcIp, &session_2->SrcIp);
if (ret != 0)
{
return ret;
}
// The destination IP address must match
return CmpIpAddr(&session_1->DstIp, &session_2->DstIp);
}
UINT ProtoSessionHash(void *p)
{
IP *ip;
UINT ret = 0;
PROTO_SESSION *session = p;
if (session == NULL)
{
return 0;
}
ip = &session->SrcIp;
for (BYTE i = 0; i < sizeof(ip->address); ++i)
{
ret += ip->address[i];
}
ret += ip->ipv6_scope_id;
ret += session->SrcPort;
ip = &session->DstIp;
for (BYTE i = 0; i < sizeof(ip->address); ++i)
{
ret += ip->address[i];
}
ret += ip->ipv6_scope_id;
ret += session->DstPort;
return ret;
}
bool ProtoEnabled(const PROTO *proto, const char *name)
{
PROTO_OPTION *option, tmp_o;
PROTO_CONTAINER *container, tmp_c;
if (proto == NULL || name == NULL)
{
return false;
}
tmp_c.Name = name;
container = Search(proto->Containers, &tmp_c);
if (container == NULL)
{
return false;
}
tmp_o.Name = PROTO_OPTION_TOGGLE_NAME;
option = Search(container->Options, &tmp_o);
if (option == NULL || option->Type != PROTO_OPTION_BOOL)
{
return false;
}
return option->Bool;
}
PROTO *ProtoNew(CEDAR *cedar)
{
PROTO *proto;
if (cedar == NULL)
{
return NULL;
}
proto = Malloc(sizeof(PROTO));
proto->Cedar = cedar;
proto->Containers = NewList(ProtoContainerCompare);
proto->Sessions = NewHashList(ProtoSessionHash, ProtoSessionCompare, 0, true);
AddRef(cedar->ref);
// WireGuard
Add(proto->Containers, ProtoContainerNew(WgsGetProtoImpl()));
// OpenVPN
Add(proto->Containers, ProtoContainerNew(OvsGetProtoImpl()));
// SSTP
Add(proto->Containers, ProtoContainerNew(SstpGetProtoImpl()));
proto->UdpListener = NewUdpListener(ProtoHandleDatagrams, proto, &cedar->Server->ListenIP);
return proto;
}
void ProtoDelete(PROTO *proto)
{
UINT i = 0;
if (proto == NULL)
{
return;
}
StopUdpListener(proto->UdpListener);
for (i = 0; i < HASH_LIST_NUM(proto->Sessions); ++i)
{
ProtoSessionDelete(LIST_DATA(proto->Sessions->AllList, i));
}
ReleaseHashList(proto->Sessions);
for (i = 0; i < LIST_NUM(proto->Containers); ++i)
{
ProtoContainerDelete(LIST_DATA(proto->Containers, i));
}
ReleaseList(proto->Containers);
FreeUdpListener(proto->UdpListener);
ReleaseCedar(proto->Cedar);
Free(proto);
}
PROTO_CONTAINER *ProtoContainerNew(const PROTO_IMPL *impl)
{
UINT i;
PROTO_OPTION *option;
PROTO_CONTAINER *container;
const PROTO_OPTION *impl_options;
if (impl == NULL)
{
return false;
}
container = Malloc(sizeof(PROTO_CONTAINER));
container->Name = impl->Name();
container->Options = NewList(ProtoOptionCompare);
container->Impl = impl;
option = ZeroMalloc(sizeof(PROTO_OPTION));
option->Name = PROTO_OPTION_TOGGLE_NAME;
option->Type = PROTO_OPTION_BOOL;
option->Bool = true;
Add(container->Options, option);
impl_options = impl->Options();
for (i = 0; impl_options[i].Name != NULL; ++i)
{
const PROTO_OPTION *impl_option = &impl_options[i];
option = ZeroMalloc(sizeof(PROTO_OPTION));
option->Name = impl_option->Name;
option->Type = impl_option->Type;
switch (impl_option->Type)
{
case PROTO_OPTION_BOOL:
option->Bool = impl_option->Bool;
break;
case PROTO_OPTION_STRING:
option->String = impl_option->String != NULL ? CopyStr(impl_option->String) : impl->OptionStringValue(option->Name);
break;
default:
Debug("ProtoContainerNew(): unhandled option type %u!\n", impl_option->Type);
Free(option);
continue;
}
Add(container->Options, option);
}
Debug("ProtoContainerNew(): %s\n", container->Name);
return container;
}
void ProtoContainerDelete(PROTO_CONTAINER *container)
{
UINT i;
LIST *options;
if (container == NULL)
{
return;
}
options = container->Options;
for (i = 0; i < LIST_NUM(options); ++i)
{
PROTO_OPTION *option = LIST_DATA(options, i);
if (option->Type == PROTO_OPTION_STRING)
{
Free(option->String);
}
Free(option);
}
ReleaseList(options);
Free(container);
}
const PROTO_CONTAINER *ProtoDetect(const PROTO *proto, const PROTO_MODE mode, const UCHAR *data, const UINT size)
{
UINT i;
if (proto == NULL || data == NULL || size == 0)
{
return NULL;
}
for (i = 0; i < LIST_NUM(proto->Containers); ++i)
{
const PROTO_CONTAINER *container = LIST_DATA(proto->Containers, i);
const PROTO_IMPL *impl = container->Impl;
if (ProtoEnabled(proto, container->Name) == false)
{
Debug("ProtoDetect(): skipping %s because it's disabled\n", container->Name);
continue;
}
if (impl->IsPacketForMe != NULL && impl->IsPacketForMe(mode, data, size))
{
Debug("ProtoDetect(): %s detected\n", container->Name);
return container;
}
}
Debug("ProtoDetect(): unrecognized protocol\n");
return NULL;
}
PROTO_SESSION *ProtoSessionNew(const PROTO *proto, const PROTO_CONTAINER *container, const IP *src_ip, const USHORT src_port, const IP *dst_ip, const USHORT dst_port)
{
LIST *options;
PROTO_SESSION *session;
const PROTO_IMPL *impl;
if (container == NULL || src_ip == NULL || src_port == 0 || dst_ip == NULL || dst_port == 0)
{
return NULL;
}
options = container->Options;
impl = container->Impl;
session = ZeroMalloc(sizeof(PROTO_SESSION));
session->SockEvent = NewSockEvent();
session->InterruptManager = NewInterruptManager();
LockList(options);
if (impl->Init != NULL && impl->Init(&session->Param, container->Options, proto->Cedar, session->InterruptManager, session->SockEvent, NULL, NULL) == false)
{
Debug("ProtoNewSession(): failed to initialize %s\n", container->Name);
UnlockList(options);
ReleaseSockEvent(session->SockEvent);
FreeInterruptManager(session->InterruptManager);
Free(session);
return NULL;
}
UnlockList(options);
session->Proto = proto;
session->Impl = impl;
CopyIP(&session->SrcIp, src_ip);
session->SrcPort = src_port;
CopyIP(&session->DstIp, dst_ip);
session->DstPort = dst_port;
session->DatagramsIn = NewListFast(NULL);
session->DatagramsOut = NewListFast(NULL);
session->Lock = NewLock();
session->Thread = NewThread(ProtoSessionThread, session);
ProtoLog(proto, session, "LP_SESSION_CREATED");
return session;
}
void ProtoSessionDelete(PROTO_SESSION *session)
{
if (session == NULL)
{
return;
}
session->Halt = true;
SetSockEvent(session->SockEvent);
WaitThread(session->Thread, INFINITE);
ReleaseThread(session->Thread);
session->Impl->Free(session->Param);
ReleaseSockEvent(session->SockEvent);
FreeInterruptManager(session->InterruptManager);
ReleaseList(session->DatagramsIn);
ReleaseList(session->DatagramsOut);
DeleteLock(session->Lock);
ProtoLog(session->Proto, session, "LP_SESSION_DELETED");
Free(session);
}
bool ProtoSetListenIP(PROTO *proto, const IP *ip)
{
if (proto == NULL || ip == NULL)
{
return false;
}
Copy(&proto->UdpListener->ListenIP, ip, sizeof(proto->UdpListener->ListenIP));
return true;
}
bool ProtoSetUdpPorts(PROTO *proto, const LIST *ports)
{
UINT i = 0;
if (proto == NULL || ports == NULL)
{
return false;
}
DeleteAllPortFromUdpListener(proto->UdpListener);
for (i = 0; i < LIST_NUM(ports); ++i)
{
UINT port = *((UINT *)LIST_DATA(ports, i));
if (port >= 1 && port <= 65535)
{
AddPortToUdpListener(proto->UdpListener, port);
}
}
return true;
}
bool ProtoHandleConnection(PROTO *proto, SOCK *sock, const char *protocol)
{
const PROTO_IMPL *impl;
void *impl_data = NULL;
UCHAR *buf;
TCP_RAW_DATA *recv_raw_data;
FIFO *send_fifo;
INTERRUPT_MANAGER *im;
SOCK_EVENT *se;
if (proto == NULL || sock == NULL)
{
return false;
}
{
const PROTO_CONTAINER *container = NULL;
wchar_t *proto_name;
LIST *options;
if (protocol != NULL)
{
UINT i;
for (i = 0; i < LIST_NUM(proto->Containers); ++i)
{
const PROTO_CONTAINER *tmp = LIST_DATA(proto->Containers, i);
if (StrCmp(tmp->Name, protocol) == 0)
{
container = tmp;
break;
}
}
}
else
{
UCHAR tmp[PROTO_CHECK_BUFFER_SIZE];
if (Peek(sock, tmp, sizeof(tmp)) == 0)
{
return false;
}
container = ProtoDetect(proto, PROTO_MODE_TCP, tmp, sizeof(tmp));
}
if (container == NULL)
{
return false;
}
options = container->Options;
impl = container->Impl;
im = NewInterruptManager();
se = NewSockEvent();
LockList(options);
if (impl->Init != NULL && impl->Init(&impl_data, options, proto->Cedar, im, se, sock->CipherName, sock->RemoteHostname) == false)
{
Debug("ProtoHandleConnection(): failed to initialize %s\n", container->Name);
UnlockList(options);
FreeInterruptManager(im);
ReleaseSockEvent(se);
return false;
}
UnlockList(options);
proto_name = CopyStrToUni(container->Name);
ProtoLog(proto, NULL, "LP_SESSION_CREATED", proto_name, &sock->RemoteIP, sock->RemotePort, &sock->LocalIP, sock->LocalPort, L"TCP");
Free(proto_name);
}
SetTimeout(sock, TIMEOUT_INFINITE);
JoinSockToSockEvent(sock, se);
recv_raw_data = NewTcpRawData(&sock->RemoteIP, sock->RemotePort, &sock->LocalIP, sock->LocalPort);
send_fifo = NewFifoFast();
buf = Malloc(PROTO_TCP_BUFFER_SIZE);
Debug("ProtoHandleConnection(): entering main loop\n");
// Receive data from the TCP socket
while (true)
{
UINT next_interval;
bool stop = false;
while (true)
{
const UINT ret = Recv(sock, buf, PROTO_TCP_BUFFER_SIZE, sock->SecureMode);
if (ret == SOCK_LATER)
{
// No more data to read
break;
}
else if (ret == 0)
{
// Disconnected
stop = true;
break;
}
else
{
// Write the received data into the FIFO
WriteFifo(recv_raw_data->Data, buf, ret);
}
}
if (impl->ProcessData(impl_data, recv_raw_data, send_fifo) == false)
{
stop = true;
}
// Send data to the TCP socket
while (FifoSize(send_fifo) >= 1)
{
const UINT ret = Send(sock, FifoPtr(send_fifo), FifoSize(send_fifo), sock->SecureMode);
if (ret == SOCK_LATER)
{
// Can not write anymore
break;
}
else if (ret == 0)
{
// Disconnected
stop = true;
break;
}
else
{
// Remove data that has been sent from the FIFO
ReadFifo(send_fifo, NULL, ret);
}
}
if (stop)
{
// Error or disconnection occurs
Debug("ProtoHandleConnection(): breaking main loop\n");
break;
}
// Wait until the next event occurs
next_interval = GetNextIntervalForInterrupt(im);
next_interval = MIN(next_interval, UDPLISTENER_WAIT_INTERVAL);
WaitSockEvent(se, next_interval);
}
impl->Free(impl_data);
FreeInterruptManager(im);
ReleaseSockEvent(se);
FreeTcpRawData(recv_raw_data);
ReleaseFifo(send_fifo);
Free(buf);
{
wchar_t *proto_name = CopyStrToUni(impl->Name());
ProtoLog(proto, NULL, "LP_SESSION_DELETED", proto_name, &sock->RemoteIP, sock->RemotePort, &sock->LocalIP, sock->LocalPort, L"TCP");
Free(proto_name);
}
return true;
}
void ProtoHandleDatagrams(UDPLISTENER *listener, LIST *datagrams)
{
UINT i;
PROTO *proto;
HASH_LIST *sessions;
if (listener == NULL || datagrams == NULL)
{
return;
}
proto = listener->Param;
sessions = proto->Sessions;
for (i = 0; i < LIST_NUM(datagrams); ++i)
{
UDPPACKET *datagram = LIST_DATA(datagrams, i);
PROTO_SESSION *session, tmp;
CopyIP(&tmp.SrcIp, &datagram->SrcIP);
tmp.SrcPort = datagram->SrcPort;
CopyIP(&tmp.DstIp, &datagram->DstIP);
tmp.DstPort = datagram->DestPort;
session = SearchHash(sessions, &tmp);
if (session == NULL)
{
const PROTO_CONTAINER *container = ProtoDetect(proto, PROTO_MODE_UDP, datagram->Data, datagram->Size);
if (container == NULL)
{
continue;
}
session = ProtoSessionNew(proto, container, &tmp.SrcIp, tmp.SrcPort, &tmp.DstIp, tmp.DstPort);
if (session == NULL)
{
continue;
}
AddHash(proto->Sessions, session);
}
Lock(session->Lock);
{
if (session->Halt == false)
{
void *data = Clone(datagram->Data, datagram->Size);
UDPPACKET *packet = NewUdpPacket(&datagram->SrcIP, datagram->SrcPort, &datagram->DstIP, datagram->DestPort, data, datagram->Size);
Add(session->DatagramsIn, packet);
}
}
Unlock(session->Lock);
}
for (i = 0; i < LIST_NUM(sessions->AllList); ++i)
{
PROTO_SESSION *session = LIST_DATA(sessions->AllList, i);
if (session->Halt)
{
DeleteHash(sessions, session);
ProtoSessionDelete(session);
continue;
}
if (LIST_NUM(session->DatagramsIn) > 0)
{
SetSockEvent(session->SockEvent);
}
}
}
void ProtoSessionThread(THREAD *thread, void *param)
{
PROTO_SESSION *session = param;
if (thread == NULL || session == NULL)
{
return;
}
while (session->Halt == false)
{
UINT interval;
void *param = session->Param;
const PROTO_IMPL *impl = session->Impl;
LIST *received = session->DatagramsIn;
LIST *to_send = session->DatagramsOut;
Lock(session->Lock);
{
UINT i;
session->Halt = impl->ProcessDatagrams(param, received, to_send) == false;
UdpListenerSendPackets(session->Proto->UdpListener, to_send);
for (i = 0; i < LIST_NUM(received); ++i)
{
FreeUdpPacket(LIST_DATA(received, i));
}
DeleteAll(received);
DeleteAll(to_send);
}
Unlock(session->Lock);
if (session->Halt)
{
Debug("ProtoSessionThread(): breaking main loop\n");
break;
}
// Wait until the next event occurs
interval = GetNextIntervalForInterrupt(session->InterruptManager);
interval = MIN(interval, UDPLISTENER_WAIT_INTERVAL);
WaitSockEvent(session->SockEvent, interval);
}
}