From 7d58e6bf60a65af1f00b43155406a8ceb497df51 Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Fri, 26 Jul 2019 08:36:54 +0200 Subject: [PATCH] Add interface for easy protocol implementation This commit adds a protocol interface to the server, its purpose is to manage TCP connections and the various third-party protocols. More specifically, ProtoHandleConnection() takes care of exchanging the packets between the local and remote endpoint; the protocol implementation only has to parse them and act accordingly. The interface knows which protocol is the connection for by calling IsPacketForMe(), a function implemented for each protocol. --- src/Cedar/Cedar.c | 6 + src/Cedar/Cedar.h | 4 +- src/Cedar/Cedar.vcproj | 8 ++ src/Cedar/Connection.c | 79 ++---------- src/Cedar/Proto.c | 271 +++++++++++++++++++++++++++++++++++++++++ src/Cedar/Proto.h | 44 +++++++ src/Cedar/Protocol.c | 2 +- src/Mayaqua/MayaType.h | 1 + src/Mayaqua/Network.c | 34 ++++++ src/Mayaqua/Network.h | 12 ++ 10 files changed, 392 insertions(+), 69 deletions(-) create mode 100644 src/Cedar/Proto.c create mode 100644 src/Cedar/Proto.h diff --git a/src/Cedar/Cedar.c b/src/Cedar/Cedar.c index c4a6f56a..b8aa5254 100644 --- a/src/Cedar/Cedar.c +++ b/src/Cedar/Cedar.c @@ -1606,6 +1606,9 @@ void InitCedar() // Initialize protocol module InitProtocol(); + + // Initialize third-party protocol interface + ProtoInit(); } // Free Cedar communication module @@ -1616,6 +1619,9 @@ void FreeCedar() return; } + // Free third-party protocol interface + ProtoFree(); + // Free protocol module FreeProtocol(); } diff --git a/src/Cedar/Cedar.h b/src/Cedar/Cedar.h index f9b6b320..50bd61ae 100644 --- a/src/Cedar/Cedar.h +++ b/src/Cedar/Cedar.h @@ -305,8 +305,7 @@ #define CONNECTION_TYPE_ADMIN_RPC 5 // RPC for Management #define CONNECTION_TYPE_ENUM_HUB 6 // HUB enumeration #define CONNECTION_TYPE_PASSWORD 7 // Password change -#define CONNECTION_TYPE_SSTP 8 // SSTP -#define CONNECTION_TYPE_OPENVPN 9 // OpenVPN +#define CONNECTION_TYPE_OTHER 0xffffffff // E.g. Third-party protocol // Protocol #define CONNECTION_TCP 0 // TCP protocol @@ -1031,6 +1030,7 @@ typedef struct CEDAR // Layer-2/Layer-3 converter #include // Third party protocols +#include #include #include #include diff --git a/src/Cedar/Cedar.vcproj b/src/Cedar/Cedar.vcproj index 07d866f8..9faa83fc 100644 --- a/src/Cedar/Cedar.vcproj +++ b/src/Cedar/Cedar.vcproj @@ -650,6 +650,10 @@ RelativePath=".\NullLan.c" > + + @@ -1280,6 +1284,10 @@ RelativePath=".\NullLan.h" > + + diff --git a/src/Cedar/Connection.c b/src/Cedar/Connection.c index 71e2d0bb..ae8ddde4 100644 --- a/src/Cedar/Connection.c +++ b/src/Cedar/Connection.c @@ -2901,21 +2901,8 @@ void ConnectionAccept(CONNECTION *c) X *x; K *k; char tmp[128]; - UCHAR openssl_check_buf[2]; - char *error_details = NULL; - SERVER *server; - UCHAR *peek_buf = NULL; - UINT peek_buf_size = 1500; - char sni[256] = {0}; - bool native1 = false; - bool native2 = false; - bool native3 = false; - bool no_native = false; - UINT peek_size = 0; UINT initial_timeout = CONNECTING_TIMEOUT; - bool no_peek_log = false; UCHAR ctoken_hash[SHA1_SIZE]; - bool no_write_ctoken_log = false; // Validate arguments if (c == NULL) @@ -2925,13 +2912,7 @@ void ConnectionAccept(CONNECTION *c) Zero(ctoken_hash, sizeof(ctoken_hash)); - peek_buf = ZeroMalloc(peek_buf_size); - - Debug("ConnectionAccept()\n"); - - server = c->Cedar->Server; - - // get a socket + // Get a socket s = c->FirstSock; AddRef(s->ref); @@ -2945,37 +2926,18 @@ void ConnectionAccept(CONNECTION *c) initial_timeout += GetMachineRand() % (CONNECTING_TIMEOUT / 2); SetTimeout(s, initial_timeout); - - // Peek whether OpenSSL packet - if (s->IsReverseAcceptedSocket == false) + // Handle third-party protocols + if (s->IsReverseAcceptedSocket == false && s->Type == SOCK_TCP) { - if (s->Type == SOCK_TCP && (c->Cedar != NULL && c->Cedar->Server != NULL && c->Cedar->Server->DisableOpenVPNServer == false)) + if (c->Cedar != NULL && c->Cedar->Server != NULL) { - if (Peek(s, openssl_check_buf, sizeof(openssl_check_buf)) == sizeof(openssl_check_buf)) + c->Type = CONNECTION_TYPE_OTHER; + + if (ProtoHandleConnection(c->Cedar, s) == true) { - if (OvsCheckTcpRecvBufIfOpenVPNProtocol(openssl_check_buf, sizeof(openssl_check_buf))) - { - // Detect OpenSSL packet - Debug("Detect OpenSSL on TCP!\n"); - - no_native = true; - - if (OvsGetNoOpenVpnTcp() == false) - { - // Do OpenSSL processing - c->Type = CONNECTION_TYPE_OPENVPN; - if (OvsPerformTcpServer(c->Cedar, s) == false) - { - error_details = "OpenVPN_TCP_Aborted"; - } - } - - goto ERROR; - } + goto FINAL; } } - - } // Specify the encryption algorithm @@ -2992,22 +2954,18 @@ void ConnectionAccept(CONNECTION *c) Unlock(c->Cedar->lock); // Start the SSL communication - Debug("StartSSL()\n"); Copy(&s->SslAcceptSettings, &c->Cedar->SslAcceptSettings, sizeof(SSL_ACCEPT_SETTINGS)); if (StartSSL(s, x, k) == false) { // Failed AddNoSsl(c->Cedar, &s->RemoteIP); - Debug("Failed to StartSSL.\n"); + Debug("ConnectionAccept(): StartSSL() failed\n"); FreeX(x); FreeK(k); - error_details = "StartSSL"; - - goto ERROR; + goto FINAL; } - FreeX(x); FreeK(k); @@ -3019,29 +2977,18 @@ void ConnectionAccept(CONNECTION *c) if (ServerAccept(c) == false) { // Failed - Debug("ServerAccept Failed. Err = %u\n", c->Err); - goto ERROR; + Debug("ConnectionAccept(): ServerAccept() failed with error %u\n", c->Err); } +FINAL: if (c->flag1 == false) { Debug("%s %u c->flag1 == false\n", __FILE__, __LINE__); Disconnect(s); } + DelConnection(c->Cedar, c); ReleaseSock(s); - - Free(peek_buf); - return; - -ERROR: - Debug("ConnectionAccept() Error.\n"); - - - Disconnect(s); - DelConnection(c->Cedar, c); - ReleaseSock(s); - Free(peek_buf); } // Stop the threads putting additional connection of all that are currently running diff --git a/src/Cedar/Proto.c b/src/Cedar/Proto.c new file mode 100644 index 00000000..084632b6 --- /dev/null +++ b/src/Cedar/Proto.c @@ -0,0 +1,271 @@ +#include "CedarPch.h" + +#include "Proto_OpenVPN.h" + +static LIST *protocols = NULL; + +int ProtoCompare(void *p1, void *p2) +{ + PROTO *proto_1, *proto_2; + + if (p1 == NULL || p2 == NULL) + { + return 0; + } + + proto_1 = (PROTO *)p1; + proto_2 = (PROTO *)p2; + + if (StrCmp(proto_1->impl->Name(), proto_2->impl->Name()) == 0) + { + return true; + } + + return false; +} + +void ProtoInit() +{ + if (protocols != NULL) + { + ProtoFree(); + } + + protocols = NewList(ProtoCompare); + + // OpenVPN + ProtoAdd(OvsGetProtoImpl()); +} + +void ProtoFree() +{ + UINT i; + PROTO_IMPL *impl; + + for (i = 0; i < ProtoNum(); ++i) + { + PROTO *proto = ProtoGet(i); + impl = proto->impl; + Free(proto); + } + + ReleaseList(protocols); + protocols = NULL; +} + +bool ProtoAdd(PROTO_IMPL *impl) +{ + PROTO *proto; + + if (protocols == NULL || impl == NULL) + { + return false; + } + + proto = Malloc(sizeof(PROTO)); + proto->impl = impl; + + Add(protocols, proto); + + Debug("ProtoAdd(): added %s\n", proto->impl->Name()); + + return true; +} + +UINT ProtoNum() +{ + return LIST_NUM(protocols); +} + +PROTO *ProtoGet(const UINT index) +{ + return LIST_DATA(protocols, index); +} + +PROTO *ProtoDetect(SOCK *sock) +{ + UCHAR buf[PROTO_CHECK_BUFFER_SIZE]; + UINT i; + + if (sock == NULL) + { + return NULL; + } + + if (Peek(sock, buf, sizeof(buf)) == 0) + { + return false; + } + + for (i = 0; i < ProtoNum(); ++i) + { + PROTO *p = ProtoGet(i); + if (p->impl->IsPacketForMe(buf, sizeof(buf))) + { + Debug("ProtoDetect(): %s detected\n", p->impl->Name()); + return p; + } + } + + return NULL; +} + +bool ProtoHandleConnection(CEDAR *cedar, SOCK *sock) +{ + void *impl_data; + const PROTO_IMPL *impl; + const PROTO *proto; + + UCHAR *buf; + TCP_RAW_DATA *recv_raw_data; + FIFO *send_fifo; + INTERRUPT_MANAGER *im; + SOCK_EVENT *se; + + const UINT64 giveup = Tick64() + (UINT64)OPENVPN_NEW_SESSION_DEADLINE_TIMEOUT; + + if (cedar == NULL || sock == NULL) + { + return false; + } + + proto = ProtoDetect(sock); + + if (proto == NULL) + { + Debug("ProtoHandleConnection(): unrecognized protocol\n"); + return false; + } + + impl = proto->impl; + + if (StrCmp(impl->Name(), "OpenVPN") == 0 && cedar->Server->DisableOpenVPNServer == true) + { + Debug("ProtoHandleConnection(): OpenVPN detected, but it's disabled\n"); + return false; + } + + if ((impl->SupportedModes() & PROTO_MODE_TCP) == false) + { + return false; + } + + im = NewInterruptManager(); + se = NewSockEvent(); + + if (impl->Init != NULL && impl->Init(&impl_data, 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 (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 + 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; +} diff --git a/src/Cedar/Proto.h b/src/Cedar/Proto.h new file mode 100644 index 00000000..e2a89a25 --- /dev/null +++ b/src/Cedar/Proto.h @@ -0,0 +1,44 @@ +#ifndef PROTO_H +#define PROTO_H + +// OpenVPN sends 2 bytes, thus this is the buffer size. +// If another protocol requires more bytes to be detected, the buffer size must be increased. +#define PROTO_CHECK_BUFFER_SIZE 2 + +#define PROTO_TCP_BUFFER_SIZE (128 * 1024) + +#define PROTO_MODE_TCP 1 +#define PROTO_MODE_UDP 2 + +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 (*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; + +typedef struct PROTO +{ + PROTO_IMPL *impl; +} PROTO; + +int ProtoCompare(void *p1, void *p2); + +void ProtoInit(); +void ProtoFree(); + +bool ProtoAdd(PROTO_IMPL *impl); + +UINT ProtoNum(); +PROTO *ProtoGet(const UINT index); +PROTO *ProtoDetect(SOCK *sock); + +bool ProtoHandleConnection(CEDAR *cedar, SOCK *sock); + +#endif diff --git a/src/Cedar/Protocol.c b/src/Cedar/Protocol.c index e54dc353..36db2d0a 100644 --- a/src/Cedar/Protocol.c +++ b/src/Cedar/Protocol.c @@ -6412,7 +6412,7 @@ bool ServerDownloadSignature(CONNECTION *c, char **error_detail_str) { bool sstp_ret; // Accept the SSTP connection - c->Type = CONNECTION_TYPE_SSTP; + c->Type = CONNECTION_TYPE_OTHER; sstp_ret = AcceptSstp(c); diff --git a/src/Mayaqua/MayaType.h b/src/Mayaqua/MayaType.h index ce3f9492..b00174be 100644 --- a/src/Mayaqua/MayaType.h +++ b/src/Mayaqua/MayaType.h @@ -421,6 +421,7 @@ typedef struct TUBEPAIR_DATA TUBEPAIR_DATA; typedef struct UDPLISTENER UDPLISTENER; typedef struct UDPLISTENER_SOCK UDPLISTENER_SOCK; typedef struct UDPPACKET UDPPACKET; +typedef struct TCP_RAW_DATA TCP_RAW_DATA; typedef struct INTERRUPT_MANAGER INTERRUPT_MANAGER; typedef struct TUBE_FLUSH_LIST TUBE_FLUSH_LIST; typedef struct ICMP_RESULT ICMP_RESULT; diff --git a/src/Mayaqua/Network.c b/src/Mayaqua/Network.c index 38422823..eee6d40c 100644 --- a/src/Mayaqua/Network.c +++ b/src/Mayaqua/Network.c @@ -18980,6 +18980,40 @@ UDPLISTENER_SOCK *DetermineUdpSocketForSending(UDPLISTENER *u, UDPPACKET *p) return NULL; } +void FreeTcpRawData(TCP_RAW_DATA *trd) +{ + // Validate arguments + if (trd == NULL) + { + return; + } + + ReleaseFifo(trd->Data); + Free(trd); +} + +TCP_RAW_DATA *NewTcpRawData(IP *src_ip, UINT src_port, IP *dst_ip, UINT dst_port) +{ + TCP_RAW_DATA *trd; + // Validate arguments + if (dst_ip == NULL || dst_port == 0) + { + return NULL; + } + + trd = ZeroMalloc(sizeof(TCP_RAW_DATA)); + + Copy(&trd->SrcIP, src_ip, sizeof(IP)); + trd->SrcPort = src_port; + + Copy(&trd->DstIP, dst_ip, sizeof(IP)); + trd->DstPort = dst_port; + + trd->Data = NewFifoFast(); + + return trd; +} + // Release of the UDP packet void FreeUdpPacket(UDPPACKET *p) { diff --git a/src/Mayaqua/Network.h b/src/Mayaqua/Network.h index a9a980a6..b44c8f69 100644 --- a/src/Mayaqua/Network.h +++ b/src/Mayaqua/Network.h @@ -456,6 +456,16 @@ struct TUBEPAIR_DATA SOCK_EVENT *SockEvent1, *SockEvent2; // SockEvent }; +// TCP raw data +struct TCP_RAW_DATA +{ + IP SrcIP; // Source IP address + IP DstIP; // Destination IP address + UINT SrcPort; // Source port + UINT DstPort; // Destination port + FIFO *Data; // Data body +}; + // UDP listener socket entry struct UDPLISTENER_SOCK { @@ -1411,6 +1421,8 @@ void AddPortToUdpListener(UDPLISTENER *u, UINT port); void DeletePortFromUdpListener(UDPLISTENER *u, UINT port); void DeleteAllPortFromUdpListener(UDPLISTENER *u); void UdpListenerSendPackets(UDPLISTENER *u, LIST *packet_list); +TCP_RAW_DATA *NewTcpRawData(IP *src_ip, UINT src_port, IP *dst_ip, UINT dst_port); +void FreeTcpRawData(TCP_RAW_DATA *trd); UDPPACKET *NewUdpPacket(IP *src_ip, UINT src_port, IP *dst_ip, UINT dst_port, void *data, UINT size); void FreeUdpPacket(UDPPACKET *p); UDPLISTENER_SOCK *DetermineUdpSocketForSending(UDPLISTENER *u, UDPPACKET *p);