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 a3aea00820 Cedar/Proto: implement UDP system
When a datagram is received, the matching session is looked up in a hash list; if it's not found, a new session is created.

This method allows to use a single UDP port for multiple protocols, as we do with TCP.

Also, each session has its own dedicated thread, used to process the received datagrams and generate the ones that are then sent through the UDP listener.

In addition to guaranteeing constant performance, separate threads also prevent a single one from blocking all sessions.
2020-05-11 08:22:44 +02:00

561 lines
11 KiB
C

#include "CedarPch.h"
#include "Proto_OpenVPN.h"
int ProtoImplCompare(void *p1, void *p2)
{
PROTO_IMPL *impl_1 = p1, *impl_2 = p2;
if (impl_1 == NULL || impl_2 == NULL)
{
return 0;
}
if (StrCmp(impl_1->Name(), impl_2->Name()) == 0)
{
return true;
}
return false;
}
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;
if (IsIP6(ip))
{
UINT i;
for (i = 0; i < sizeof(ip->ipv6_addr); ++i)
{
ret += ip->ipv6_addr[i];
}
ret += ip->ipv6_scope_id;
}
else
{
UINT i;
for (i = 0; i < sizeof(ip->addr); ++i)
{
ret += ip->addr[i];
}
}
ret += session->SrcPort;
ip = &session->DstIp;
if (IsIP6(ip))
{
UINT i;
for (i = 0; i < sizeof(ip->ipv6_addr); ++i)
{
ret += ip->ipv6_addr[i];
}
ret += ip->ipv6_scope_id;
}
else
{
UINT i;
for (i = 0; i < sizeof(ip->addr); ++i)
{
ret += ip->addr[i];
}
}
ret += session->DstPort;
return ret;
}
PROTO *ProtoNew(CEDAR *cedar)
{
PROTO *proto;
if (cedar == NULL)
{
return NULL;
}
proto = Malloc(sizeof(PROTO));
proto->Cedar = cedar;
proto->Impls = NewList(ProtoImplCompare);
proto->Sessions = NewHashList(ProtoSessionHash, ProtoSessionCompare, 0, true);
AddRef(cedar->ref);
// OpenVPN
ProtoImplAdd(proto, OvsGetProtoImpl());
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)
{
ProtoDeleteSession(LIST_DATA(proto->Sessions->AllList, i));
}
FreeUdpListener(proto->UdpListener);
ReleaseHashList(proto->Sessions);
ReleaseList(proto->Impls);
ReleaseCedar(proto->Cedar);
Free(proto);
}
bool ProtoImplAdd(PROTO *proto, PROTO_IMPL *impl) {
if (proto == NULL || impl == NULL)
{
return false;
}
Add(proto->Impls, impl);
Debug("ProtoImplAdd(): added %s\n", impl->Name());
return true;
}
PROTO_IMPL *ProtoImplDetect(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->Impls); ++i)
{
PROTO_IMPL *impl = LIST_DATA(proto->Impls, i);
if (impl->IsPacketForMe(mode, data, size) == false)
{
continue;
}
if (StrCmp(impl->Name(), "OpenVPN") == 0 && proto->Cedar->Server->DisableOpenVPNServer)
{
Debug("ProtoImplDetect(): OpenVPN detected, but it's disabled\n");
continue;
}
Debug("ProtoImplDetect(): %s detected\n", impl->Name());
return impl;
}
Debug("ProtoImplDetect(): unrecognized protocol\n");
return NULL;
}
PROTO_SESSION *ProtoNewSession(PROTO *proto, PROTO_IMPL *impl, const IP *src_ip, const USHORT src_port, const IP *dst_ip, const USHORT dst_port)
{
PROTO_SESSION *session;
if (impl == NULL || src_ip == NULL || src_port == 0 || dst_ip == NULL || dst_port == 0)
{
return NULL;
}
session = ZeroMalloc(sizeof(PROTO_SESSION));
session->SockEvent = NewSockEvent();
session->InterruptManager = NewInterruptManager();
if (impl->Init != NULL && impl->Init(&session->Param, proto->Cedar, session->InterruptManager, session->SockEvent) == false)
{
Debug("ProtoNewSession(): failed to initialize %s\n", impl->Name());
ReleaseSockEvent(session->SockEvent);
FreeInterruptManager(session->InterruptManager);
Free(session);
return NULL;
}
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);
return session;
}
void ProtoDeleteSession(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);
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)
{
void *impl_data = NULL;
const PROTO_IMPL *impl;
UCHAR *buf;
TCP_RAW_DATA *recv_raw_data;
FIFO *send_fifo;
INTERRUPT_MANAGER *im;
SOCK_EVENT *se;
if (proto == NULL || sock == NULL)
{
return false;
}
{
UCHAR tmp[PROTO_CHECK_BUFFER_SIZE];
if (Peek(sock, tmp, sizeof(tmp)) == 0)
{
return false;
}
impl = ProtoImplDetect(proto, PROTO_MODE_TCP, tmp, sizeof(tmp));
if (impl == NULL)
{
return false;
}
}
im = NewInterruptManager();
se = NewSockEvent();
if (impl->Init != NULL && impl->Init(&impl_data, proto->Cedar, im, se) == false)
{
Debug("ProtoHandleConnection(): failed to initialize %s\n", impl->Name());
FreeInterruptManager(im);
ReleaseSockEvent(se);
return false;
}
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, false);
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), false);
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);
}
}
impl->BufferLimit(impl_data, FifoSize(send_fifo) > MAX_BUFFERING_PACKET_SIZE);
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);
return true;
}
void ProtoHandleDatagrams(UDPLISTENER *listener, LIST *datagrams)
{
UINT i;
HASH_LIST *sessions;
PROTO *proto = listener->Param;
if (proto == NULL || listener == NULL || datagrams == NULL)
{
return;
}
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)
{
tmp.Impl = ProtoImplDetect(proto, PROTO_MODE_UDP, datagram->Data, datagram->Size);
if (tmp.Impl == NULL)
{
continue;
}
session = ProtoNewSession(proto, tmp.Impl, &tmp.SrcIp, tmp.SrcPort, &tmp.DstIp, tmp.DstPort);
if (session == NULL)
{
continue;
}
AddHash(proto->Sessions, session);
}
if (session->Halt)
{
DeleteHash(sessions, session);
ProtoDeleteSession(session);
continue;
}
Lock(session->Lock);
{
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 (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)
{
bool ok;
UINT interval;
void *param = session->Param;
PROTO_IMPL *impl = session->Impl;
LIST *received = session->DatagramsIn;
LIST *to_send = session->DatagramsOut;
Lock(session->Lock);
{
UINT i;
ok = impl->ProcessDatagrams(param, received, to_send);
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 (ok == false)
{
Debug("ProtoSessionThread(): breaking main loop\n");
session->Halt = true;
break;
}
// Wait until the next event occurs
interval = GetNextIntervalForInterrupt(session->InterruptManager);
interval = MIN(interval, UDPLISTENER_WAIT_INTERVAL);
WaitSockEvent(session->SockEvent, interval);
}
}