From 667108319ddd064ff4c634214362d3f93287b61d Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Mon, 11 May 2020 07:07:04 +0200 Subject: [PATCH 1/5] Cedar: prepare Proto for UDP support - An additional parameter is added to IsPacketForMe(), used to specify the protocol type (currently either TCP or UDP). - SupportedModes() is dropped because it's now redundant. - IsOk() and EstablishedSessions() are dropped because error checking should be handled by the implementation. - ProtoImplDetect() now takes a buffer and its size rather than a SOCK, so that it can be used to detect UDP protocols. - The OpenVPN toggle check is moved to ProtoImplDetect(), so that we don't have to duplicate it once UDP support is implemented. --- src/Cedar/Proto.c | 68 ++++++++++------------------ src/Cedar/Proto.h | 15 ++++--- src/Cedar/Proto_OpenVPN.c | 95 +++++++++++++++------------------------ src/Cedar/Proto_OpenVPN.h | 4 +- 4 files changed, 71 insertions(+), 111 deletions(-) diff --git a/src/Cedar/Proto.c b/src/Cedar/Proto.c index 482aa85a..b15f8e52 100644 --- a/src/Cedar/Proto.c +++ b/src/Cedar/Proto.c @@ -65,31 +65,34 @@ bool ProtoImplAdd(PROTO *proto, PROTO_IMPL *impl) { return true; } -PROTO_IMPL *ProtoImplDetect(PROTO *proto, SOCK *sock) +PROTO_IMPL *ProtoImplDetect(PROTO *proto, const PROTO_MODE mode, const UCHAR *data, const UINT size) { - UCHAR buf[PROTO_CHECK_BUFFER_SIZE]; UINT i; - if (proto == NULL || sock == NULL) + if (proto == NULL || data == NULL || size == 0) { return NULL; } - if (Peek(sock, buf, sizeof(buf)) == 0) - { - return false; - } - for (i = 0; i < LIST_NUM(proto->Impls); ++i) { PROTO_IMPL *impl = LIST_DATA(proto->Impls, i); - if (impl->IsPacketForMe(buf, sizeof(buf))) + if (impl->IsPacketForMe(mode, data, size) == false) { - Debug("ProtoImplDetect(): %s detected\n", impl->Name()); - return impl; + 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; } @@ -104,29 +107,23 @@ bool ProtoHandleConnection(PROTO *proto, SOCK *sock) INTERRUPT_MANAGER *im; SOCK_EVENT *se; - const UINT64 giveup = Tick64() + (UINT64)OPENVPN_NEW_SESSION_DEADLINE_TIMEOUT; - if (proto == NULL || sock == NULL) { return false; } - impl = ProtoImplDetect(proto, sock); - if (impl == NULL) { - Debug("ProtoHandleConnection(): unrecognized protocol\n"); - return false; - } + UCHAR tmp[PROTO_CHECK_BUFFER_SIZE]; + if (Peek(sock, tmp, sizeof(tmp)) == 0) + { + return false; + } - if (StrCmp(impl->Name(), "OpenVPN") == 0 && proto->Cedar->Server->DisableOpenVPNServer == true) - { - Debug("ProtoHandleConnection(): OpenVPN detected, but it's disabled\n"); - return false; - } - - if ((impl->SupportedModes() & PROTO_MODE_TCP) == false) - { - return false; + impl = ProtoImplDetect(proto, PROTO_MODE_TCP, tmp, sizeof(tmp)); + if (impl == NULL) + { + return false; + } } im = NewInterruptManager(); @@ -208,23 +205,6 @@ bool ProtoHandleConnection(PROTO *proto, SOCK *sock) impl->BufferLimit(impl_data, FifoSize(send_fifo) > MAX_BUFFERING_PACKET_SIZE); - if (impl->IsOk(impl_data) == false) - { - if (impl->EstablishedSessions(impl_data) == 0) - { - if (Tick64() >= giveup) - { - Debug("ProtoHandleConnection(): I waited too much for the session to start, I give up!\n"); - stop = true; - } - } - else - { - Debug("ProtoHandleConnection(): implementation not OK, stopping the server\n"); - stop = true; - } - } - if (stop) { // Error or disconnection occurs diff --git a/src/Cedar/Proto.h b/src/Cedar/Proto.h index 26b3c1ee..0fcc5e69 100644 --- a/src/Cedar/Proto.h +++ b/src/Cedar/Proto.h @@ -7,8 +7,12 @@ #define PROTO_TCP_BUFFER_SIZE (128 * 1024) -#define PROTO_MODE_TCP 1 -#define PROTO_MODE_UDP 2 +typedef enum PROTO_MODE +{ + PROTO_MODE_UNKNOWN = 0, + PROTO_MODE_TCP = 1, + PROTO_MODE_UDP = 2 +} PROTO_MODE; typedef struct PROTO { @@ -21,12 +25,9 @@ typedef struct PROTO_IMPL bool (*Init)(void **param, CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se); void (*Free)(void *param); char *(*Name)(); - UINT (*SupportedModes)(); - bool (*IsPacketForMe)(const UCHAR *data, const UINT size); + bool (*IsPacketForMe)(const PROTO_MODE mode, const UCHAR *data, const UINT size); bool (*ProcessData)(void *param, TCP_RAW_DATA *received_data, FIFO *data_to_send); void (*BufferLimit)(void *param, const bool reached); - bool (*IsOk)(void *param); - UINT (*EstablishedSessions)(void *param); } PROTO_IMPL; int ProtoImplCompare(void *p1, void *p2); @@ -35,7 +36,7 @@ PROTO *ProtoNew(CEDAR *cedar); void ProtoDelete(PROTO *proto); bool ProtoImplAdd(PROTO *proto, PROTO_IMPL *impl); -PROTO_IMPL *ProtoImplDetect(PROTO *proto, SOCK *sock); +PROTO_IMPL *ProtoImplDetect(PROTO *proto, const PROTO_MODE mode, const UCHAR *data, const UINT size); bool ProtoHandleConnection(PROTO *proto, SOCK *sock); diff --git a/src/Cedar/Proto_OpenVPN.c b/src/Cedar/Proto_OpenVPN.c index f61c3de2..11ca8266 100644 --- a/src/Cedar/Proto_OpenVPN.c +++ b/src/Cedar/Proto_OpenVPN.c @@ -21,12 +21,9 @@ PROTO_IMPL *OvsGetProtoImpl() OvsInit, OvsFree, OvsName, - OvsSupportedModes, OvsIsPacketForMe, OvsProcessData, OvsBufferLimit, - OvsIsOk, - OvsEstablishedSessions }; return &impl; @@ -55,22 +52,30 @@ char *OvsName() return "OpenVPN"; } -// Return the supported modes (TCP & UDP) -UINT OvsSupportedModes() -{ - return PROTO_MODE_TCP | PROTO_MODE_UDP; -} - // Check whether it's an OpenVPN packet -bool OvsIsPacketForMe(const UCHAR *buf, const UINT size) +bool OvsIsPacketForMe(const PROTO_MODE mode, const UCHAR *data, const UINT size) { - if (buf == NULL || size != 2) + if (mode == PROTO_MODE_TCP) { - return false; - } + if (data == NULL || size < 2) + { + return false; + } - if (buf[0] == 0x00 && buf[1] == 0x0E) + if (data[0] == 0x00 && data[1] == 0x0E) + { + return true; + } + } + else if (mode == PROTO_MODE_UDP) { + OPENVPN_PACKET *packet = OvsParsePacket(data, size); + if (packet == NULL) + { + return false; + } + + OvsFreePacket(packet); return true; } @@ -81,16 +86,14 @@ bool OvsProcessData(void *param, TCP_RAW_DATA *received_data, FIFO *data_to_send { bool ret = true; UINT i; - OPENVPN_SERVER *server; + OPENVPN_SERVER *server = param; UCHAR buf[OPENVPN_TCP_MAX_PACKET_SIZE]; - if (param == NULL || received_data == NULL || data_to_send == NULL) + if (server == NULL || received_data == NULL || data_to_send == NULL) { return false; } - server = param; - // Separate to a list of datagrams by interpreting the data received from the TCP socket while (true) { @@ -166,6 +169,21 @@ bool OvsProcessData(void *param, TCP_RAW_DATA *received_data, FIFO *data_to_send DeleteAll(server->SendPacketList); + if (server->Giveup <= server->Now) + { + for (UINT i = 0; i < LIST_NUM(server->SessionList); ++i) + { + OPENVPN_SESSION *se = LIST_DATA(server->SessionList, i); + + if (se->Established) + { + return ret && server->DisconnectCount < 1; + } + } + + return false; + } + return ret; } @@ -179,46 +197,6 @@ void OvsBufferLimit(void *param, const bool reached) ((OPENVPN_SERVER *)param)->SupressSendPacket = reached; } -bool OvsIsOk(void *param) -{ - OPENVPN_SERVER *s; - - if (param == NULL) - { - return false; - } - - s = param; - - return (s->DisconnectCount < 1) && (s->SessionEstablishedCount > 0); -} - -UINT OvsEstablishedSessions(void *param) -{ - LIST *sessions; - UINT i; - UINT established_sessions = 0; - - if (param == NULL) - { - return 0; - } - - sessions = ((OPENVPN_SERVER *)param)->SessionList; - - for (i = 0;i < LIST_NUM(sessions);i++) - { - OPENVPN_SESSION *se = LIST_DATA(sessions, i); - - if (se->Established) - { - ++established_sessions; - } - } - - return established_sessions; -} - // Write the OpenVPN log void OvsLog(OPENVPN_SERVER *s, OPENVPN_SESSION *se, OPENVPN_CHANNEL *c, char *name, ...) { @@ -2924,6 +2902,7 @@ OPENVPN_SERVER *NewOpenVpnServer(CEDAR *cedar, INTERRUPT_MANAGER *interrupt, SOC s->SendPacketList = NewListFast(NULL); s->Now = Tick64(); + s->Giveup = s->Now + OPENVPN_NEW_SESSION_DEADLINE_TIMEOUT; s->NextSessionId = 1; diff --git a/src/Cedar/Proto_OpenVPN.h b/src/Cedar/Proto_OpenVPN.h index 8fae45f3..7c88e6ce 100644 --- a/src/Cedar/Proto_OpenVPN.h +++ b/src/Cedar/Proto_OpenVPN.h @@ -194,6 +194,7 @@ struct OPENVPN_SERVER LIST *SendPacketList; // Transmission packet list LIST *SessionList; // Session list UINT64 Now; // Current time + UINT64 Giveup; // Session establishment deadline SOCK_EVENT *SockEvent; // Socket event UCHAR TmpBuf[OPENVPN_TMP_BUFFER_SIZE]; // Temporary buffer UINT DisconnectCount; // The number of session lost that have occurred so far @@ -220,8 +221,7 @@ PROTO_IMPL *OvsGetProtoImpl(); bool OvsInit(void **param, CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se); void OvsFree(void *param); char *OvsName(); -UINT OvsSupportedModes(); -bool OvsIsPacketForMe(const UCHAR *buf, const UINT size); +bool OvsIsPacketForMe(const PROTO_MODE mode, const UCHAR *data, const UINT size); bool OvsProcessData(void *param, TCP_RAW_DATA *received_data, FIFO *data_to_send); void OvsBufferLimit(void *param, const bool reached); bool OvsIsOk(void *param); From 0570f7d31c72f77d07d1a6438456f32049b27a22 Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Mon, 11 May 2020 07:50:55 +0200 Subject: [PATCH 2/5] Mayaqua/Network: add StopUdpListener() This allows to stop a UDP listener without deleting it. It's especially useful when no datagrams should be received anymore, but there are other threads accessing the listener. --- src/Mayaqua/Network.c | 17 ++++++++++++++--- src/Mayaqua/Network.h | 1 + 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Mayaqua/Network.c b/src/Mayaqua/Network.c index 717d0f9d..14c1f325 100644 --- a/src/Mayaqua/Network.c +++ b/src/Mayaqua/Network.c @@ -19329,6 +19329,19 @@ UDPLISTENER *NewUdpListenerEx(UDPLISTENER_RECV_PROC *recv_proc, void *param, IP return u; } +// Stop the UDP listener +void StopUdpListener(UDPLISTENER *u) +{ + if (u == NULL) + { + return; + } + + u->Halt = true; + SetSockEvent(u->Event); + WaitThread(u->Thread, INFINITE); +} + // Release the UDP listener void FreeUdpListener(UDPLISTENER *u) { @@ -19339,10 +19352,8 @@ void FreeUdpListener(UDPLISTENER *u) return; } - u->Halt = true; - SetSockEvent(u->Event); + StopUdpListener(u); - WaitThread(u->Thread, INFINITE); ReleaseThread(u->Thread); ReleaseSockEvent(u->Event); diff --git a/src/Mayaqua/Network.h b/src/Mayaqua/Network.h index 1744ff70..b99f179a 100644 --- a/src/Mayaqua/Network.h +++ b/src/Mayaqua/Network.h @@ -1357,6 +1357,7 @@ UINT64 GetHostIPAddressListHash(); UDPLISTENER *NewUdpListener(UDPLISTENER_RECV_PROC *recv_proc, void *param, IP *listen_ip); UDPLISTENER *NewUdpListenerEx(UDPLISTENER_RECV_PROC *recv_proc, void *param, IP *listen_ip, UINT packet_type); void UdpListenerThread(THREAD *thread, void *param); +void StopUdpListener(UDPLISTENER *u); void FreeUdpListener(UDPLISTENER *u); void AddPortToUdpListener(UDPLISTENER *u, UINT port); void DeletePortFromUdpListener(UDPLISTENER *u, UINT port); From a3aea00820116a4f941a1563cce7602ef750af42 Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Mon, 11 May 2020 08:22:44 +0200 Subject: [PATCH 3/5] 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. --- src/Cedar/Proto.c | 330 ++++++++++++++++++++++++++++++++++++++++++++++ src/Cedar/Proto.h | 34 ++++- 2 files changed, 363 insertions(+), 1 deletion(-) diff --git a/src/Cedar/Proto.c b/src/Cedar/Proto.c index b15f8e52..d4299da0 100644 --- a/src/Cedar/Proto.c +++ b/src/Cedar/Proto.c @@ -19,6 +19,102 @@ int ProtoImplCompare(void *p1, void *p2) 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; @@ -31,22 +127,36 @@ PROTO *ProtoNew(CEDAR *cedar) 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); @@ -96,6 +206,109 @@ PROTO_IMPL *ProtoImplDetect(PROTO *proto, const PROTO_MODE mode, const UCHAR *da 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; @@ -228,3 +441,120 @@ bool ProtoHandleConnection(PROTO *proto, SOCK *sock) 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); + } +} diff --git a/src/Cedar/Proto.h b/src/Cedar/Proto.h index 0fcc5e69..b9b946f2 100644 --- a/src/Cedar/Proto.h +++ b/src/Cedar/Proto.h @@ -18,6 +18,8 @@ typedef struct PROTO { CEDAR *Cedar; LIST *Impls; + HASH_LIST *Sessions; + UDPLISTENER *UdpListener; } PROTO; typedef struct PROTO_IMPL @@ -26,11 +28,33 @@ typedef struct PROTO_IMPL void (*Free)(void *param); char *(*Name)(); bool (*IsPacketForMe)(const PROTO_MODE mode, const UCHAR *data, const UINT size); - bool (*ProcessData)(void *param, TCP_RAW_DATA *received_data, FIFO *data_to_send); + bool (*ProcessData)(void *param, TCP_RAW_DATA *in, FIFO *out); + bool (*ProcessDatagrams)(void *param, LIST *in, LIST *out); void (*BufferLimit)(void *param, const bool reached); } PROTO_IMPL; +typedef struct PROTO_SESSION +{ + void *Param; + PROTO *Proto; + PROTO_IMPL *Impl; + IP SrcIp; + USHORT SrcPort; + IP DstIp; + USHORT DstPort; + LIST *DatagramsIn; + LIST *DatagramsOut; + SOCK_EVENT *SockEvent; + INTERRUPT_MANAGER *InterruptManager; + THREAD *Thread; + LOCK *Lock; + volatile bool Halt; +} PROTO_SESSION; + int ProtoImplCompare(void *p1, void *p2); +int ProtoSessionCompare(void *p1, void *p2); + +UINT ProtoSessionHash(void *p); PROTO *ProtoNew(CEDAR *cedar); void ProtoDelete(PROTO *proto); @@ -38,6 +62,14 @@ void ProtoDelete(PROTO *proto); bool ProtoImplAdd(PROTO *proto, PROTO_IMPL *impl); PROTO_IMPL *ProtoImplDetect(PROTO *proto, const PROTO_MODE mode, const UCHAR *data, const UINT size); +PROTO_SESSION *ProtoNewSession(PROTO *proto, PROTO_IMPL *impl, const IP *src_ip, const USHORT src_port, const IP *dst_ip, const USHORT dst_port); +void ProtoDeleteSession(PROTO_SESSION *session); + +bool ProtoSetListenIP(PROTO *proto, const IP *ip); +bool ProtoSetUdpPorts(PROTO *proto, const LIST *ports); + bool ProtoHandleConnection(PROTO *proto, SOCK *sock); +void ProtoHandleDatagrams(UDPLISTENER *listener, LIST *datagrams); +void ProtoSessionThread(THREAD *thread, void *param); #endif From 27f7d43ff76d533120b5fa26019fca761f945931 Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Mon, 11 May 2020 08:23:29 +0200 Subject: [PATCH 4/5] Cedar/Proto_OpenVPN: remove UDP system, use the one provided by Proto As a side effect, the DH parameter is now applied to the TCP server as well. Previously, the default value was always used, ignoring the one from the configuration. --- src/Cedar/Proto_OpenVPN.c | 199 ++++++++++++-------------------------- src/Cedar/Proto_OpenVPN.h | 23 +---- 2 files changed, 67 insertions(+), 155 deletions(-) diff --git a/src/Cedar/Proto_OpenVPN.c b/src/Cedar/Proto_OpenVPN.c index 11ca8266..c6acc5fa 100644 --- a/src/Cedar/Proto_OpenVPN.c +++ b/src/Cedar/Proto_OpenVPN.c @@ -23,6 +23,7 @@ PROTO_IMPL *OvsGetProtoImpl() OvsName, OvsIsPacketForMe, OvsProcessData, + OvsProcessDatagrams, OvsBufferLimit, }; @@ -82,14 +83,14 @@ bool OvsIsPacketForMe(const PROTO_MODE mode, const UCHAR *data, const UINT size) return false; } -bool OvsProcessData(void *param, TCP_RAW_DATA *received_data, FIFO *data_to_send) +bool OvsProcessData(void *param, TCP_RAW_DATA *in, FIFO *out) { bool ret = true; UINT i; OPENVPN_SERVER *server = param; UCHAR buf[OPENVPN_TCP_MAX_PACKET_SIZE]; - if (server == NULL || received_data == NULL || data_to_send == NULL) + if (server == NULL || in == NULL || out == NULL) { return false; } @@ -99,7 +100,7 @@ bool OvsProcessData(void *param, TCP_RAW_DATA *received_data, FIFO *data_to_send { UDPPACKET *packet; USHORT payload_size, packet_size; - FIFO *fifo = received_data->Data; + FIFO *fifo = in->Data; const UINT fifo_size = FifoSize(fifo); if (fifo_size < sizeof(USHORT)) @@ -133,13 +134,12 @@ bool OvsProcessData(void *param, TCP_RAW_DATA *received_data, FIFO *data_to_send } // Insert packet into the list - packet = NewUdpPacket(&received_data->SrcIP, received_data->SrcPort, &received_data->DstIP, received_data->DstPort, Clone(buf + sizeof(USHORT), payload_size), payload_size); - packet->Type = OPENVPN_PROTOCOL_TCP; + packet = NewUdpPacket(&in->SrcIP, in->SrcPort, &in->DstIP, in->DstPort, Clone(buf + sizeof(USHORT), payload_size), payload_size); Add(server->RecvPacketList, packet); } // Process the list of received datagrams - OvsRecvPacket(server, server->RecvPacketList); + OvsRecvPacket(server, server->RecvPacketList, OPENVPN_PROTOCOL_TCP); // Release the received packet list for (i = 0; i < LIST_NUM(server->RecvPacketList); ++i) @@ -158,10 +158,10 @@ bool OvsProcessData(void *param, TCP_RAW_DATA *received_data, FIFO *data_to_send // Store the size in the TCP send queue first USHORT us = Endian16((USHORT)p->Size); - WriteFifo(data_to_send, &us, sizeof(USHORT)); + WriteFifo(out, &us, sizeof(USHORT)); // Write the data body - WriteFifo(data_to_send, p->Data, p->Size); + WriteFifo(out, p->Data, p->Size); // Packet release FreeUdpPacket(p); @@ -171,7 +171,8 @@ bool OvsProcessData(void *param, TCP_RAW_DATA *received_data, FIFO *data_to_send if (server->Giveup <= server->Now) { - for (UINT i = 0; i < LIST_NUM(server->SessionList); ++i) + UINT i; + for (i = 0; i < LIST_NUM(server->SessionList); ++i) { OPENVPN_SESSION *se = LIST_DATA(server->SessionList, i); @@ -187,6 +188,47 @@ bool OvsProcessData(void *param, TCP_RAW_DATA *received_data, FIFO *data_to_send return ret; } +bool OvsProcessDatagrams(void *param, LIST *in, LIST *out) +{ + UINT i; + LIST *to_send; + OPENVPN_SERVER *server = param; + + if (server == NULL || in == NULL || out == NULL) + { + return false; + } + + OvsRecvPacket(server, in, OPENVPN_PROTOCOL_UDP); + + to_send = server->SendPacketList; + + for (i = 0; i < LIST_NUM(to_send); ++i) + { + Add(out, LIST_DATA(to_send, i)); + } + + DeleteAll(server->SendPacketList); + + if (server->Giveup <= server->Now) + { + UINT i; + for (i = 0; i < LIST_NUM(server->SessionList); ++i) + { + OPENVPN_SESSION *se = LIST_DATA(server->SessionList, i); + + if (se->Established) + { + return server->DisconnectCount < 1; + } + } + + return false; + } + + return true; +} + void OvsBufferLimit(void *param, const bool reached) { if (param == NULL) @@ -507,7 +549,7 @@ final: } // Process the received packet -void OvsProceccRecvPacket(OPENVPN_SERVER *s, UDPPACKET *p) +void OvsProceccRecvPacket(OPENVPN_SERVER *s, UDPPACKET *p, UINT protocol) { OPENVPN_CHANNEL *c; OPENVPN_SESSION *se; @@ -519,7 +561,7 @@ void OvsProceccRecvPacket(OPENVPN_SERVER *s, UDPPACKET *p) } // Search for the session - se = OvsFindOrCreateSession(s, &p->DstIP, p->DestPort, &p->SrcIP, p->SrcPort, p->Type); + se = OvsFindOrCreateSession(s, &p->DstIP, p->DestPort, &p->SrcIP, p->SrcPort, protocol); if (se == NULL) { return; @@ -749,8 +791,13 @@ void OvsProcessRecvControlPacket(OPENVPN_SERVER *s, OPENVPN_SESSION *se, OPENVPN // Create an SSL pipe Lock(s->Cedar->lock); { - bool cert_verify = true; - c->SslPipe = NewSslPipeEx(true, s->Cedar->ServerX, s->Cedar->ServerK, s->Dh, cert_verify, &c->ClientCert); + if (s->Dh->Size != s->Cedar->DhParamBits) + { + DhFree(s->Dh); + s->Dh = DhNewFromBits(s->Cedar->DhParamBits); + } + + c->SslPipe = NewSslPipeEx(true, s->Cedar->ServerX, s->Cedar->ServerK, s->Dh, true, &c->ClientCert); } Unlock(s->Cedar->lock); @@ -2165,7 +2212,7 @@ OPENVPN_SESSION *OvsSearchSession(OPENVPN_SERVER *s, IP *server_ip, UINT server_ } // Receive packets in the OpenVPN server -void OvsRecvPacket(OPENVPN_SERVER *s, LIST *recv_packet_list) +void OvsRecvPacket(OPENVPN_SERVER *s, LIST *recv_packet_list, UINT protocol) { UINT i, j; LIST *delete_session_list = NULL; @@ -2197,7 +2244,7 @@ void OvsRecvPacket(OPENVPN_SERVER *s, LIST *recv_packet_list) { UDPPACKET *p = LIST_DATA(recv_packet_list, i); - OvsProceccRecvPacket(s, p); + OvsProceccRecvPacket(s, p, protocol); } // Treat for all sessions and all channels @@ -2908,7 +2955,7 @@ OPENVPN_SERVER *NewOpenVpnServer(CEDAR *cedar, INTERRUPT_MANAGER *interrupt, SOC OvsLog(s, NULL, NULL, "LO_START"); - s->Dh = DhNewFromBits(DH_PARAM_BITS_DEFAULT); + s->Dh = DhNewFromBits(cedar->DhParamBits); return s; } @@ -2956,123 +3003,3 @@ void FreeOpenVpnServer(OPENVPN_SERVER *s) Free(s); } - -// UDP reception procedure -void OpenVpnServerUdpListenerProc(UDPLISTENER *u, LIST *packet_list) -{ - OPENVPN_SERVER_UDP *us; - // Validate arguments - if (u == NULL || packet_list == NULL) - { - return; - } - - us = (OPENVPN_SERVER_UDP *)u->Param; - - if (us->OpenVpnServer != NULL) - { - { - u->PollMyIpAndPort = false; - - ClearStr(us->Cedar->OpenVPNPublicPorts, sizeof(us->Cedar->OpenVPNPublicPorts)); - } - - OvsRecvPacket(us->OpenVpnServer, packet_list); - - UdpListenerSendPackets(u, us->OpenVpnServer->SendPacketList); - DeleteAll(us->OpenVpnServer->SendPacketList); - } -} - -// Create an OpenVPN server (UDP mode) -OPENVPN_SERVER_UDP *NewOpenVpnServerUdp(CEDAR *cedar) -{ - OPENVPN_SERVER_UDP *u; - // Validate arguments - if (cedar == NULL) - { - return NULL; - } - - u = ZeroMalloc(sizeof(OPENVPN_SERVER_UDP)); - - u->Cedar = cedar; - - AddRef(u->Cedar->ref); - - // Create a UDP listener - u->UdpListener = NewUdpListenerEx(OpenVpnServerUdpListenerProc, u, &cedar->Server->ListenIP, OPENVPN_PROTOCOL_UDP); - - // Create an OpenVPN server - u->OpenVpnServer = NewOpenVpnServer(cedar, u->UdpListener->Interrupts, u->UdpListener->Event); - - return u; -} - -void OpenVpnServerUdpSetDhParam(OPENVPN_SERVER_UDP *u, DH_CTX *dh) -{ - // Validate arguments - if (u == NULL) { - return; - } - - if (u->OpenVpnServer->Dh) - { - DhFree(u->OpenVpnServer->Dh); - } - - u->OpenVpnServer->Dh = dh; -} - -// Apply the port list to the OpenVPN server -void OvsApplyUdpPortList(OPENVPN_SERVER_UDP *u, char *port_list, IP *listen_ip) -{ - LIST *o; - UINT i; - // Validate arguments - if (u == NULL) - { - return; - } - - DeleteAllPortFromUdpListener(u->UdpListener); - - if (u->UdpListener != NULL && listen_ip != NULL) - { - Copy(&u->UdpListener->ListenIP, listen_ip, sizeof(IP)); - } - - o = StrToIntList(port_list, true); - - for (i = 0;i < LIST_NUM(o);i++) - { - UINT port = *((UINT *)LIST_DATA(o, i)); - - if (port >= 1 && port <= 65535) - { - AddPortToUdpListener(u->UdpListener, port); - } - } - - ReleaseIntList(o); -} - -// Release the OpenVPN server (UDP mode) -void FreeOpenVpnServerUdp(OPENVPN_SERVER_UDP *u) -{ - // Validate arguments - if (u == NULL) - { - return; - } - - // Stop the UDP listener - FreeUdpListener(u->UdpListener); - - // Release the OpenVPN server - FreeOpenVpnServer(u->OpenVpnServer); - - ReleaseCedar(u->Cedar); - - Free(u); -} diff --git a/src/Cedar/Proto_OpenVPN.h b/src/Cedar/Proto_OpenVPN.h index 7c88e6ce..39324b84 100644 --- a/src/Cedar/Proto_OpenVPN.h +++ b/src/Cedar/Proto_OpenVPN.h @@ -204,15 +204,6 @@ struct OPENVPN_SERVER UINT SessionEstablishedCount; // Number of session establishment }; -// OpenVPN server (UDP mode) -struct OPENVPN_SERVER_UDP -{ - CEDAR *Cedar; - UDPLISTENER *UdpListener; // UDP listener - OPENVPN_SERVER *OpenVpnServer; // OpenVPN server - UINT64 VgsNextGetPublicPortsTick; -}; - // OpenVPN Default Client Option String #define OVPN_DEF_CLIENT_OPTION_STRING "dev-type tun,link-mtu 1500,tun-mtu 1500,cipher AES-128-CBC,auth SHA1,keysize 128,key-method 2,tls-client" @@ -222,20 +213,16 @@ bool OvsInit(void **param, CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se); void OvsFree(void *param); char *OvsName(); bool OvsIsPacketForMe(const PROTO_MODE mode, const UCHAR *data, const UINT size); -bool OvsProcessData(void *param, TCP_RAW_DATA *received_data, FIFO *data_to_send); +bool OvsProcessData(void *param, TCP_RAW_DATA *in, FIFO *out); +bool OvsProcessDatagrams(void *param, LIST *in, LIST *out); void OvsBufferLimit(void *param, const bool reached); bool OvsIsOk(void *param); UINT OvsEstablishedSessions(void *param); -OPENVPN_SERVER_UDP *NewOpenVpnServerUdp(CEDAR *cedar); -void FreeOpenVpnServerUdp(OPENVPN_SERVER_UDP *u); -void OpenVpnServerUdpListenerProc(UDPLISTENER *u, LIST *packet_list); -void OvsApplyUdpPortList(OPENVPN_SERVER_UDP *u, char *port_list, IP *listen_ip); - OPENVPN_SERVER *NewOpenVpnServer(CEDAR *cedar, INTERRUPT_MANAGER *interrupt, SOCK_EVENT *sock_event); void FreeOpenVpnServer(OPENVPN_SERVER *s); -void OvsRecvPacket(OPENVPN_SERVER *s, LIST *recv_packet_list); -void OvsProceccRecvPacket(OPENVPN_SERVER *s, UDPPACKET *p); +void OvsRecvPacket(OPENVPN_SERVER *s, LIST *recv_packet_list, UINT protocol); +void OvsProceccRecvPacket(OPENVPN_SERVER *s, UDPPACKET *p, UINT protocol); int OvsCompareSessionList(void *p1, void *p2); OPENVPN_SESSION *OvsSearchSession(OPENVPN_SERVER *s, IP *server_ip, UINT server_port, IP *client_ip, UINT client_port, UINT protocol); OPENVPN_SESSION *OvsNewSession(OPENVPN_SERVER *s, IP *server_ip, UINT server_port, IP *client_ip, UINT client_port, UINT protocol); @@ -279,6 +266,4 @@ UINT OvsCalcTcpMss(OPENVPN_SERVER *s, OPENVPN_SESSION *se, OPENVPN_CHANNEL *c); CIPHER *OvsGetCipher(char *name); MD *OvsGetMd(char *name); -void OpenVpnServerUdpSetDhParam(OPENVPN_SERVER_UDP *u, DH_CTX *dh); - #endif // PROTO_OPENVPN_H From 981b57ee28bc54d066cf11a5a8e8579c37d99043 Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Mon, 11 May 2020 07:59:34 +0200 Subject: [PATCH 5/5] Cedar/Server: set ports in Proto, remove OpenVPN UDP server leftovers The setting's name is still "OpenVPN_UdpPortList". We will change it as soon as there's another UDP protocol implemented in Proto. --- src/Cedar/Server.c | 38 +++++++++----------------------------- src/Cedar/Server.h | 1 - 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/src/Cedar/Server.c b/src/Cedar/Server.c index 8f43404a..417a4480 100644 --- a/src/Cedar/Server.c +++ b/src/Cedar/Server.c @@ -33,6 +33,8 @@ void SiSetOpenVPNAndSSTPConfig(SERVER *s, OPENVPN_SSTP_CONFIG *c) Lock(s->OpenVpnSstpConfigLock); { + LIST *ports; + // Save the settings if (s->Cedar->Bridge || s->ServerType != SERVER_TYPE_STANDALONE) { @@ -45,24 +47,14 @@ void SiSetOpenVPNAndSSTPConfig(SERVER *s, OPENVPN_SSTP_CONFIG *c) s->DisableOpenVPNServer = !c->EnableOpenVPN; } - NormalizeIntListStr(s->OpenVpnServerUdpPorts, sizeof(s->OpenVpnServerUdpPorts), - c->OpenVPNPortList, true, ", "); + // TODO: Now that we have a unified protocol interface (Proto), the setting's name should be changed. + NormalizeIntListStr(s->OpenVpnServerUdpPorts, sizeof(s->OpenVpnServerUdpPorts), c->OpenVPNPortList, true, ", "); + ports = StrToIntList(s->OpenVpnServerUdpPorts, true); + ProtoSetUdpPorts(s->Proto, ports); + ReleaseIntList(ports); s->Cedar->OpenVPNObfuscation = c->OpenVPNObfuscation; StrCpy(s->Cedar->OpenVPNObfuscationMask, sizeof(s->Cedar->OpenVPNObfuscationMask), c->OpenVPNObfuscationMask); - - // Apply the OpenVPN configuration - if (s->OpenVpnServerUdp != NULL) - { - if (s->DisableOpenVPNServer) - { - OvsApplyUdpPortList(s->OpenVpnServerUdp, "", NULL); - } - else - { - OvsApplyUdpPortList(s->OpenVpnServerUdp, s->OpenVpnServerUdpPorts, &s->ListenIP); - } - } } Unlock(s->OpenVpnSstpConfigLock); } @@ -2629,8 +2621,6 @@ void SiInitConfiguration(SERVER *s) s->Proto = ProtoNew(s->Cedar); // IPsec server s->IPsecServer = NewIPsecServer(s->Cedar); - // OpenVPN server (UDP) - s->OpenVpnServerUdp = NewOpenVpnServerUdp(s->Cedar); } SLog(s->Cedar, "LS_LOAD_CONFIG_1"); @@ -5660,7 +5650,9 @@ void SiLoadServerCfg(SERVER *s, FOLDER *f) } s->DontBackupConfig = CfgGetBool(f, "DontBackupConfig"); + CfgGetIp(f, "ListenIP", &s->ListenIP); + ProtoSetListenIP(s->Proto, &s->ListenIP); if (CfgIsItem(f, "BackupConfigOnlyWhenModified")) { @@ -6036,10 +6028,6 @@ void SiLoadServerCfg(SERVER *s, FOLDER *f) } SetDhParam(DhNewFromBits(c->DhParamBits)); - if (s->OpenVpnServerUdp) - { - OpenVpnServerUdpSetDhParam(s->OpenVpnServerUdp, DhNewFromBits(c->DhParamBits)); - } } Unlock(c->lock); @@ -6549,14 +6537,6 @@ void SiFreeConfiguration(SERVER *s) s->IPsecServer = NULL; } - // Terminate the OpenVPN server - if (s->OpenVpnServerUdp != NULL) - { - FreeOpenVpnServerUdp(s->OpenVpnServerUdp); - s->OpenVpnServerUdp = NULL; - } - - // Terminate the DDNS client if (s->DDnsClient != NULL) { diff --git a/src/Cedar/Server.h b/src/Cedar/Server.h index 75225ecf..cd26634d 100644 --- a/src/Cedar/Server.h +++ b/src/Cedar/Server.h @@ -244,7 +244,6 @@ struct SERVER PROTO *Proto; // Protocols handler IPSEC_SERVER *IPsecServer; // IPsec server function - OPENVPN_SERVER_UDP *OpenVpnServerUdp; // OpenVPN server function char OpenVpnServerUdpPorts[MAX_SIZE]; // UDP port list string DDNS_CLIENT *DDnsClient; // DDNS client feature LOCK *OpenVpnSstpConfigLock; // Lock OpenVPN and SSTP configuration