/*H********************************************************************************/ /*! \File prototunnel.c \Description ProtoTunnel creates and manages a Virtual DirtySock Tunnel connection. The tunnel transparently bundles data sent from multiple ports to a specific remote host into a single send, optionally encrypting packets based on the tunnel mappings set up by the caller. Only data sent over a UDP socket may be tunneled. ProtoTunnel does not itself send packets unsolicited, it merely encapsulates packets being sent by other API's like CommUDP or VoIP. Therefore, ProtoTunnel packets always include user data. \Notes \verbatim Encryption Key ProtoTunnel assumes that the encryption key used is provided by the caller creating the tunnel. It does not provide any mechanism for secure key exchange; this is left to the caller. A good random encryption key of at least sixty-four characters is strongly recommended to provide adequate security. Packet format ProtoTunnel bundles multiple packets bound for different ports on the same destination address into a single packet. There are two types of packets, handshake packets and regular packets. A handshake packet is identical in layout to a regular packet, with the addition of handshake data. Packets contain the following data elements: - Two-byte Ident field - Two-byte tunnel encryption header - Handshake info (handshake packets only) - Encrypted list of one or more two-byte sub-packet headers - Encrypted HMAC message digest - Encrypted packet data (if any) - Unencrypted packet data (if any) 0 8 15 ------------------------------- | Ident | Ident field ------------------------------- | Encryption | Encryption field ------------------------------- | | | Handshake | Handshake field (handshake packets only) | | ------------------------------- | Packet size | PIdx | Packet #1 header ------------------------------- | Packet size | PIdx | Packet #2 header ------------------------------- . . . ------------------------------- | Packet size | PIdx | Packet #n header ------------------------------- | | | HMAC | HMAC message digest (length dependent on HMAC type and size, may be zero) | | ------------------------------- | | | Encrypted Packet Data | Encrypted Packet Data | | ------------------------------- | | | Unencrypted Packet Data | Unencrypted Packet Data | | ------------------------------- Ident field The Ident field contains one of two values; handshake packets are identified by the high bit being set (0x8000) and otherwise contain a two byte protocol version identifier. For example, an Ident field of 0x8101 would indicate a handshake packet using protocol version 1.1. Non-handshake packets do not include the high bit, with the lower 15 bits used to indicate the remote tunnel index on an active connection. The ident field is authenticated but not encrypted, so it may be processed before the packet is decrypted. During handshaking, an endpoint receiving packets with a lesser version of the protocol specified will downgrade to that version. (Note that downgrading to versions older than 1.1 is not supported). The ident field is the only field that is required to exist in any given ProtoTunnel protocol version. Encryption field The Encryption field contains a 16bit stream index used to synchronize the stream cipher in the event of lost packets. The high bit (0x8000) is reserved to indicate that the packet is encrypted. The remaining 15bit value is shifted up by three bits to give a total window size of 256k. Because of the shift, the stream cipher encrypts in blocks of eight bytes. This requires the stream cipher to be iterated over the amount of data that is encrypted, and then advanced by the number of bytes required to round up to the next multiple of eight bytes. In the event one or more tunnel packets are lost, the receiving side will advance the stream cipher by the amount of data lost to remain synchronized with the sender. A packet received that has a negative stream offset relative to the current offset is assumed to be a previous packet. ProtoTunnel will make an attempt to decrypt such a packet with a copy of the previous state. If that fails, the packet is discarded. All packets contain some encrypted data, and all packet data is authenticated (see HMAC below). Handshake field Handshake data is sent until a connection is determined to be active. The format of the field is as follows: 0 8 15 ------------------------------- | Client Ident | Client Ident field | | ------------------------------- | Connection Ident | Connection Ident field ------------------------------- | Active |XXXXXXXXXXXXXX| Active field ------------------------------- The Client Ident is used on the receiving side to match incoming handshake packets to the specific tunnel endpoint configured to receive them. This is necessary when the packets arrive from an unexpected address and/or port, usually due to NAT. Because the Handshake data is unencrypted, this can be trivially done, without having to potentially try decryption against many endpoints. This is an important consideration for servers that many have hundreds or even thousands of endpoints. The Connection Ident field is used to specify the Ident that non-handshake packets from the remote endpoint will use to identify the connection they belong to. This ident is simply the index of the tunnel endpoint in the tunnel list. Because the non-handshake Ident field is 15 bits, this limits the maximum number of tunnel endpoints to 32,767. The Ident present in non-handshake packets is useful when a port and/or address change takes place. There are multiple scenarios where this type of behavior can occur; for example a connection where data is not sent for a period of time can have its route expired by a NAT device. When data resumes being sent, it likely will originate from a different port. Another example is a corporate firewall, which may aggressively reallocate ports or even addresses dynamically. Including the Ident in unencrypted form non- handshake packets allows trivial rematching of incoming packet data without needing to decrypt the packet data. The Active field is how ProtoTunnel determines if a connection is active. Initially, it is set to zero. When an endpoint successfully receives data from a remote endpoint, the Active field in the handshake packets it sends is set to one. When an endpoint receives a handshake packet from a remote endpoint with the Active field set to one, the connection is considered to be complete, and handshake information is no longer sent. Packet Headers Packet headers consist of a twelve-bit size field and a four-bit port index. This means the maximum bundled packet size is 4k and the maximum number of port mappings in a tunnel is 16. ProtoTunnel restricts the actual maximum number of port mappings to eight. ProtoTunnel also reserves the last port to use for embedded control data (see ProtoTunnel Control Info below), so this leaves seven user-allocatable ports. It also means that tunnels are required to be created with the same port mappings on each side of the tunnel, otherwise the bundled tunnel packets will not be forwarded to the correct ports. The tunnel demultiplexer iterates through the packet headers until the aggregate size of packet data plus packet headers plus encryption header equals the size of the received packet. When the packet format is encrypted, packet headers are decrypted one at a time until all of the headers are successfully decrypted. If there are any discrepencies (e.g. invalid port mappings or aggregate sizes don't match) the packet is considered to be invalid and is discarded. Encrypted HMAC message digest The HMAC message digest provides message authentication; in the event that packet data is modified on the wire, the HMAC calculation on the receiving side will not match the expected result, which will indicate that the data has been modified, and the packet will be discarded. Any supported HMAC algorithm maybe be chosen, and any amount of truncation is supported up to 1/2 the hash size. The maximum size of the HMAC is 24 bytes. A NULL HMAC may be chosen, in which case no space is consumed in the packet body. It is assumed that both sides are configured with the same HMAC type and size, otherwise a connection is not be possible. Encrypted Packet Data Encrypted packets, if any, immediately follow the final header. All encrypted packet data is decrypted together in one pass. Unencrypted Packet Data Unencrypted packets, if any, immediately follow encrypted packets, or packet headers if there are no encrypted packets. Matching packets to an endpoint When ProtoTunnel is running in a configuration where multiple endpoints are using the same socket, an incoming packet must be matched to a specific tunnel endpoint for it to be properly received. Two examples of this type of scenario are a dedicated server, or a peer-mesh game with more than two clients. Due to NAT behavior we may not always know where a packet will be coming from at any given time. ProtoTunnel matches differently depending on whether the tunnel is active or not. If the tunnel is not yet active, the ClientId field in the handshake data is used to match the packet to the appropriate tunnel. During this phase of handshaking, ProtoTunnel will update the address and port of the remote endpoint based on where the data originates from, assuming the packet can be decrypted and authenticated. When a tunnel is active, ProtoTunnel uses the Ident field to directly route the packet to the proper endpoint. If the source addr and/or port change while a tunnel is active, the updated port will be detected and used to match future packets. \endverbatim \Copyright Copyright (c) 2005-2015 Electronic Arts Inc. \Version 12/02/2005 (jbrookes) First Version */ /********************************************************************************H*/ /*** Include files ****************************************************************/ #include #include "DirtySDK/platform.h" #include "DirtySDK/dirtysock/dirtynet.h" #include "DirtySDK/dirtysock/dirtylib.h" #include "DirtySDK/dirtysock/dirtymem.h" #include "DirtySDK/crypt/cryptarc4.h" #include "DirtySDK/crypt/crypthash.h" #include "DirtySDK/crypt/crypthmac.h" #include "DirtySDK/proto/prototunnel.h" /*** Defines **********************************************************************/ #define PROTOTUNNEL_HMAC_DEBUG (FALSE) #define PROTOTUNNEL_HMAC_MAXSIZE (16) //!< maximum supported HMAC size #define PROTOTUNNEL_HMAC_DEFSIZE (12) //!< default supported HMAC size #define PROTOTUNNEL_PKTHDRSIZE (2) #define PROTOTUNNEL_MAXHDROFFS (sizeof(ProtoTunnelHandshakeT)) #define PROTOTUNNEL_MAXPACKETS (8) #define PROTOTUNNEL_MAXHDRSIZE (PROTOTUNNEL_PKTHDRSIZE * PROTOTUNNEL_MAXPACKETS) #define PROTOTUNNEL_PACKETBUFFER (SOCKET_MAXUDPRECV) #define PROTOTUNNEL_MAXPACKET (PROTOTUNNEL_PACKETBUFFER - PROTOTUNNEL_MAXHDROFFS - PROTOTUNNEL_PKTHDRSIZE - PROTOTUNNEL_HMAC_MAXSIZE) #define PROTOTUNNEL_CRYPTBITS (3) // number of bits added to extend window #define PROTOTUNNEL_CRYPTALGN (1 << PROTOTUNNEL_CRYPTBITS) #define PROTOTUNNEL_CRYPTMASK (PROTOTUNNEL_CRYPTALGN-1) #define PROTOTUNNEL_CRYPTARC4_ITER (12) // improved security by skipping slightly less secure first output data (3k bytes), this data is not transmitted so does not produce overhead on the connection #define PROTOTUNNEL_CONTROL_PORTIDX (PROTOTUNNEL_MAXPORTS-1) #define PROTOTUNNEL_IDENT_HANDSHAKE (0x8000) #define PROTOTUNNEL_IPPROTO (IPPROTO_IP) #define PROTOTUNNEL_MAXKEYS (PROTOTUNNEL_MAXPORTS) // comma delimited set of support versions // IMPORTANT: make sure to add/delete when min/current version is changed #define PROTOTUNNEL_VERSIONS ("1.1") typedef enum ProtoTunnelControlE { PROTOTUNNEL_CTRL_INIT } ProtoTunnelControlE; typedef enum ProtoTunnelPortListFuncE { PROTOTUNNEL_PORTLIST_ADD, PROTOTUNNEL_PORTLIST_DEL, PROTOTUNNEL_PORTLIST_CHK } ProtoTunnelPortListFuncE; // packet processing return codes #define PROTOTUNNEL_PACKETRECVFAIL_VALIDATE (-1) //!< decrypt or validate failed #define PROTOTUNNEL_PACKETRECVFAIL_NOMATCH (-2) //!< could not match packet to a tunnel #define PROTOTUNNEL_PACKETRECVFAIL_VERSION (-3) //!< fatal prototunnel protocol version mismatch #define PROTOTUNNEL_PACKETRECVFAIL_OUTOFORDER (-4) //!< out-of-order packet discard; packet is previous to our current window offset #define PROTOTUNNEL_PACKETRECVFAIL_TRYAGAIN (-5) //!< packet decrypt failed but rematch found new key/offset; try again /*** Macros ***********************************************************************/ #define PROTOTUNNEL_GetIdentFromPacket(__pPacketData) (((__pPacketData[0])<<8) | (__pPacketData)[1]) #define PROTOTUNNEL_GetEncryptionFromPacket(__uTunnelVers, __pPacketData) (((__pPacketData[2])<<8) | (__pPacketData)[3]) #define PROTOTUNNEL_GetStreamOffsetFromPacket(__uTunnelVers, __pPacketData) (PROTOTUNNEL_GetEncryptionFromPacket(__uTunnelVers, __pPacketData) & 0x7fff) /*** Type Definitions *************************************************************/ //! handshake packet format for version 1.1 of the protocol typedef struct ProtoTunnelHandshake_1_1_T { uint8_t aCrypt[2]; //!< stream cipher offset uint8_t aClientIdent[4]; //!< client identifier, used to tag handshake packets for initial matching of packet to tunnel uint8_t aConnIdent[2]; //!< connection identifier, used to tag regular packets for re-matching uint8_t uActive; //!< is connection active? } ProtoTunnelHandshake_1_1_T; //! prototunnel handshake packet format typedef struct ProtoTunnelHandshakeT { uint8_t aIdent[2]; union { ProtoTunnelHandshake_1_1_T V1_1; } Version; } ProtoTunnelHandshakeT; //! handshake control info typedef struct ProtoTunnelControlT { uint8_t uPacketType; //!< PROTOTUNNEL_CTRL_* uint8_t aClientId[4]; //!< source clientId uint8_t aProtocolVers[2]; //!< protocol version uint8_t uActive; //!< is connection active? } ProtoTunnelControlT; typedef struct ProtoTunnelT { ProtoTunnelInfoT Info; //!< mapping info uint32_t uVirtualAddr; //!< virtual address, used for NAT support uint32_t uLocalClientId; //!< tunnel-specific local clientId (can override ProtoTunnelT if non-zero) int16_t iBuffSize; //!< current next open position in packet data buffer int16_t iDataSize; //!< current amount of data queued int8_t iNumPackets; //!< number of packets queued uint8_t uHandshakeSize; //!< size of handshake header, determined by protocol version uint8_t uConnIdent[2]; //!< connection identifier, determined in handshaking (version 1.1+ only) NetCritT PacketCrit; //!< packet buffer critical section uint16_t uSendOffset; //!< stream send offset uint16_t uRecvOffset; //!< stream recv offset uint16_t uRecvOOPOffset; //!< stream recv offset for OOPState uint16_t _pad2; CryptArc4T CryptSendState; //!< crypto state for encrypting data CryptArc4T CryptRecvState; //!< crypto state for decrypting data CryptArc4T CryptRecvOOPState; //!< state for receiving out-of-order packets #if DIRTYCODE_LOGGING uint32_t uLastStatUpdate; //!< last time stat update was printed ProtoTunnelStatT LastSendStat; //!< previous send stat #endif uint32_t uLastTunnelSend; //!< last time data was sent on this tunnel ProtoTunnelStatT SendStat; //!< cumulative send statistics ProtoTunnelStatT RecvStat; //!< cumulative recv statistics uint32_t uLastSendNumBytes; //!< tracking variable for send bytes per second calculation uint32_t uLastSendNumSubpacketBytes; //!< tracking variable for send number of subpacket bytes per second calculation uint32_t uLastRecvNumBytes; //!< tracking variable for receive bytes per second calculation uint32_t uLastRecvNumSubpacketBytes; //!< tracking variable for receive number of subpacket bytes per second calculation char aKeyList[PROTOTUNNEL_MAXKEYS][128]; //!< crypto key(s) char aHmacKey[64]; //!< storage for HMAC key (HMAC IV encrypted by tunnel key) uint8_t uActive; //!< "active" if we've received data from remote peer uint8_t uRefCount; //!< tunnel ref count uint8_t uSendKey; //!< index of key we are using to send with uint8_t uRecvKey; //!< index of key we are using to recv with uint8_t bSendCtrlInfo; //!< whether to send control info or not uint8_t aForceCrypt[PROTOTUNNEL_MAXPACKETS]; //!< force crypt override (per sub-packet) uint8_t aPacketData[PROTOTUNNEL_PACKETBUFFER+PROTOTUNNEL_MAXHDRSIZE+2]; //!< packet aggregator } ProtoTunnelT; struct ProtoTunnelRefT { // module memory group int32_t iMemGroup; //!< module mem group id void *pMemGroupUserData; //!< user data associated with mem group SocketT *pSocket; //!< physical socket tunnel uses SocketT *pServerSocket; //!< server socket (xboxone only) uint16_t aServerRemotePortList[16]; //!< remote ports for server socket uint16_t uTunnelPort; //!< port socket tunnel is bound to uint32_t uLocalClientId; //!< local client identifier uint16_t uVersion; //!< protocol version uint8_t uHmacType; //!< HMAC hash type uint8_t uHmacSize; //!< HMAC size int32_t iMaxTunnels; //!< maximum number of tunnels int32_t iMaxRecv; //!< maximum number of receive calls that may be made in one execution of _ProtoTunnelRecvCallback int32_t iIdleCbRate; //!< rate that socket callback is called by idle thread in milliseconds int32_t iVerbosity; //!< verbosity level uint32_t uVirtualAddr; //!< next free virtual address uint32_t uFlushRate; //!< rate at which packets are flushed by update #if DIRTYCODE_DEBUG int32_t iPacketDrop; //!< drop the next n packets #endif uint32_t uNumRecvCalls; //!< number of receive calls uint32_t uNumPktsRecvd; //!< number of packets received uint32_t uNumSubPktsRecvd; //!< number of sub-packets received uint32_t uNumPktsDiscard; //!< number of out-of-order packet discards ProtoTunnelCallbackT *pCallback; //!< prototunnel event callback void *pUserData; //!< callback user data RawRecvCallbackT *pRawRecvCallback; //!< raw inbound data filtering function void *pRawRecvUserData; //!< callback user data NetCritT TunnelsCritS; //!< "send thread" critical section NetCritT TunnelsCritR; //!< "recv thread" critical section ProtoTunnelT Tunnels[1]; //!< variable-length tunnel array - must be last }; /*** Variables ********************************************************************/ // hmac init vector; borrowed from SHA512 initial hash static const uint8_t _ProtoTunnel_aHmacInitVec[64] = { 0x6a, 0x09, 0xe6, 0x67, 0xf3, 0xbc, 0xc9, 0x08, 0xbb, 0x67, 0xae, 0x85, 0x84, 0xca, 0xa7, 0x3b, 0x3c, 0x6e, 0xf3, 0x72, 0xfe, 0x94, 0xf8, 0x2b, 0xa5, 0x4f, 0xf5, 0x3a, 0x5f, 0x1d, 0x36, 0xf1, 0x51, 0x0e, 0x52, 0x7f, 0xad, 0xe6, 0x82, 0xd1, 0x9b, 0x05, 0x68, 0x8c, 0x2b, 0x3e, 0x6c, 0x1f, 0x1f, 0x83, 0xd9, 0xab, 0xfb, 0x41, 0xbd, 0x6b, 0x5b, 0xe0, 0xcd, 0x19, 0x13, 0x7e, 0x21, 0x79 }; static uint8_t _ProtoTunnel_bBaseAddrUsed[256]; //! used to control behavior for _ProtoTunnelSocketOpen static uint8_t _ProtoTunnel_bRetryRandomOnFailure = TRUE; /*** Private Functions ************************************************************/ int32_t ProtoTunnelValidatePacket(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, uint8_t *pOutputData, const uint8_t *pPacketData, int32_t iPacketSize, const char *pKey); /*F********************************************************************************/ /*! \Function _ProtoTunnelSetVersion \Description Set protocol version for the specified tunnel \Input *pProtoTunnel - module state \Input *pTunnel - tunnel to set version for \Input uTunnelVers - version to set (PROTOTUNNEL_VERSION_*) \Version 03/26/2015 (jbrookes) */ /********************************************************************************F*/ static void _ProtoTunnelSetVersion(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, uint32_t uTunnelVers) { uint32_t uHandshakeSize = 2; // all handshake sizes are a minimum of two bytes // calculate size of header switch (uTunnelVers) { case PROTOTUNNEL_VERSION_1_1: uHandshakeSize += sizeof(ProtoTunnelHandshake_1_1_T); break; default: NetPrintf(("prototunnel: [%p] trying to set unknown version %d.%d; assuming 1.1 format\n", pProtoTunnel, uTunnelVers>>8, uTunnelVers&0xff)); uHandshakeSize += sizeof(ProtoTunnelHandshake_1_1_T); break; } pTunnel->Info.uTunnelVers = uTunnelVers; pTunnel->uHandshakeSize = uHandshakeSize; } /*F********************************************************************************/ /*! \Function _ProtoTunnelGetBaseAddress \Description Assign a free base address to the specified prototunnel instance \Input *pProtoTunnel - module state \Output int32_t - 0 for success, -1 if no address available \Version 08/22/2014 (mclouatre) */ /********************************************************************************F*/ static int32_t _ProtoTunnelGetBaseAddress(ProtoTunnelRefT *pProtoTunnel) { int32_t iRetCode = -1; // default to error int32_t iIndex; // skip index=0 because we don't want to create base addresses with format 0.x.x.x for (iIndex = 1; iIndex < 256; iIndex++) { if (_ProtoTunnel_bBaseAddrUsed[iIndex] == FALSE) { _ProtoTunnel_bBaseAddrUsed[iIndex] = TRUE; pProtoTunnel->uVirtualAddr = iIndex << 24; NetPrintf(("prototunnel: [%p] base virtual address set to %a\n", pProtoTunnel, pProtoTunnel->uVirtualAddr)); iRetCode = 0; // signal success break; } } return(iRetCode); } /*F********************************************************************************/ /*! \Function _ProtoTunnelReleaseBaseAddress \Description Return a free base address to be assigned to a new protoutunnel instance \Input *pProtoTunnel - module state \Version 08/22/2014 (mclouatre) */ /********************************************************************************F*/ static void _ProtoTunnelReleaseBaseAddress(ProtoTunnelRefT *pProtoTunnel) { NetPrintf(("prototunnel: [%p] releasing base virtual address %a\n", pProtoTunnel, (pProtoTunnel->uVirtualAddr&0xFF000000))); _ProtoTunnel_bBaseAddrUsed[pProtoTunnel->uVirtualAddr >> 24] = FALSE; } /*F********************************************************************************/ /*! \Function _ProtoTunnelIndexFromId \Description Return tunnel index of tunnel with specified Id, or -1 if there is no tunnel that matches. \Input *pProtoTunnel - module state \Input uTunnelId - tunnel ident \Output int32_t - tunnel index, or -1 if not found \Version 02/20/2009 (jbrookes) */ /********************************************************************************F*/ static int32_t _ProtoTunnelIndexFromId(ProtoTunnelRefT *pProtoTunnel, uint32_t uTunnelId) { int32_t iTunnel; // find tunnel id for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel += 1) { // found it? if (pProtoTunnel->Tunnels[iTunnel].uVirtualAddr == uTunnelId) { return(iTunnel); } } // not found return(-1); } /*F********************************************************************************/ /*! \Function _ProtoTunnelStreamAdvance \Description Advance stream offset, and advance crypt state if we need to for padding purposes. \Input *pCryptState - pointer to crypt state to advance \Input *pOffset - pointer to current offset (16bit in crypt data units) \Input uOffset - amount to advance by (in bytes) \Version 12/08/2005 (jbrookes) */ /********************************************************************************F*/ static void _ProtoTunnelStreamAdvance(CryptArc4T *pCryptState, uint16_t *pOffset, uint32_t uOffset) { uint32_t uCryptAlign; // convert from number of bytes to number of crypt data units *pOffset += (uint16_t)(uOffset>>PROTOTUNNEL_CRYPTBITS); // if we have 'left-over' bytes, advance the crypt state and add a data unit if ((uCryptAlign = (uOffset & PROTOTUNNEL_CRYPTMASK)) != 0) { uCryptAlign = PROTOTUNNEL_CRYPTALGN - uCryptAlign; CryptArc4Advance(pCryptState, uCryptAlign); *pOffset += 1; } // handle 15 bit wrapping *pOffset &= 0x7fff; } /*F********************************************************************************/ /*! \Function _ProtoTunnelVirtualToPhysical \Description Convert a virtual address into its corresponding physical address. \Input *pProtoTunnel - pointer to module state \Input uVirtualAddr - virtual address \Input *pBuf - [out] storage for sockaddr (optional) \Input iBufSize - size of buffer \Output int32_t - physical address, or zero if no match \Version 03/31/2006 (jbrookes) */ /********************************************************************************F*/ static int32_t _ProtoTunnelVirtualToPhysical(ProtoTunnelRefT *pProtoTunnel, uint32_t uVirtualAddr, char *pBuf, int32_t iBufSize) { ProtoTunnelT *pTunnel; uint32_t uRemoteAddr; int32_t iTunnel; // acquire exclusive access to tunnel list NetCritEnter(&pProtoTunnel->TunnelsCritS); NetCritEnter(&pProtoTunnel->TunnelsCritR); // find tunnel virtual address is bound to for (iTunnel = 0, uRemoteAddr = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++) { pTunnel = &pProtoTunnel->Tunnels[iTunnel]; if (pTunnel->uVirtualAddr == uVirtualAddr) { struct sockaddr SockAddr; if ((pBuf != NULL) && (iBufSize >= (signed)sizeof(SockAddr))) { SockaddrInit(&SockAddr, AF_INET); SockaddrInSetAddr(&SockAddr, pTunnel->Info.uRemoteAddr); SockaddrInSetPort(&SockAddr, pTunnel->Info.uRemotePort); ds_memcpy(pBuf, &SockAddr, sizeof(SockAddr)); } uRemoteAddr = pTunnel->Info.uRemoteAddr; break; } } // release exclusive access to tunnel list NetCritLeave(&pProtoTunnel->TunnelsCritR); NetCritLeave(&pProtoTunnel->TunnelsCritS); // return addr to caller return(uRemoteAddr); } /*F********************************************************************************/ /*! \Function _ProtoTunnelServerPortListFunc \Description Perform a function (add, del, check) on server socket port list \Input *pProtoTunnel - pointer to module state \Input uPort - port value \Input ePortOp - function type (PORTLIST_*) \Output uint32_t - true=success, else false \Version 10/04/2017 (jbrookes) */ /********************************************************************************F*/ static uint32_t _ProtoTunnelServerPortListFunc(ProtoTunnelRefT *pProtoTunnel, uint16_t uPort, ProtoTunnelPortListFuncE ePortOp) { const int32_t iNumPorts = sizeof(pProtoTunnel->aServerRemotePortList) / sizeof(pProtoTunnel->aServerRemotePortList[0]); uint16_t uCheckVal = (ePortOp != PROTOTUNNEL_PORTLIST_ADD) ? uPort : 0; uint32_t bSuccess = FALSE; int32_t iPortIdx; // find entry for (iPortIdx = 0; iPortIdx < iNumPorts; iPortIdx += 1) { if (pProtoTunnel->aServerRemotePortList[iPortIdx] == uCheckVal) { break; } } // operate on entry if (iPortIdx < iNumPorts) { switch (ePortOp) { case PROTOTUNNEL_PORTLIST_DEL: uPort = 0; case PROTOTUNNEL_PORTLIST_ADD: pProtoTunnel->aServerRemotePortList[iPortIdx] = uPort; case PROTOTUNNEL_PORTLIST_CHK: bSuccess = TRUE; default: break; } } // return result to caller return(bSuccess); } /*F********************************************************************************/ /*! \Function _ProtoTunnelSocketSendto \Description Convert a virtual address into its corresponding physical address. \Input *pProtoTunnel - pointer to module state \Input *pBuf - data to send \Input iLen - length of data to send \Input iFlags - flags \Input *pTo - address to send to \Input iToLen - length of address \Output int32_t - SocketSendto() result, or SOCKERR_INVALID if no socket \Version 10/01/2013 (jbrookes) */ /********************************************************************************F*/ static int32_t _ProtoTunnelSocketSendto(ProtoTunnelRefT *pProtoTunnel, const char *pBuf, int32_t iLen, int32_t iFlags, const struct sockaddr *pTo, int32_t iToLen) { SocketT *pSocket = _ProtoTunnelServerPortListFunc(pProtoTunnel, SockaddrInGetPort(pTo), PROTOTUNNEL_PORTLIST_CHK) ? pProtoTunnel->pServerSocket : pProtoTunnel->pSocket; if (pSocket != NULL) { return(SocketSendto(pSocket, pBuf, iLen, iFlags, pTo, iToLen)); } else { return(SOCKERR_INVALID); } } /*F********************************************************************************/ /*! \Function _ProtoTunnelBufferCollect \Description Collect buffered data in final packet form. \Input *pProtoTunnel - pointer to module state \Input *pTunnel - tunnel to send data on \Input iHeadSize - size of packet headers \Input *pPacketData - [out] buffer for finalized packet \Input **ppHeaderDst - pointer to current header output in finalized packet \Input **ppPacketDst - pointer to current packet data output in finalized packet \Input uPortFlag - flag indicating whether we are collecting encrypted or unencrypted packet data \Input *pLimit - end of buffer, used for debug overflow check \Output int32_t - size of packet data added to output buffer \Version 12/08/2005 (jbrookes) */ /********************************************************************************F*/ static int32_t _ProtoTunnelBufferCollect(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, int32_t iHeadSize, uint8_t *pPacketData, uint8_t **ppHeaderDst, uint8_t **ppPacketDst, uint32_t uPortFlag, uint8_t *pLimit) { int32_t iDataSize, iPacket, iPacketSize; uint8_t *pHeaderSrc, *pPacketSrc; uint32_t bEnabled; // point to packet headers and data pHeaderSrc = pTunnel->aPacketData + PROTOTUNNEL_MAXHDROFFS; pPacketSrc = pTunnel->aPacketData + PROTOTUNNEL_MAXHDRSIZE + PROTOTUNNEL_MAXHDROFFS; iDataSize = 0; // collect packets for (iPacket = 0; iPacket < pTunnel->iNumPackets; iPacket++) { // get packet size iPacketSize = (pHeaderSrc[0] << 4) | (pHeaderSrc[1] >> 4); // if packet encryption matches that specified by the caller, copy it and its header bEnabled = (((pTunnel->Info.aPortFlags[pHeaderSrc[1] & 0xf] & (unsigned)PROTOTUNNEL_PORTFLAG_ENCRYPTED) == uPortFlag)); if (pTunnel->aForceCrypt[iPacket] != 0) { bEnabled = !bEnabled; } if (bEnabled) { // make sure we can fit the packet; this shouldn't happen, because we only buffer packets we can fit in ProtoTunnelBufferData() if ((((*ppPacketDst) + iPacketSize) - pLimit) > 0) { NetPrintf(("prototunnel: [%p][%04d] collect buffer overflow by %d bytes; packet of size %d will be discarded\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), (((*ppPacketDst) + iPacketSize) - pLimit), iPacketSize)); break; } // copy packet data and add it to packet buffer ds_memcpy(*ppPacketDst, pPacketSrc, iPacketSize); (*ppHeaderDst)[0] = pHeaderSrc[0]; (*ppHeaderDst)[1] = pHeaderSrc[1]; iDataSize += iPacketSize; *ppPacketDst += iPacketSize; *ppHeaderDst += PROTOTUNNEL_PKTHDRSIZE; } // increment to next packet header and packet data pHeaderSrc += PROTOTUNNEL_PKTHDRSIZE; pPacketSrc += iPacketSize; } // return size to caller return(iDataSize); } /*F********************************************************************************/ /*! \Function _ProtoTunnelBufferSend \Description Send buffered data \Input *pProtoTunnel - pointer to module state \Input *pTunnel - tunnel to send data on \Input uCurTick - current tick count \Version 12/02/2005 (jbrookes) */ /********************************************************************************F*/ static void _ProtoTunnelBufferSend(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, uint32_t uCurTick) { uint8_t aPacketData[PROTOTUNNEL_PACKETBUFFER]; int32_t iCryptSize, iDataSize, iHeadSize, iHshkSize, iResult; uint8_t *pHeaderDst, *pPacketDst, *pCrypt; uint8_t *pHmac; uint16_t uSendHeader = pTunnel->uSendOffset | 0x8000; // add GENERIC bit so we know the protocol type struct sockaddr SendAddr; #if DIRTYCODE_LOGGING int32_t iNumPackets; #endif // get sole access to packet buffer NetCritEnter(&pTunnel->PacketCrit); // no data to send or socket to send it on? if ((pProtoTunnel->pSocket == NULL) || (pTunnel->iDataSize <= 0)) { NetCritLeave(&pTunnel->PacketCrit); return; } // create send addr SockaddrInit(&SendAddr, AF_INET); SockaddrInSetAddr(&SendAddr, pTunnel->Info.uRemoteAddr); SockaddrInSetPort(&SendAddr, pTunnel->Info.uRemotePort); // format packet in local buffer ds_memclr(aPacketData, sizeof(aPacketData)); // get handshake header size and total header size (including sub-packet headers) iHshkSize = (pTunnel->bSendCtrlInfo) ? pTunnel->uHandshakeSize : 4; iHeadSize = (pTunnel->iNumPackets * PROTOTUNNEL_PKTHDRSIZE) + iHshkSize; // set up for copy pHeaderDst = aPacketData + iHshkSize; pPacketDst = aPacketData + iHeadSize; // reserve space for the HMAC; we put it here so it will be encrypted and easy to locate pHmac = aPacketData + iHeadSize; iCryptSize = pProtoTunnel->uHmacSize; ds_memclr(pHmac, iCryptSize); pPacketDst += iCryptSize; // collect encrypted packets iCryptSize += _ProtoTunnelBufferCollect(pProtoTunnel, pTunnel, iHeadSize, aPacketData, &pHeaderDst, &pPacketDst, PROTOTUNNEL_PORTFLAG_ENCRYPTED, aPacketData + PROTOTUNNEL_PACKETBUFFER); // collect unencrypted packets iDataSize = _ProtoTunnelBufferCollect(pProtoTunnel, pTunnel, iHeadSize, aPacketData, &pHeaderDst, &pPacketDst, 0, aPacketData + PROTOTUNNEL_PACKETBUFFER) + iCryptSize; // calculate total encrypted size (encrypted headers + encrypted data, but skip the first two bytes) iCryptSize += iHeadSize - iHshkSize; // total size of packet iDataSize += iHeadSize; // write protocol header if (pTunnel->bSendCtrlInfo) { ProtoTunnelHandshakeT Handshake; uint32_t uLocalClientId = (pTunnel->uLocalClientId != 0) ? pTunnel->uLocalClientId : pProtoTunnel->uLocalClientId; uint16_t uTunnelIndex = pTunnel - pProtoTunnel->Tunnels; uint16_t uIdent = pTunnel->Info.uTunnelVers | PROTOTUNNEL_IDENT_HANDSHAKE; Handshake.aIdent[0] = (uint8_t)(uIdent >> 8); Handshake.aIdent[1] = (uint8_t)(uIdent); Handshake.Version.V1_1.aCrypt[0] = 0; Handshake.Version.V1_1.aCrypt[1] = 0; Handshake.Version.V1_1.aClientIdent[0] = (uint8_t)(uLocalClientId >> 24); Handshake.Version.V1_1.aClientIdent[1] = (uint8_t)(uLocalClientId >> 16); Handshake.Version.V1_1.aClientIdent[2] = (uint8_t)(uLocalClientId >> 8); Handshake.Version.V1_1.aClientIdent[3] = (uint8_t)(uLocalClientId); Handshake.Version.V1_1.aConnIdent[0] = (uint8_t)(uTunnelIndex >> 8); Handshake.Version.V1_1.aConnIdent[1] = (uint8_t)(uTunnelIndex); Handshake.Version.V1_1.uActive = pTunnel->uActive; ds_memcpy(aPacketData, &Handshake, sizeof(Handshake)); pCrypt = ((ProtoTunnelHandshakeT *)aPacketData)->Version.V1_1.aCrypt; } else { aPacketData[0] = pTunnel->uConnIdent[0]; aPacketData[1] = pTunnel->uConnIdent[1]; pCrypt = aPacketData+2; } // set tunnel encryption header pCrypt[0] = (uint8_t)(uSendHeader >> 8); pCrypt[1] = (uint8_t)(uSendHeader >> 0); // calculate the hmac and write it into the space reserved for it if (pProtoTunnel->uHmacType != CRYPTHASH_NULL) { CryptHmacCalc(pHmac, pProtoTunnel->uHmacSize, aPacketData, iDataSize, (uint8_t *)pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), (CryptHashTypeE)pProtoTunnel->uHmacType); #if PROTOTUNNEL_HMAC_DEBUG NetPrintMem(aPacketData, iDataSize, "send-hmac-data"); NetPrintMem(pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), "send-hmac-key"); NetPrintMem(pHmac, pProtoTunnel->uHmacSize, "send-hmac"); #endif } // verbose-only writing of unencrypted payload if (pProtoTunnel->iVerbosity > 3) { NetPrintMem(aPacketData, (iDataSize < 64) ? iDataSize : 64, "prototunnel-send-nocrypt"); } // encrypt payload and advance stream if necessary CryptArc4Apply(&pTunnel->CryptSendState, aPacketData + iHshkSize, iCryptSize); _ProtoTunnelStreamAdvance(&pTunnel->CryptSendState, &pTunnel->uSendOffset, iCryptSize); // accumulate stats pTunnel->SendStat.uLastPacketTime = uCurTick; pTunnel->SendStat.uNumBytes += iDataSize; pTunnel->SendStat.uNumSubpacketBytes += iDataSize - iHeadSize - pProtoTunnel->uHmacSize; pTunnel->SendStat.uNumPackets += 1; pTunnel->SendStat.uNumSubpackets += pTunnel->iNumPackets + pTunnel->bSendCtrlInfo; #if DIRTYCODE_LOGGING iNumPackets = pTunnel->iNumPackets + pTunnel->bSendCtrlInfo; #endif // mark data as sent pTunnel->iBuffSize = 0; pTunnel->iDataSize = 0; pTunnel->iNumPackets = 0; ds_memclr(pTunnel->aForceCrypt, sizeof(pTunnel->aForceCrypt)); // release packet buffer critical section NetCritLeave(&pTunnel->PacketCrit); #if DIRTYCODE_LOGGING if (NetTickDiff(uCurTick, pTunnel->uLastStatUpdate) > 5000) { ProtoTunnelStatT DiffStat; DiffStat.uNumBytes = pTunnel->SendStat.uNumBytes - pTunnel->LastSendStat.uNumBytes; DiffStat.uNumSubpacketBytes = pTunnel->SendStat.uNumSubpacketBytes - pTunnel->LastSendStat.uNumSubpacketBytes; DiffStat.uNumPackets = pTunnel->SendStat.uNumPackets - pTunnel->LastSendStat.uNumPackets; DiffStat.uNumSubpackets = pTunnel->SendStat.uNumSubpackets - pTunnel->LastSendStat.uNumSubpackets; ds_memcpy_s(&pTunnel->LastSendStat, sizeof(pTunnel->LastSendStat), &pTunnel->SendStat, sizeof(pTunnel->SendStat)); NetPrintfVerbose((pProtoTunnel->iVerbosity, 2, "prototunnel: [%p][%04d] pkts: %d eff: %.2f sent: %d eff: %.2f\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), DiffStat.uNumPackets, (float)DiffStat.uNumSubpackets / (float)DiffStat.uNumPackets, DiffStat.uNumBytes, (float)DiffStat.uNumSubpacketBytes / (float)DiffStat.uNumBytes)); pTunnel->uLastStatUpdate = uCurTick; } if (pProtoTunnel->iVerbosity > 3) { NetPrintMem(aPacketData, (iDataSize < 64) ? iDataSize : 64, "prototunnel-send"); } #endif // send the data if ((iResult = _ProtoTunnelSocketSendto(pProtoTunnel, (char *)aPacketData, iDataSize, 0, &SendAddr, sizeof(SendAddr))) < 0) { NetPrintf(("prototunnel: [%p][%04d] send - error %d sending buffered packet to %a:%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iResult, SockaddrInGetAddr(&SendAddr), SockaddrInGetPort(&SendAddr))); } else { NetPrintfVerbose((pProtoTunnel->iVerbosity, 2, "prototunnel: [%p][%04d] send - sent %d bytes [%d packets] to %a:%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iDataSize, iNumPackets, SockaddrInGetAddr(&SendAddr), SockaddrInGetPort(&SendAddr))); } pTunnel->uLastTunnelSend = NetTick(); } /*F********************************************************************************/ /*! \Function _ProtoTunnelBufferData \Description Buffer a send \Input *pProtoTunnel - pointer to module state \Input *pTunnel - tunnel to buffer data on \Input *pData - data to be sent \Input uSize - size of data to send \Input uPortIdx - index of port in tunnel map \Input bForceCrypt - TRUE to force encryption of packet, else FALSE \Output int32_t - amount of data buffered, or socket error code (SOCKERR_XXX) \Version 12/02/2005 (jbrookes) */ /********************************************************************************F*/ static int32_t _ProtoTunnelBufferData(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, const uint8_t *pData, uint32_t uSize, uint32_t uPortIdx, uint8_t bForceCrypt) { uint32_t uCurTick = NetTick(); uint8_t *pBuffer; // make sure data is within size limits if ((uSize == 0) || (uSize > PROTOTUNNEL_MAXPACKET)) { NetPrintf(("prototunnel: [%p][%04d] packet of size %d will not be tunneled (max size = %d)\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uSize, PROTOTUNNEL_MAXPACKET)); NetPrintMem(pData, 64, "prototunnel-nobuf"); return(SOCKERR_NORSRC); } // acquire packet buffer critical section NetCritEnter(&pTunnel->PacketCrit); // if buffer is full, or we've already buffered max packets, flush buffer if ((((unsigned)pTunnel->iDataSize + uSize + PROTOTUNNEL_PKTHDRSIZE) > PROTOTUNNEL_PACKETBUFFER) || (pTunnel->iNumPackets == PROTOTUNNEL_MAXPACKETS)) { // flush current packet _ProtoTunnelBufferSend(pProtoTunnel, pTunnel, uCurTick); } // if buffer is empty, reserve space for max header data (1.1 handshake + max sub-packet headers) if (pTunnel->iBuffSize == 0) { pTunnel->iBuffSize = PROTOTUNNEL_MAXHDROFFS + PROTOTUNNEL_MAXHDRSIZE; pTunnel->iDataSize = PROTOTUNNEL_MAXHDROFFS + pProtoTunnel->uHmacSize; ds_memclr(pTunnel->aPacketData, pTunnel->iBuffSize); } // store packet header pBuffer = pTunnel->aPacketData + (pTunnel->iNumPackets * PROTOTUNNEL_PKTHDRSIZE) + PROTOTUNNEL_MAXHDROFFS; pBuffer[0] = (uint8_t)(uSize >> 4); pBuffer[1] = (uint8_t)((uSize << 4) | uPortIdx); // store packet data pBuffer = pTunnel->aPacketData + pTunnel->iBuffSize; ds_memcpy(pBuffer, pData, uSize); pTunnel->iBuffSize += uSize; pTunnel->iDataSize += uSize + PROTOTUNNEL_PKTHDRSIZE; pTunnel->aForceCrypt[(uint32_t)pTunnel->iNumPackets] = bForceCrypt; // update overall count pTunnel->iNumPackets += 1; if (pTunnel->Info.aPortFlags[uPortIdx] & PROTOTUNNEL_PORTFLAG_AUTOFLUSH) { _ProtoTunnelBufferSend(pProtoTunnel, pTunnel, uCurTick); } // release packet buffer critical section NetCritLeave(&pTunnel->PacketCrit); // return buffered size to caller return(uSize); } /*F********************************************************************************/ /*! \Function _ProtoTunnelSendCallback \Description Global send callback handler. \Input *pSocket - socket to send on \Input iType - socket type \Input *pData - data to be sent \Input iDataSize - size of data to send \Input *pTo - destination address \Input *pCallref - prototunnel ref \Output int32_t - 0 = send not handled; >0 = send successfully handled (bytes sent); <0 = send handled but failed (SOCKERR_XXX) \Version 12/02/2005 (jbrookes) */ /********************************************************************************F*/ static int32_t _ProtoTunnelSendCallback(SocketT *pSocket, int32_t iType, const uint8_t *pData, int32_t iDataSize, const struct sockaddr *pTo, void *pCallref) { ProtoTunnelRefT *pProtoTunnel = (ProtoTunnelRefT *)pCallref; ProtoTunnelT *pTunnel = NULL; uint32_t uAddr, uPort; int32_t iPort = 0, iTunnel; int32_t iResult = 0; // default to "not handled" uint32_t bFound, bForceCrypt=0; #if DIRTYCODE_LOGGING uint32_t bFoundAddr = FALSE; #endif struct sockaddr SockAddr; // only handle dgram sockets, and don't handle our own socket if ((iType != SOCK_DGRAM) || (pProtoTunnel->pSocket == pSocket) || (pProtoTunnel->pServerSocket == pSocket)) { return(0); // not handled } // if a destination address is not specified, get it from the socket if (pTo == NULL) { SocketInfo(pSocket, 'peer', 0, &SockAddr, sizeof(SockAddr)); pTo = &SockAddr; } // get destination address and port uAddr = SockaddrInGetAddr(pTo); uPort = SockaddrInGetPort(pTo); // ignore sends to an invalid destination if ((uAddr == 0) || (uPort == 0)) { return(0); // not handled } // get exclusive access to tunnel list NetCritEnter(&pProtoTunnel->TunnelsCritS); // see if we have a match for (iTunnel = 0, bFound = FALSE; (iTunnel < pProtoTunnel->iMaxTunnels) && (bFound == FALSE); iTunnel++) { pTunnel = &pProtoTunnel->Tunnels[iTunnel]; // does virtual address match? if (pTunnel->uVirtualAddr != uAddr) { continue; } #if DIRTYCODE_LOGGING // remember if we have a tunnel to this address bFoundAddr = TRUE; #endif // see if we have a port match or are at the end of our port list for (iPort = 0; iPort < PROTOTUNNEL_MAXPORTS; iPort++) { uint32_t uPort2 = pTunnel->Info.aRemotePortList[iPort]; if (uPort2 == uPort) { bFound = TRUE; break; } } } // found a match? if (bFound == TRUE) { // if we have a match, queue data iResult = _ProtoTunnelBufferData(pProtoTunnel, pTunnel, pData, iDataSize, iPort, bForceCrypt); } else { NetPrintfVerbose((pProtoTunnel->iVerbosity, 1, "prototunnel: [%p] send - no match for data sent to %a:%d (%s mismatch)\n", pProtoTunnel, uAddr, uPort, bFoundAddr ? "port" : "addr")); } // release exclusive access to tunnel list and return result NetCritLeave(&pProtoTunnel->TunnelsCritS); return(iResult); } /*F********************************************************************************/ /*! \Function _ProtoTunnelDecryptAndValidatePacket2 \Description Decrypt the tunnel packet \Input *pProtoTunnel - pointer to module state \Input *pTunnel - tunnel \Input *pHeadOffset - [out] offset of sub-packet header data \Input *pPacketData - pointer to tunnel packet \Input iRecvLen - size of tunnel packet \Input bUpdateState - TRUE to update crypto state, else FALSE \Input bCurrentState - TRUE when decrypting against current state, FALSE against oop state \Output int32_t - PROTOTUNNEL_PACKETRECVFAIL_* on error, else number of subpackets decoded \Version 12/07/2005 (jbrookes) */ /********************************************************************************F*/ static int32_t _ProtoTunnelDecryptAndValidatePacket2(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, uint32_t *pHeadOffset, uint8_t *pPacketData, int32_t iRecvLen, uint8_t bUpdateState, uint8_t bCurrentState) { int32_t iEncryptedSize, iNumPackets, iPacketOffset, iPacketSize, iHdrCopySize; uint8_t aPacketHeader[PROTOTUNNEL_MAXHDRSIZE+PROTOTUNNEL_MAXHDROFFS]; uint8_t *pPacketStart = pPacketData; uint32_t uRecvOffset, uRecvOffsetState; CryptArc4T CryptRecvState, *pCryptRecvState; uint16_t *pRecvOffsetState; uint16_t uIdent, uVersion; uint8_t bSynced = FALSE; uint32_t uHeadOffset; // make a copy of packet header before doing anything else, in case we need to restore it after a decrypt failure iHdrCopySize = DS_MIN(sizeof(aPacketHeader), (unsigned)iRecvLen); ds_memcpy(aPacketHeader, pPacketData, iHdrCopySize); // get reference to crypt state based on whether we are decoding the current state or oop state pCryptRecvState = (bCurrentState) ? &pTunnel->CryptRecvState : &pTunnel->CryptRecvOOPState; pRecvOffsetState = (bCurrentState) ? &pTunnel->uRecvOffset : &pTunnel->uRecvOOPOffset; // get stream offset from packet header - this tells us where we need to be to be able to decrypt the packet uRecvOffset = PROTOTUNNEL_GetStreamOffsetFromPacket(pTunnel->Info.uTunnelVers, pPacketData); // work on a local copy of the crypt state and stream offset, in case we need to throw this packet out ds_memcpy_s(&CryptRecvState, sizeof(CryptRecvState), pCryptRecvState, sizeof(*pCryptRecvState)); uRecvOffsetState = *pRecvOffsetState; // lost data? sync the cipher if (uRecvOffset != uRecvOffsetState) { // calc how many data units we've missed int16_t iRecvOffset = (signed)uRecvOffset; int16_t iTunlOffset = (signed)uRecvOffsetState; // calc 15bit offset int16_t iRecvDiff = (iRecvOffset - iTunlOffset) & 0x7fff; /* sign extend our 15bit offset - we do it this way because the simpler way of shifting left and back again right relies on the right shift sign-extending the value, however in the C standard it is implementation-dependent whether the shift is treated as signed or unsigned */ iRecvDiff |= (iRecvDiff & 16384) << 1; /* check to see if packet is previous to our current window offset; we call this an out-of-order packet and handle it differently from a skipped packet, which is ahead of our current window offset. this distinction is made because it is trivial to advance the state, but it cannot be rolled back */ if (iRecvDiff < 0) { NetPrintf(("prototunnel: [%p][%04d] received out of order packet (off=%d)\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iRecvDiff)); return(PROTOTUNNEL_PACKETRECVFAIL_OUTOFORDER); } // advance the cipher to resync NetPrintf(("prototunnel: [%p][%04d] stream skip %d bytes\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iRecvDiff<Tunnels), uVersion>>8, uVersion&0xff)); if (uVersion != pTunnel->Info.uTunnelVers) { NetPrintf(("prototunnel: [%p][%04d] version mismatch; received %d.%d but expected %d.%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uVersion>>8, uVersion&0xff, pTunnel->Info.uTunnelVers>>8, pTunnel->Info.uTunnelVers&0xff)); if ((uVersion > pTunnel->Info.uTunnelVers) || (uVersion < PROTOTUNNEL_VERSION_1_1)) { NetPrintf(("prototunnel: [%p][%04d] can't connect with protocol version %d.%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uVersion>>8, uVersion&0xff)); return(PROTOTUNNEL_PACKETRECVFAIL_VERSION); } } ds_memcpy(&Handshake, pPacketData, sizeof(Handshake)); pTunnel->uConnIdent[0] = Handshake.Version.V1_1.aConnIdent[0]; pTunnel->uConnIdent[1] = Handshake.Version.V1_1.aConnIdent[1]; NetPrintf(("prototunnel: [%p][%04d] setting connection ident to %d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), (pTunnel->uConnIdent[0] << 8) | pTunnel->uConnIdent[1])); uHeadOffset = pTunnel->uHandshakeSize; } else { uVersion = pTunnel->Info.uTunnelVers; uHeadOffset = 4; } // skip header pPacketData += uHeadOffset; iPacketOffset = uHeadOffset; // save sub-packet header offset for later use *pHeadOffset = uHeadOffset; /* subtract out hmac size while we decrypt packet headers and check overall packet size, as hmac is not included in that calculation */ iRecvLen -= pProtoTunnel->uHmacSize; // iterate through packet headers for (iNumPackets = 0, iEncryptedSize = 0; iPacketOffset < iRecvLen; iNumPackets++) { // decrypt the packet header CryptArc4Apply(&CryptRecvState, pPacketData, PROTOTUNNEL_PKTHDRSIZE); uRecvOffset += PROTOTUNNEL_PKTHDRSIZE; // extract size and port info iPacketSize = (pPacketData[0] << 4) | (pPacketData[1] >> 4); iPacketOffset += iPacketSize + PROTOTUNNEL_PKTHDRSIZE; if (pTunnel->Info.aPortFlags[pPacketData[1] & 0xf] & PROTOTUNNEL_PORTFLAG_ENCRYPTED) { iEncryptedSize += iPacketSize; } // increment to next packet header pPacketData += PROTOTUNNEL_PKTHDRSIZE; } // make sure size matches if (iPacketOffset != iRecvLen) { NetPrintf(("prototunnel: [%p][%04d] recv - mismatched size in received packet (%d received, %d decoded)\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iRecvLen+pProtoTunnel->uHmacSize, iPacketOffset)); // restore original packet header ds_memcpy(pPacketStart, aPacketHeader, iHdrCopySize); return(PROTOTUNNEL_PACKETRECVFAIL_VALIDATE); } // restore hmac to packet size, and add hmac to size of data to be decrypted iRecvLen += pProtoTunnel->uHmacSize; iEncryptedSize += pProtoTunnel->uHmacSize; // decrypt encrypted packet data if (iEncryptedSize > 0) { CryptArc4Apply(&CryptRecvState, pPacketData, iEncryptedSize); uRecvOffset += iEncryptedSize; } // calculate HMAC and compare with what we received if (pProtoTunnel->uHmacType != CRYPTHASH_NULL) { uint8_t aPackHmac[PROTOTUNNEL_HMAC_MAXSIZE], aCalcHmac[PROTOTUNNEL_HMAC_MAXSIZE]; // make a copy of HMAC ds_memcpy_s(aPackHmac, sizeof(aPackHmac), pPacketData, pProtoTunnel->uHmacSize); // clear HMAC payload in packet to zero so we can calculate the HMAC ourselves ds_memclr(pPacketData, pProtoTunnel->uHmacSize); // calculate HMAC CryptHmacCalc(aCalcHmac, pProtoTunnel->uHmacSize, pPacketStart, iRecvLen, (uint8_t *)pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), (CryptHashTypeE)pProtoTunnel->uHmacType); // validate if (memcmp(aPackHmac, aCalcHmac, pProtoTunnel->uHmacSize)) { NetPrintf(("prototunnel: [%p][%04d] bad HMAC!\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); #if PROTOTUNNEL_HMAC_DEBUG NetPrintMem(pPacketData, pProtoTunnel->uHmacSize, "recv-hmac"); NetPrintMem(pPacketStart, iRecvLen, "recv-hmac-data"); NetPrintMem(pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), "recv-hmac-key"); #endif return(PROTOTUNNEL_PACKETRECVFAIL_VALIDATE); } else { #if PROTOTUNNEL_HMAC_DEBUG NetPrintf(("prototunnel: [%p][%04d] recv - hmac validated\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); #endif } } // update crypt state and stream offset with working temp copies if (bUpdateState) { // if we had to sync the cipher, save current state to oop state before advancing for possible future out-of-order packet decoding if (bCurrentState && bSynced) { NetPrintf(("prototunnel: [%p][%04d] saving out-of-order state\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); ds_memcpy_s(&pTunnel->CryptRecvOOPState, sizeof(pTunnel->CryptRecvOOPState), pCryptRecvState, sizeof(*pCryptRecvState)); pTunnel->uRecvOOPOffset = *pRecvOffsetState; } // now save advanced state ds_memcpy_s(pCryptRecvState, sizeof(*pCryptRecvState), &CryptRecvState, sizeof(CryptRecvState)); *pRecvOffsetState = (uint16_t)uRecvOffsetState; // advance stream by received encrypted packet data size, plus padding if necessary _ProtoTunnelStreamAdvance(pCryptRecvState, pRecvOffsetState, uRecvOffset); } // if we downgraded protocol version, update here if (pTunnel->Info.uTunnelVers != uVersion) { NetPrintf(("prototunnel: [%p][%04d] downgrading to version %d.%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uVersion>>8, uVersion&0xff)); _ProtoTunnelSetVersion(pProtoTunnel, pTunnel, uVersion); } // return number of packets decoded return(iNumPackets); } /*F********************************************************************************/ /*! \Function _ProtoTunnelDecryptAndValidatePacket \Description Decrypt the tunnel packet, with limited out-of-order packet recovery \Input *pProtoTunnel - pointer to module state \Input *pTunnel - tunnel \Input *pHeadOffset - [out] offset of sub-packet header data \Input *pPacketData - pointer to tunnel packet \Input iRecvLen - size of tunnel packet \Input bUpdateState - TRUE to update crypt state, else FALSE \Output int32_t - PROTOTUNNEL_PACKETRECVFAIL_* on error, else number of subpackets decoded \Notes If an out-of-order packet is detected against the current tunnel decrypt state, a single attempt is made to recover the packet using the out-of-order decrypt state. \Version 02/21/2014 (jbrookes) */ /********************************************************************************F*/ static int32_t _ProtoTunnelDecryptAndValidatePacket(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, uint32_t *pHeadOffset, uint8_t *pPacketData, int32_t iRecvLen, uint8_t bUpdateState) { int32_t iResult; // try decrypting packet with current state if ((iResult = _ProtoTunnelDecryptAndValidatePacket2(pProtoTunnel, pTunnel, pHeadOffset, pPacketData, iRecvLen, bUpdateState, TRUE)) == PROTOTUNNEL_PACKETRECVFAIL_OUTOFORDER) { // received out-of-order packet prior to our current state; try again with out-of-order packet state if ((iResult = _ProtoTunnelDecryptAndValidatePacket2(pProtoTunnel, pTunnel, pHeadOffset, pPacketData, iRecvLen, bUpdateState, FALSE)) > 0) { NetPrintf(("prototunnel: [%p][%04d] out of order packet recovered by oop state\n", pProtoTunnel, pTunnel - pProtoTunnel->Tunnels)); } } // return result return(iResult); } /*F********************************************************************************/ /*! \Function _ProtoTunnelGetControlInfo \Description Get control info from handshake \Input *pTunnel - tunnel packet was received on \Input *pControlInfo - [out] control info from packet data \Input *pPacketData - source data \Output uint32_t - non-zero if control info was found, else zero \Version 03/26/2015 (jbrookes) */ /********************************************************************************F*/ static uint32_t _ProtoTunnelGetControlInfo(ProtoTunnelT *pTunnel, ProtoTunnelControlT *pControlInfo, const uint8_t *pPacketData) { uint32_t bFound = FALSE; // get ident uint16_t uIdent = PROTOTUNNEL_GetIdentFromPacket(pPacketData); // get control info, if present if (uIdent & PROTOTUNNEL_IDENT_HANDSHAKE) { ProtoTunnelHandshakeT *pHandshake = (ProtoTunnelHandshakeT *)pPacketData; pControlInfo->uPacketType = PROTOTUNNEL_CTRL_INIT; pControlInfo->aProtocolVers[0] = pHandshake->aIdent[0] & ~0x80; pControlInfo->aProtocolVers[1] = pHandshake->aIdent[1]; pControlInfo->aClientId[0] = pHandshake->Version.V1_1.aClientIdent[0]; pControlInfo->aClientId[1] = pHandshake->Version.V1_1.aClientIdent[1]; pControlInfo->aClientId[2] = pHandshake->Version.V1_1.aClientIdent[2]; pControlInfo->aClientId[3] = pHandshake->Version.V1_1.aClientIdent[3]; pControlInfo->uActive = pHandshake->Version.V1_1.uActive; bFound = TRUE; } else { ds_memclr(pControlInfo, sizeof(*pControlInfo)); } return(bFound); } /*F********************************************************************************/ /*! \Function _ProtoTunnelCryptSetup \Description Setup crypto state for key index iKey \Input *pTunnel - tunnel to setup crypto state for \Input iKey - index of key to use to setup state \Input bForceSetup - force setup \Version 02/24/2016 (jbrookes) */ /********************************************************************************F*/ static void _ProtoTunnelCryptSetup(ProtoTunnelT *pTunnel, int32_t iKey, uint8_t bForceSetup) { uint8_t *pKey = (uint8_t *)pTunnel->aKeyList[iKey]; int32_t iKeyLen = (int32_t)strlen((char *)pKey); // only setup if we haven't already, or if forced if ((pTunnel->uSendKey == (uint8_t)iKey) && (bForceSetup != TRUE)) { return; } // initialize HMAC key (shared for both send and recv) by running IV through RC4 initialized with tunnel key CryptArc4Init(&pTunnel->CryptSendState, pKey, iKeyLen, PROTOTUNNEL_CRYPTARC4_ITER); ds_memcpy_s(pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), _ProtoTunnel_aHmacInitVec, sizeof(_ProtoTunnel_aHmacInitVec)); CryptArc4Apply(&pTunnel->CryptSendState, (uint8_t *)pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey)); // initialize send/recv crypto state CryptArc4Init(&pTunnel->CryptSendState, pKey, iKeyLen, PROTOTUNNEL_CRYPTARC4_ITER); CryptArc4Init(&pTunnel->CryptRecvState, pKey, iKeyLen, PROTOTUNNEL_CRYPTARC4_ITER); pTunnel->uSendKey = pTunnel->uRecvKey = (uint8_t)iKey; pTunnel->uSendOffset = pTunnel->uRecvOffset = 0; } /*F********************************************************************************/ /*! \Function _ProtoTunnelMatchTunnel \Description Attempt to decrypt incoming data against a tunnel, and check for clientId match \Input *pProtoTunnel - module state \Input *pTunnel - tunnel to check data against \Input *pPacketData - source data \Input iPacketSize - size of source data \Output int32_t - FALSE=no match, TRUE=match \Version 06/26/2006 (jbrookes) */ /********************************************************************************F*/ static int32_t _ProtoTunnelMatchTunnel(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, const uint8_t *pPacketData, int32_t iPacketSize) { uint8_t aPacketData[PROTOTUNNEL_PACKETBUFFER]; int32_t iNumPackets, iRecvKey; ProtoTunnelControlT ControlInfo; uint32_t uClientId; // try any non-empty keys in this tunnel for (iNumPackets = -1, iRecvKey = 0; iRecvKey < PROTOTUNNEL_MAXKEYS; iRecvKey += 1) { // skip empty keys if (pTunnel->aKeyList[iRecvKey][0] == '\0') { continue; } // try and validate packet against this key with a zero stream offset if ((iNumPackets = ProtoTunnelValidatePacket(pProtoTunnel, pTunnel, aPacketData, pPacketData, iPacketSize, pTunnel->aKeyList[iRecvKey])) > 0) { break; } } // if we couldn't successfully decode the packet, we return no match if (iNumPackets < 0) { return(FALSE); } // if we're rematching an active tunnel, we're done if (pTunnel->uActive != 0) { // reset crypt state, matching what ProtoTunnelValidatePacket did to successfully decrypt the packet _ProtoTunnelCryptSetup(pTunnel, iRecvKey, TRUE); return(TRUE); } // get packet control info if (_ProtoTunnelGetControlInfo(pTunnel, &ControlInfo, aPacketData) != 0) { uClientId = ControlInfo.aClientId[0] << 24; uClientId |= ControlInfo.aClientId[1] << 16; uClientId |= ControlInfo.aClientId[2] << 8; uClientId |= ControlInfo.aClientId[3]; } else { NetPrintf(("prototunnel: [%p][%04d] no control data included in packet on inactive tunnel\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); return(FALSE); } // if we found the matching tunnel, we're done if (uClientId == pTunnel->Info.uRemoteClientId) { #if DIRTYCODE_LOGGING if (iRecvKey != 0) { NetPrintf(("prototunnel: [%p][%04d] matched key with index=%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iRecvKey)); } #endif _ProtoTunnelCryptSetup(pTunnel, iRecvKey, FALSE); return(TRUE); } else { NetPrintf(("prototunnel: [%p][%04d] packet clientId 0x%08x does not match tunnel clientId 0x%08x\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uClientId, pTunnel->Info.uRemoteClientId)); if (pProtoTunnel->iVerbosity > 3) { NetPrintMem(aPacketData, (iPacketSize < 64) ? iPacketSize : 64, "prototunnel-recv-decrypt"); } } // no match return(FALSE); } /*F********************************************************************************/ /*! \Function _ProtoTunnelFindTunnel \Description Find the tunnel endpoint for incoming packet \Input *pProtoTunnel - module state \Input *pPacketData - packet data \Input iRecvLen - packet size \Input uRecvAddr - source addr of packet \Input uRecvPort - source port of packet \Output int32_t - matching tunnel index; iMaxTunnels means no match \Version 03/26/2015 (jbrookes) */ /********************************************************************************F*/ static int32_t _ProtoTunnelFindTunnel(ProtoTunnelRefT *pProtoTunnel, uint8_t *pPacketData, int32_t iRecvLen, uint32_t uRecvAddr, uint16_t uRecvPort) { int32_t iTunnel = pProtoTunnel->iMaxTunnels; ProtoTunnelT *pTunnel = NULL; uint32_t uClientId; uint16_t uIdent = PROTOTUNNEL_GetIdentFromPacket(pPacketData); if (uIdent & PROTOTUNNEL_IDENT_HANDSHAKE) { ProtoTunnelHandshakeT *pHandshake = (ProtoTunnelHandshakeT *)pPacketData; uClientId = pHandshake->Version.V1_1.aClientIdent[0] << 24; uClientId |= pHandshake->Version.V1_1.aClientIdent[1] << 16; uClientId |= pHandshake->Version.V1_1.aClientIdent[2] << 8; uClientId |= pHandshake->Version.V1_1.aClientIdent[3]; // find tunnel with matching clientId for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel += 1) { pTunnel = &pProtoTunnel->Tunnels[iTunnel]; if (pTunnel->Info.uRemoteClientId == uClientId) { if (pTunnel->uActive == 0) { // decrypt and validate packet if (!_ProtoTunnelMatchTunnel(pProtoTunnel, pTunnel, pPacketData, iRecvLen)) { NetPrintf(("prototunnel: [%p][%04d] error; validate failed on match of uClientId=0x%08x to inactive tunnel\n", pProtoTunnel, iTunnel, uClientId)); iTunnel = pProtoTunnel->iMaxTunnels; } } else { NetPrintf(("prototunnel: [%p][%04d] warning; matching uClientId=0x%08x to active tunnel\n", pProtoTunnel, iTunnel, uClientId)); } break; } } } else if (uIdent < (unsigned)pProtoTunnel->iMaxTunnels) { // a non-handshake ident field contains the local tunnel index exchanged in handshaking pTunnel = &pProtoTunnel->Tunnels[uIdent]; if (pTunnel->uActive == 1) { // decrypt and validate packet if (_ProtoTunnelMatchTunnel(pProtoTunnel, pTunnel, pPacketData, iRecvLen) != 0) { // found tunnel to match incoming data with iTunnel = (signed)uIdent; } } } // handle port change if ((iTunnel < pProtoTunnel->iMaxTunnels) && (pTunnel != NULL) && (pTunnel->Info.uRemotePort != uRecvPort)) { NetPrintf(("prototunnel: [%p][%04d] detected port change; updating remote port from %d to %d\n", pProtoTunnel, iTunnel, pTunnel->Info.uRemotePort, uRecvPort)); pTunnel->Info.uRemotePort = uRecvPort; } // couldn't match? if (iTunnel == pProtoTunnel->iMaxTunnels) { NetPrintf(("prototunnel: [%p] could not match packet from %a:%d (ident=0x%04x)\n", pProtoTunnel, uRecvAddr, uRecvPort, uIdent)); } return(iTunnel); } /*F********************************************************************************/ /*! \Function _ProtoTunnelRecvData \Description Match incoming data with a tunnel, and if source address is mapped, decode the number of subpackets and decrypt packet data. \Input *pProtoTunnel - module state \Input *pRemotePortList - [out] storage for remote port list of matching tunnel \Input iPortListSize - size of port list \Input *pHeadOffset - [out] storage for sub-packet header data offset \Input *pPacketData - source data \Input iRecvLen - size of source data \Input *pRecvAddr - source address \Output int32_t - PROTOTUNNEL_PACKETRECVFAIL_*, else number of decoded packets \Version 12/02/2005 (jbrookes) */ /********************************************************************************F*/ static int32_t _ProtoTunnelRecvData(ProtoTunnelRefT *pProtoTunnel, uint16_t *pRemotePortList, int32_t iPortListSize, uint32_t *pHeadOffset, uint8_t *pPacketData, int32_t iRecvLen, struct sockaddr *pRecvAddr) { uint32_t uRecvAddr, uRecvPort; int32_t iNumPackets, iTunnel; ProtoTunnelT *pTunnel; uint32_t uCurTick = NetTick(); // get exclusive access to tunnel list NetCritEnter(&pProtoTunnel->TunnelsCritR); // find tunnel with matching remote address, port, and mode for (iTunnel = 0, uRecvAddr = SockaddrInGetAddr(pRecvAddr), uRecvPort = SockaddrInGetPort(pRecvAddr); iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++) { pTunnel = &pProtoTunnel->Tunnels[iTunnel]; if ((pTunnel->Info.uRemoteAddr == uRecvAddr) && (pTunnel->Info.uRemotePort == uRecvPort)) { if (pTunnel->uActive == 0) { // decrypt packet and verify clientId matches if (_ProtoTunnelMatchTunnel(pProtoTunnel, pTunnel, pPacketData, iRecvLen)) { // found tunnel to match incoming data with break; } } else { break; } } } // if no matching tunnel found, use packet data to match to a tunnel if (iTunnel == pProtoTunnel->iMaxTunnels) { iTunnel = _ProtoTunnelFindTunnel(pProtoTunnel, pPacketData, iRecvLen, uRecvAddr, uRecvPort); } // did we find a tunnel? if (iTunnel < pProtoTunnel->iMaxTunnels) { pTunnel = &pProtoTunnel->Tunnels[iTunnel]; // if the tunnel isn't active yet, bind to it and mark it as active if (pTunnel->uActive == 0) { if ((pTunnel->Info.uRemoteAddr != uRecvAddr) || (pTunnel->Info.uRemotePort != uRecvPort)) { NetPrintf(("prototunnel: [%p][%04d] updating remote address from %a:%d to %a:%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pTunnel->Info.uRemoteAddr, pTunnel->Info.uRemotePort, uRecvAddr, uRecvPort)); } // update tunnel to addr/port combo pTunnel->Info.uRemoteAddr = uRecvAddr; pTunnel->Info.uRemotePort = uRecvPort; // mark that we're now active pTunnel->uActive = 1; NetPrintf(("prototunnel: [%p][%04d] matched incoming data from %a:%d to tunnelId 0x%08x / clientId 0x%08x\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uRecvAddr, uRecvPort, pTunnel->uVirtualAddr, pTunnel->Info.uRemoteClientId)); } // make a copy of port list for later use ds_memcpy(pRemotePortList, pTunnel->Info.aRemotePortList, iPortListSize); // display recv info NetPrintfVerbose((pProtoTunnel->iVerbosity, 2, "prototunnel: [%p][%04d] recv - received %d bytes on tunnel 0x%08x from %a:%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iRecvLen, pTunnel->uVirtualAddr, uRecvAddr, uRecvPort)); iNumPackets = _ProtoTunnelDecryptAndValidatePacket(pProtoTunnel, pTunnel, pHeadOffset, pPacketData, iRecvLen, TRUE); if (iNumPackets > 0) { ProtoTunnelControlT ControlInfo; // accumulate receive stats pTunnel->RecvStat.uLastPacketTime = uCurTick; pTunnel->RecvStat.uNumBytes += iRecvLen; pTunnel->RecvStat.uNumSubpacketBytes += iRecvLen - (iNumPackets * PROTOTUNNEL_PKTHDRSIZE) - 2 - pProtoTunnel->uHmacSize; pTunnel->RecvStat.uNumPackets += 1; pTunnel->RecvStat.uNumSubpackets += iNumPackets; pProtoTunnel->uNumSubPktsRecvd += (unsigned)iNumPackets; // process any control info if (_ProtoTunnelGetControlInfo(pTunnel, &ControlInfo, pPacketData) != 0) { if (ControlInfo.uActive == 1) { NetPrintf(("prototunnel: [%p][%04d] connection is active; disabling control packets\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); pTunnel->bSendCtrlInfo = FALSE; } else if (!pTunnel->bSendCtrlInfo) { /* this path is typically exercised when one side of the connection ends up refcounting a tunnel and the other side ends up destroying/recreating the tunnel because both consoles get Blaze messages with different timing. Under such conditions, the prototunnel handshaking (sending of ctrl messages) needs to occur again. */ NetPrintf(("prototunnel: [%p][%04d] peer no longer sees connection as active; resume sending control packets\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); pTunnel->bSendCtrlInfo = TRUE; } } else if ((pTunnel->bSendCtrlInfo) && (pTunnel->uActive == 1)) { NetPrintf(("prototunnel: [%p][%04d] connection is active; disabling control packets\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); pTunnel->bSendCtrlInfo = FALSE; } // rewrite address to virtual address SockaddrInSetAddr(pRecvAddr, pTunnel->uVirtualAddr); } else if (iNumPackets == PROTOTUNNEL_PACKETRECVFAIL_VALIDATE) { /* attempt rematch with a different key and/or stream offset of zero; if rematch is successful we return TRYAGAIN, as we need the caller to call us again following a successful rematch. if rematch fails here, we do not want to try again, so we directly return the error code */ NetPrintf(("prototunnel: [%p][%04d] attempting tunnel rematch after VALIDATE error\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); if (_ProtoTunnelMatchTunnel(pProtoTunnel, pTunnel, pPacketData, iRecvLen)) { NetPrintf(("prototunnel: [%p][%04d] tunnel rematch succeeded\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); iNumPackets = PROTOTUNNEL_PACKETRECVFAIL_TRYAGAIN; } else { NetPrintf(("prototunnel: [%p][%04d] tunnel rematch failed\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); } } else if (iNumPackets == PROTOTUNNEL_PACKETRECVFAIL_OUTOFORDER) { NetPrintf(("prototunnel: [%p][%04d] discarding out of order packet\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); pTunnel->SendStat.uNumDiscards += 1; pProtoTunnel->uNumPktsDiscard += 1; } } else { // tell calling code there is no matching tunnel iNumPackets = PROTOTUNNEL_PACKETRECVFAIL_NOMATCH; } // release exclusive access NetCritLeave(&pProtoTunnel->TunnelsCritR); // return number of packets decoded return(iNumPackets); } /*F********************************************************************************/ /*! \Function _ProtoTunnelRecv \Description Callback to handle idle and recv callbacks on a tunnel socket. \Input *pProtoTunnel - module ref \Input pPacketData - packet data \Input iRecvLen - receive length \Input *pRecvAddr - sender's address \Output int32_t - zero \Version 12/02/2005 (jbrookes) */ /********************************************************************************F*/ static int32_t _ProtoTunnelRecv(ProtoTunnelRefT *pProtoTunnel, uint8_t *pPacketData, int32_t iRecvLen, struct sockaddr *pRecvAddr) { int32_t iPacket, iNumPackets; uint16_t aRemotePortList[PROTOTUNNEL_MAXPORTS]; uint8_t *pPacketHead; uint32_t uHeadOffset; // find matching tunnel and decode packets if ((iNumPackets = _ProtoTunnelRecvData(pProtoTunnel, aRemotePortList, sizeof(aRemotePortList), &uHeadOffset, pPacketData, iRecvLen, pRecvAddr)) == PROTOTUNNEL_PACKETRECVFAIL_TRYAGAIN) { // we were able to match a new key/stream offset; run it through again iNumPackets = _ProtoTunnelRecvData(pProtoTunnel, aRemotePortList, sizeof(aRemotePortList), &uHeadOffset, pPacketData, iRecvLen, pRecvAddr); } // if no packets if (iNumPackets <= 0) { NetPrintf(("prototunnel: [%p] recv - received unhandled %d bytes from %a:%d (%d)\n", pProtoTunnel, iRecvLen, SockaddrInGetAddr(pRecvAddr), SockaddrInGetPort(pRecvAddr), iNumPackets)); // if event callback is set, call it if ((pProtoTunnel->pCallback != NULL) && (iNumPackets == PROTOTUNNEL_PACKETRECVFAIL_NOMATCH)) { pProtoTunnel->pCallback(pProtoTunnel, PROTOTUNNEL_EVENT_RECVNOMATCH, (char *)pPacketData, iRecvLen, pRecvAddr, pProtoTunnel->pUserData); } // display recv info if (pProtoTunnel->iVerbosity > 3) { NetPrintMem(pPacketData, (iRecvLen < 64) ? iRecvLen : 64, "prototunnel-recv-nomatch"); } return(0); } // display decrypted packet data #if DIRTYCODE_LOGGING if (pProtoTunnel->iVerbosity > 3) { NetPrintMem(pPacketData, (iRecvLen < 64) ? iRecvLen : 64, "prototunnel-recv"); } #endif // demultiplex aggregate packet data and push into the appropriate sockets for (iPacket = 0, pPacketHead = pPacketData+uHeadOffset, pPacketData = pPacketData+uHeadOffset+(iNumPackets*PROTOTUNNEL_PKTHDRSIZE)+pProtoTunnel->uHmacSize; iPacket < iNumPackets; iPacket++) { // extract size and port info uint32_t uPktHead = (pPacketHead[0] << 8) | pPacketHead[1]; uint32_t uPktSize = uPktHead >> 4; uint32_t uPortIdx = uPktHead & 0xf; uint32_t uPort = aRemotePortList[uPortIdx]; // if it is a control packet, skip it if (uPortIdx != PROTOTUNNEL_CONTROL_PORTIDX) { SocketT *pSocket; // find SOCK_DGRAM socket bound to this port if (SocketInfo(NULL, 'bndu', uPort, &pSocket, sizeof(pSocket)) == 0) { // rewrite port to match what socket will be expecting SockaddrInSetPort(pRecvAddr, uPort); // display tunneled receive NetPrintfVerbose((pProtoTunnel->iVerbosity, 2, "prototunnel: [%p] recv - %db->%d\n", pProtoTunnel, uPktSize, uPort)); // forward data on to caller SocketControl(pSocket, 'push', uPktSize, pPacketData, pRecvAddr); } else { NetPrintf(("prototunnel: [%p] recv - warning, got data for port %d with no socket bound to that port\n", pProtoTunnel, uPort)); } } else { NetPrintf(("prototunnel: [%p] recv - received control packet\n", pProtoTunnel)); } // skip to next packet pPacketHead += PROTOTUNNEL_PKTHDRSIZE; pPacketData += uPktSize; } return(0); } /*F********************************************************************************/ /*! \Function _ProtoTunnelRecvCallback \Description Callback to handle idle and recv callbacks on a tunnel socket. \Input *pSocket - socket ref \Input iFlags - unused \Input *_pRef - tunnel map ref \Output int32_t - zero \Version 12/02/2005 (jbrookes) */ /********************************************************************************F*/ static int32_t _ProtoTunnelRecvCallback(SocketT *pSocket, int32_t iFlags, void *_pRef) { ProtoTunnelRefT *pProtoTunnel = (ProtoTunnelRefT *)_pRef; struct sockaddr RecvAddr; int32_t iRecvAddrLen = sizeof(RecvAddr), iRecv, iRecvLen; uint8_t aPacketData[PROTOTUNNEL_PACKETBUFFER]; SockaddrInit(&RecvAddr, AF_INET); // got any input? for (iRecv = 0; iRecv < pProtoTunnel->iMaxRecv; iRecv += 1) { if ((iRecvLen = SocketRecvfrom(pSocket, (char *)aPacketData, sizeof(aPacketData), 0, &RecvAddr, &iRecvAddrLen)) <= 0) { break; } // drop packet for easy testing of lost packet recovery #if DIRTYCODE_DEBUG if (pProtoTunnel->iPacketDrop > 0) { pProtoTunnel->iPacketDrop -= 1; NetPrintf(("prototunnel: [%p] dropping received packet\n", pProtoTunnel)); continue; } #endif // forward to any registered raw inbound data filter if (pProtoTunnel->pRawRecvCallback) { if (pProtoTunnel->pRawRecvCallback(pSocket, aPacketData, iRecvLen, &RecvAddr, sizeof(RecvAddr), pProtoTunnel->pRawRecvUserData) > 0) { // inbound raw data was swallowed by the filter, this packet is to be ignored by ProtoTunnel continue; } } // process the packet _ProtoTunnelRecv(pProtoTunnel, aPacketData, iRecvLen, &RecvAddr); } // update stats pProtoTunnel->uNumRecvCalls += iRecv > 0; pProtoTunnel->uNumPktsRecvd += (unsigned)iRecv; return(0); } /*F********************************************************************************/ /*! \Function _ProtoTunnelSocketConfig \Description Configure tunnel socket \Input *pProtoTunnel - pointer to module state \Input *pSocket - socket to configure \Output uint32_t - local port socket is bound to \Version 09/18/2013 (jbrookes) split from _ProtoTunnelSocketOpen */ /********************************************************************************F*/ static uint32_t _ProtoTunnelSocketConfig(ProtoTunnelRefT *pProtoTunnel, SocketT *pSocket) { struct sockaddr BindAddr; uint32_t uPort; // retrieve bound port SocketInfo(pSocket, 'bind', 0, &BindAddr, sizeof(BindAddr)); // reference local port uPort = SockaddrInGetPort(&BindAddr); NetPrintf(("prototunnel: [%p] bound socket to port %d\n", pProtoTunnel, uPort)); // set up for socket callback events SocketCallback(pSocket, CALLB_RECV, pProtoTunnel->iIdleCbRate, pProtoTunnel, &_ProtoTunnelRecvCallback); return(uPort); } /*F********************************************************************************/ /*! \Function _ProtoTunnelSocketOpen \Description Open a tunnel socket \Input *pProtoTunnel - pointer to module state \Input iPort - port tunnel will go over \Output SocketT * - new socket or NULL on failure \Version 12/02/2005 (jbrookes) */ /********************************************************************************F*/ static SocketT *_ProtoTunnelSocketOpen(ProtoTunnelRefT *pProtoTunnel, int32_t iPort) { struct sockaddr BindAddr; SocketT *pSocket; int32_t iResult; // open the socket if ((pSocket = SocketOpen(AF_INET, SOCK_DGRAM, PROTOTUNNEL_IPPROTO)) == NULL) { NetPrintf(("prototunnel: [%p] unable to open socket\n", pProtoTunnel)); return(NULL); } // bind socket to specified port SockaddrInit(&BindAddr, AF_INET); SockaddrInSetPort(&BindAddr, iPort); if ((iResult = SocketBind(pSocket, &BindAddr, sizeof(BindAddr))) != SOCKERR_NONE) { if (_ProtoTunnel_bRetryRandomOnFailure) { NetPrintf(("prototunnel: [%p] error %d binding to port %d, trying random\n", pProtoTunnel, iResult, iPort)); SockaddrInSetPort(&BindAddr, 0); if ((iResult = SocketBind(pSocket, &BindAddr, sizeof(BindAddr))) != SOCKERR_NONE) { NetPrintf(("prototunnel: [%p] error %d binding to port\n", pProtoTunnel, iResult)); SocketClose(pSocket); return(NULL); } } else { NetPrintf(("prototunnel: [%p] error %d binding to port %d\n", pProtoTunnel, iResult, iPort)); SocketClose(pSocket); return(NULL); } } // return socket to caller return(pSocket); } /*F********************************************************************************/ /*! \Function _ProtoTunnelStatCalc \Description Calculates ProtoTunnel Stats \Input *pTunnel - Tunnel that calculations are to be performed on. \Input iSelect - 'rcvs' for receive stats and 'snds' for send stats \Version 09/17/2014 (tcho) */ /********************************************************************************F*/ static void _ProtoTunnelStatCalc(ProtoTunnelT *pTunnel, int32_t iSelect) { uint32_t uCurTick = NetTick(); uint32_t uRange = 0; if (iSelect == 'rcvs') { if (pTunnel->RecvStat.uUpdateTime == 0) { pTunnel->RecvStat.uBytePerSecond = 0; pTunnel->RecvStat.uRawBytesPerSecond = 0; pTunnel->RecvStat.uEfficiency = 0; } else { uRange = NetTickDiff(uCurTick, pTunnel->RecvStat.uUpdateTime); pTunnel->RecvStat.uRawBytesPerSecond = (pTunnel->RecvStat.uNumBytes - pTunnel->uLastRecvNumBytes) * 1000 / uRange; pTunnel->RecvStat.uBytePerSecond = (pTunnel->RecvStat.uNumSubpacketBytes - pTunnel->uLastRecvNumSubpacketBytes) * 1000 / uRange; pTunnel->RecvStat.uEfficiency = (uint32_t)(((float)pTunnel->RecvStat.uBytePerSecond/(float)pTunnel->RecvStat.uRawBytesPerSecond) * 100.0f); } // update tracking variable pTunnel->RecvStat.uPrevUpdateTime = pTunnel->RecvStat.uUpdateTime; pTunnel->RecvStat.uUpdateTime = uCurTick; pTunnel->uLastRecvNumBytes = pTunnel->RecvStat.uNumBytes; pTunnel->uLastRecvNumSubpacketBytes = pTunnel->RecvStat.uNumSubpacketBytes; } else if (iSelect == 'snds') { if (pTunnel->SendStat.uUpdateTime == 0) { pTunnel->SendStat.uBytePerSecond = 0; pTunnel->SendStat.uRawBytesPerSecond = 0; pTunnel->SendStat.uEfficiency = 0; } else { uRange = NetTickDiff(uCurTick, pTunnel->SendStat.uUpdateTime); pTunnel->SendStat.uRawBytesPerSecond = (pTunnel->SendStat.uNumBytes - pTunnel->uLastSendNumBytes) * 1000 / uRange; pTunnel->SendStat.uBytePerSecond = (pTunnel->SendStat.uNumSubpacketBytes - pTunnel->uLastSendNumSubpacketBytes) * 1000 / uRange; pTunnel->SendStat.uEfficiency = (uint32_t)(((float)pTunnel->SendStat.uBytePerSecond/(float)pTunnel->SendStat.uRawBytesPerSecond) * 100.0f); } // update tracking variable pTunnel->SendStat.uPrevUpdateTime = pTunnel->SendStat.uUpdateTime; pTunnel->SendStat.uUpdateTime = uCurTick; pTunnel->uLastSendNumBytes = pTunnel->SendStat.uNumBytes; pTunnel->uLastSendNumSubpacketBytes = pTunnel->SendStat.uNumSubpacketBytes; } } /*** Public functions *************************************************************/ /*F********************************************************************************/ /*! \Function ProtoTunnelCreate \Description Create the ProtoTunnel module. \Input iMaxTunnels - maximum number of tunnels module can allocate \Input iTunnelPort - local port for socket all tunnels will use \Output ProtoTunnelRefT * - pointer to new module, or NULL \Version 12/02/2005 (jbrookes) */ /********************************************************************************F*/ ProtoTunnelRefT *ProtoTunnelCreate(int32_t iMaxTunnels, int32_t iTunnelPort) { ProtoTunnelRefT *pProtoTunnel; int32_t iRefSize = sizeof(*pProtoTunnel) + ((iMaxTunnels-1) * sizeof(ProtoTunnelT)); int32_t iMemGroup; void *pMemGroupUserData; // maximum of 32k tunnels due to size of ident field if (iMaxTunnels > 32767) { NetPrintf(("prototunnel: clamping requested %d tunnels to max of 32767\n", iMaxTunnels)); iMaxTunnels = 32767; } // query current mem group data DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); // allocate and init module state if ((pProtoTunnel = DirtyMemAlloc(iRefSize, PROTOTUNNEL_MEMID, iMemGroup, pMemGroupUserData)) == NULL) { NetPrintf(("prototunnel: could not allocate module state\n")); return(NULL); } ds_memclr(pProtoTunnel, iRefSize); pProtoTunnel->iMemGroup = iMemGroup; pProtoTunnel->pMemGroupUserData = pMemGroupUserData; pProtoTunnel->iMaxTunnels = iMaxTunnels; pProtoTunnel->iMaxRecv = 64; pProtoTunnel->iIdleCbRate = 100; pProtoTunnel->uTunnelPort = iTunnelPort; pProtoTunnel->uHmacType = CRYPTHASH_MURMUR3; pProtoTunnel->uHmacSize = PROTOTUNNEL_HMAC_DEFSIZE; pProtoTunnel->uVersion = PROTOTUNNEL_VERSION; if (_ProtoTunnelGetBaseAddress(pProtoTunnel) != 0) { DirtyMemFree(pProtoTunnel, PROTOTUNNEL_MEMID, pProtoTunnel->iMemGroup, pProtoTunnel->pMemGroupUserData); return(NULL); } // create the tunnel socket if ((pProtoTunnel->pSocket = _ProtoTunnelSocketOpen(pProtoTunnel, iTunnelPort)) == NULL) { _ProtoTunnelReleaseBaseAddress(pProtoTunnel); DirtyMemFree(pProtoTunnel, PROTOTUNNEL_MEMID, pProtoTunnel->iMemGroup, pProtoTunnel->pMemGroupUserData); return(NULL); } pProtoTunnel->uTunnelPort = _ProtoTunnelSocketConfig(pProtoTunnel, pProtoTunnel->pSocket); // initialize critical sections NetCritInit(&pProtoTunnel->TunnelsCritS, "prototunnel-global-send"); NetCritInit(&pProtoTunnel->TunnelsCritR, "prototunnel-global-recv"); // restrict max udp packet size SocketControl(NULL, 'maxp', PROTOTUNNEL_MAXPACKET, NULL, NULL); // hook into global socket send hook SocketControl(NULL, 'sdcb', TRUE, (void *)_ProtoTunnelSendCallback, pProtoTunnel); // configure prototunnel socket to NOT call socket send callbacks SocketControl(pProtoTunnel->pSocket, 'scbk', FALSE, NULL, NULL); // return ref to caller return(pProtoTunnel); } /*F********************************************************************************/ /*! \Function ProtoTunnelDestroy \Description Destroy the ProtoTunnel module. \Input *pProtoTunnel - pointer to module state \Version 12/02/2005 (jbrookes) */ /********************************************************************************F*/ void ProtoTunnelDestroy(ProtoTunnelRefT *pProtoTunnel) { // clear global socket send hook SocketControl(NULL, 'sdcb', FALSE, (void *)_ProtoTunnelSendCallback, pProtoTunnel); // close tunnel socket if (pProtoTunnel->pSocket != NULL) { SocketClose(pProtoTunnel->pSocket); } // close server socket, if allocated if (pProtoTunnel->pServerSocket != NULL) { SocketClose(pProtoTunnel->pServerSocket); } // dispose of critical sections NetCritKill(&pProtoTunnel->TunnelsCritR); NetCritKill(&pProtoTunnel->TunnelsCritS); _ProtoTunnelReleaseBaseAddress(pProtoTunnel); // dispose of module memory DirtyMemFree(pProtoTunnel, PROTOTUNNEL_MEMID, pProtoTunnel->iMemGroup, pProtoTunnel->pMemGroupUserData); } /*F********************************************************************************/ /*! \Function ProtoTunnelCallback \Description Set event callback \Input *pProtoTunnel - pointer to module state \Input *pCallback - callback pointer \Input *pUserData - callback data \Version 03/24/2006 (jbrookes) */ /********************************************************************************F*/ void ProtoTunnelCallback(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelCallbackT *pCallback, void *pUserData) { pProtoTunnel->pCallback = pCallback; pProtoTunnel->pUserData = pUserData; } /*F********************************************************************************/ /*! \Function ProtoTunnelAlloc \Description Allocate a tunnel. \Input *pProtoTunnel - pointer to module state \Input *pInfo - tunnel info \Input *pKey - encryption key for tunnel \Output int32_t - negative=error, else allocated tunnel id \Version 12/07/2005 (jbrookes) */ /********************************************************************************F*/ int32_t ProtoTunnelAlloc(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelInfoT *pInfo, const char *pKey) { ProtoTunnelT *pTunnel; int32_t iTunnel; // get exclusive access to tunnel list NetCritEnter(&pProtoTunnel->TunnelsCritS); NetCritEnter(&pProtoTunnel->TunnelsCritR); // see if we already have a tunnel with this clientId for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++) { pTunnel = &pProtoTunnel->Tunnels[iTunnel]; if (pTunnel->Info.uRemoteClientId == pInfo->uRemoteClientId) { int32_t iKey, iKeySlot, iResult = (signed)pTunnel->uVirtualAddr; // refcount the tunnel NetPrintf(("prototunnel: [%p][%04d] refcounting tunnel with id=0x%08x key=%s clientId=0x%08x remote address=%a\n", pProtoTunnel, iTunnel, pTunnel->uVirtualAddr, pKey, pInfo->uRemoteClientId, pInfo->uRemoteAddr)); pTunnel->uRefCount += 1; // append key to key list for (iKey = 0, iKeySlot = -1; iKey < PROTOTUNNEL_MAXKEYS; iKey++) { #if DIRTYCODE_LOGGING if (!strcmp(pKey, pTunnel->aKeyList[iKey])) { NetPrintf(("prototunnel: [%p][%04d] warning - duplicate key %s on alloc\n", pProtoTunnel, iTunnel, pKey)); } #endif if ((iKeySlot == -1) && (pTunnel->aKeyList[iKey][0] == '\0')) { iKeySlot = iKey; } } if (iKeySlot != -1) { ds_strnzcpy(pTunnel->aKeyList[iKeySlot], pKey, sizeof(pTunnel->aKeyList[iKeySlot])); } else { NetPrintf(("prototunnel: [%p][%04d] error - key overflow on alloc\n", pProtoTunnel, iTunnel)); iResult = -1; } NetCritLeave(&pProtoTunnel->TunnelsCritR); NetCritLeave(&pProtoTunnel->TunnelsCritS); return(iResult); } } // find an unallocated tunnel for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++) { if (pProtoTunnel->Tunnels[iTunnel].uVirtualAddr == 0) { break; } } // make sure we found room if (iTunnel == pProtoTunnel->iMaxTunnels) { NetPrintf(("prototunnel: [%p] could not allocate a new tunnel\n", pProtoTunnel)); NetCritLeave(&pProtoTunnel->TunnelsCritR); NetCritLeave(&pProtoTunnel->TunnelsCritS); return(-1); } // ref and init tunnel pTunnel = &pProtoTunnel->Tunnels[iTunnel]; ds_memclr(pTunnel, sizeof(*pTunnel)); ds_memcpy_s(&pTunnel->Info, sizeof(pTunnel->Info), pInfo, sizeof(*pInfo)); NetCritInit(&pTunnel->PacketCrit, "prototunnel-tunnel"); ds_strnzcpy(pTunnel->aKeyList[0], pKey, sizeof(pTunnel->aKeyList[0])); pTunnel->uRefCount = 1; pTunnel->uLastTunnelSend = NetTick(); // init tunnel crypto state _ProtoTunnelCryptSetup(pTunnel, 0, TRUE); // port index seven is reserved by ProtoTunnel and always encrypted pTunnel->Info.aPortFlags[PROTOTUNNEL_CONTROL_PORTIDX] = PROTOTUNNEL_PORTFLAG_ENCRYPTED; // initialize to send connect info pTunnel->bSendCtrlInfo = TRUE; // assign a virtual address/id pTunnel->uVirtualAddr = pProtoTunnel->uVirtualAddr++; // if unspecified, set remote port if (pTunnel->Info.uRemotePort == 0) { pTunnel->Info.uRemotePort = pProtoTunnel->uTunnelPort; } // set default tunnel protocol version _ProtoTunnelSetVersion(pProtoTunnel, pTunnel, pProtoTunnel->uVersion); #if DIRTYCODE_LOGGING pTunnel->uLastStatUpdate = NetTick(); #endif // release exclusive access to tunnel list NetCritLeave(&pProtoTunnel->TunnelsCritR); NetCritLeave(&pProtoTunnel->TunnelsCritS); // debug spam #if DIRTYCODE_LOGGING { int32_t iPort; NetPrintf(("prototunnel: [%p][%04d] creating map to remote client %a:%d (id=0x%08x)\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pTunnel->Info.uRemoteAddr, pTunnel->Info.uRemotePort, pTunnel->Info.uRemoteClientId)); for (iPort = 0; (iPort < PROTOTUNNEL_MAXPORTS) && (pTunnel->Info.aRemotePortList[iPort] != 0); iPort++) { NetPrintf(("prototunnel: [%p][%04d] [%d] %d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iPort, pTunnel->Info.aRemotePortList[iPort])); } } #endif // return addr/id to caller NetPrintf(("prototunnel: [%p][%04d] allocated tunnel with id 0x%08x key=%s\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pTunnel->uVirtualAddr, pTunnel->aKeyList[0])); return(pTunnel->uVirtualAddr); } /*F********************************************************************************/ /*! \Function ProtoTunnelFree \Description Free a tunnel. \Input *pProtoTunnel - pointer to module state \Input uTunnelId - id of tunnel to free \Input *pKey - key tunnel was allocated with \Notes pKey is only required when tunnel refcounting is used. Otherwise, pKey may be specified as NULL. \Version 12/07/2005 (jbrookes) */ /********************************************************************************F*/ uint32_t ProtoTunnelFree(ProtoTunnelRefT *pProtoTunnel, uint32_t uTunnelId, const char *pKey) { return(ProtoTunnelFree2(pProtoTunnel, uTunnelId, pKey, 0)); } /*F********************************************************************************/ /*! \Function ProtoTunnelFree2 \Description Free a tunnel. Same as ProtoTunnelFree but takes an IP address that is no longer used. Consider for deprecation in the future. \Input *pProtoTunnel - pointer to module state \Input uTunnelId - id of tunnel to free \Input *pKey - key tunnel was allocated with \Input uAddr - address of peer that is being freed \Notes pKey is only required when tunnel refcounting is used. Otherwise, pKey may be specified as NULL. \Version 03/15/2010 (jrainy) */ /********************************************************************************F*/ uint32_t ProtoTunnelFree2(ProtoTunnelRefT *pProtoTunnel, uint32_t uTunnelId, const char *pKey, uint32_t uAddr) { ProtoTunnelT *pTunnel; int32_t iTunnel; uint32_t uRet = (uint32_t)-1; #if DIRTYCODE_LOGGING uint32_t bFound = FALSE; #endif // get exclusive access to tunnel list NetCritEnter(&pProtoTunnel->TunnelsCritS); NetCritEnter(&pProtoTunnel->TunnelsCritR); // find tunnel id for (iTunnel = 0, pTunnel = &pProtoTunnel->Tunnels[0]; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++, pTunnel++) { // found it? if (pTunnel->uVirtualAddr == uTunnelId) { #if DIRTYCODE_LOGGING bFound = TRUE; #endif // deallocate the tunnel if (pTunnel->uRefCount == 1) { NetPrintf(("prototunnel: [%p][%04d] freeing tunnel 0x%08x\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uTunnelId)); // flush buffer before destroy _ProtoTunnelBufferSend(pProtoTunnel, pTunnel, NetTick()); // dispose of critical section NetCritKill(&pTunnel->PacketCrit); ds_memclr(pTunnel, sizeof(*pTunnel)); uRet = 0; } else { int32_t iKey; NetPrintf(("prototunnel: [%p][%04d] decrementing refcount of tunnel 0x%08x (clientId=0x%08x)\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uTunnelId, pTunnel->Info.uRemoteClientId)); // remove key from key list for (iKey = 0; iKey < PROTOTUNNEL_MAXKEYS; iKey++) { if (!strcmp(pKey, pTunnel->aKeyList[iKey])) { ds_memclr(pTunnel->aKeyList[iKey], sizeof(pTunnel->aKeyList[iKey])); break; } } // did we blow away our send key? if (pTunnel->uSendKey == (uint8_t)iKey) { NetPrintf(("prototunnel: [%p][%04d] free of current send key; picking another\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels))); for (iKey = 0; iKey < PROTOTUNNEL_MAXKEYS; iKey++) { if (pTunnel->aKeyList[iKey][0] != '\0') { NetPrintf(("prototunnel: [%p][%04d] picking key %d (%s) offset=%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iKey, pTunnel->aKeyList[iKey], pTunnel->uSendOffset)); // init tunnel crypto state for new key _ProtoTunnelCryptSetup(pTunnel, iKey, FALSE); break; } } } else if (iKey == PROTOTUNNEL_MAXKEYS) { NetPrintf(("prototunnel: [%p][%04d] could not find key %s in key list on free\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pKey)); } pTunnel->uRefCount -= 1; uRet = pTunnel->uRefCount; NetPrintf(("prototunnel: [%p][%04d] refcounting down tunnel with id=0x%08x key=%s clientId=0x%08x remote address=%a\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pTunnel->uVirtualAddr, pKey, pTunnel->Info.uRemoteClientId, pTunnel->Info.uRemoteAddr)); } // done break; } } #if DIRTYCODE_LOGGING if (bFound == FALSE) { NetPrintf(("prototunnel: [%p][%04d] could not find tunnel with id 0x%08x to free\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), uTunnelId)); } #endif // release exclusive access to tunnel list NetCritLeave(&pProtoTunnel->TunnelsCritR); NetCritLeave(&pProtoTunnel->TunnelsCritS); return(uRet); } /*F********************************************************************************/ /*! \Function ProtoTunnelUpdatePortList \Description Updates the port list for the given tunnel. Port and port flag info is copied over from the specified info structure if the port value is non- zero. \Input *pProtoTunnel - pointer to module state \Input uTunnelId - id of tunnel to update port mapping for \Input *pInfo - structure containing updated port info \Output int32_t - zero=success, else could not find tunnel with given id \Version 06/12/2008 (jbrookes) */ /********************************************************************************F*/ int32_t ProtoTunnelUpdatePortList(ProtoTunnelRefT *pProtoTunnel, uint32_t uTunnelId, ProtoTunnelInfoT *pInfo) { ProtoTunnelT *pTunnel; int32_t iTunnel, iResult = -1; int32_t iPort; // get exclusive access to tunnel list NetCritEnter(&pProtoTunnel->TunnelsCritS); NetCritEnter(&pProtoTunnel->TunnelsCritR); // find tunnel id for (iTunnel = 0, pTunnel = &pProtoTunnel->Tunnels[0]; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++, pTunnel++) { // found it? if (pTunnel->uVirtualAddr == uTunnelId) { // copy portlist items that should be updated for (iPort = 0; iPort < PROTOTUNNEL_MAXPORTS; iPort += 1) { if (pInfo->aRemotePortList[iPort] != 0) { NetPrintf(("prototunnel: [%p][%04d] updating port mapping %d for tunnel=0x%08x from (%d,%d) to (%d,%d)\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), iPort, uTunnelId, pTunnel->Info.aRemotePortList[iPort], pTunnel->Info.aPortFlags[iPort], pInfo->aRemotePortList[iPort], pInfo->aPortFlags[iPort])); pTunnel->Info.aRemotePortList[iPort] = pInfo->aRemotePortList[iPort]; pTunnel->Info.aPortFlags[iPort] = pInfo->aPortFlags[iPort]; } } iResult = 0; break; } } // release exclusive access to tunnel list NetCritLeave(&pProtoTunnel->TunnelsCritR); NetCritLeave(&pProtoTunnel->TunnelsCritS); // return result code to caller return(iResult); } /*F********************************************************************************/ /*! \Function ProtoTunnelValidatePacket \Description Validate key against packet, a tunnel has not been allocated yet \Input *pProtoTunnel- pointer to module state \Input *pTunnel - pointer to tunnel \Input *pOutputData - [out] pointer to buffer to store decrypted data (may be NULL) \Input *pPacketData - pointer to tunnel packet \Input iPacketSize - size of tunnel packet \Input *pKey - encryption key for tunnel \Output int32_t - PROTOTUNNEL_PACKETRECVFAIL_* on error, else number of subpackets decoded \Version 06/06/2006 (jbrookes) */ /********************************************************************************F*/ int32_t ProtoTunnelValidatePacket(ProtoTunnelRefT *pProtoTunnel, ProtoTunnelT *pTunnel, uint8_t *pOutputData, const uint8_t *pPacketData, int32_t iPacketSize, const char *pKey) { uint8_t aPacketData[PROTOTUNNEL_PACKETBUFFER]; int32_t iNumPackets; uint32_t uHeadOffset; CryptArc4T CryptTemp; uint32_t uRecvOffsetTemp; uint8_t aHmacKeyTemp[64]; // copy packet data to temp buffer ds_memcpy_s(aPacketData, sizeof(aPacketData), pPacketData, iPacketSize); // save current hmac key ds_memcpy_s(aHmacKeyTemp, sizeof(aHmacKeyTemp), pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey)); // init HMAC key by running IV through RC4 initialized with tunnel key CryptArc4Init(&CryptTemp, (const unsigned char *)pKey, (int32_t)strlen(pKey), PROTOTUNNEL_CRYPTARC4_ITER); ds_memcpy_s(pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), _ProtoTunnel_aHmacInitVec, sizeof(_ProtoTunnel_aHmacInitVec)); CryptArc4Apply(&CryptTemp, (uint8_t *)pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey)); // save current recv state ds_memcpy_s(&CryptTemp, sizeof(CryptTemp), &pTunnel->CryptRecvState, sizeof(pTunnel->CryptRecvState)); uRecvOffsetTemp = pTunnel->uRecvOffset; // init recv state with specified key & reset offset CryptArc4Init(&pTunnel->CryptRecvState, (unsigned char *)pKey, (int32_t)strlen(pKey), PROTOTUNNEL_CRYPTARC4_ITER); pTunnel->uRecvOffset = 0; // decrypt and validate packet data iNumPackets = _ProtoTunnelDecryptAndValidatePacket(pProtoTunnel, pTunnel, &uHeadOffset, aPacketData, iPacketSize, FALSE); // restore previous recv state ds_memcpy(&pTunnel->CryptRecvState, &CryptTemp, sizeof(CryptTemp)); pTunnel->uRecvOffset = uRecvOffsetTemp; // restore previous HMAC key ds_memcpy_s(pTunnel->aHmacKey, sizeof(pTunnel->aHmacKey), aHmacKeyTemp, sizeof(aHmacKeyTemp)); // copy decrypted data to output buffer, if available if ((iNumPackets > 0) && (pOutputData != NULL)) { ds_memcpy_s(pOutputData, iPacketSize, aPacketData, sizeof(aPacketData)); } // return number of packets decoded return(iNumPackets); } /*F********************************************************************************/ /*! \Function ProtoTunnelStatus \Description Get module status \Input *pProtoTunnel - pointer to module state \Input iSelect - status selector \Input iValue - selector specific \Input *pBuf - [out] - selector specific \Input iBufSize - size of output buffer \Output int32_t - selector specific \Notes iControl can be one of the following: \verbatim 'actv' - return number of active tunnels based on version (via iValue) 'bnds' - return if the specified port (iValue) is part of the server port list 'dpkt' - return number of out-of-order packet discards 'hmac' - return HMAC type/size 'idle' - return idle callback rate 'lprt' - return local socket port 'maxp' - return maximum size of packet that can be tunneled (NOTE: pProtoTunnel can be NULL) 'rcvs' - copy receive statistics to pBuf 'rcal' - return number of recv calls 'rmax' - return maximum number of recv calls allowed in _ProtoTunnelRecvCallback 'rprt' - remote game port for the specificed tunnel id 'rsub' - return number of sub-packets received 'rtot' - return number of packets received 'snds' - copy send statistics to pBuf 'sock' - copy socket ref to pBuf 'vers' - return protocol version used with this tunnel id (specified with iValue) 'vset' - return set of supported versions as a comma delimited string (via pBuf) 'vtop' - convert virtual address to physical address (pBuf == sockaddr, optional) \endverbatim \Version 12/02/2005 (jbrookes) */ /********************************************************************************F*/ int32_t ProtoTunnelStatus(ProtoTunnelRefT *pProtoTunnel, int32_t iSelect, int32_t iValue, void *pBuf, int32_t iBufSize) { if (iSelect == 'actv') { int32_t iTunnel; int32_t iNumActive = 0; // acquire exclusive access to tunnel list NetCritEnter(&pProtoTunnel->TunnelsCritS); NetCritEnter(&pProtoTunnel->TunnelsCritR); // get active tunnels by version for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel += 1) { const ProtoTunnelT *pTunnel = &pProtoTunnel->Tunnels[iTunnel]; if (pTunnel->uActive == 0) { continue; } if (pTunnel->Info.uTunnelVers == (uint16_t)iValue) { iNumActive += 1; } } // release exclusive access to tunnel list NetCritLeave(&pProtoTunnel->TunnelsCritR); NetCritLeave(&pProtoTunnel->TunnelsCritS); return(iNumActive); } if (iSelect == 'bnds') { return(_ProtoTunnelServerPortListFunc(pProtoTunnel, (uint16_t)iValue, PROTOTUNNEL_PORTLIST_CHK)); } if (iSelect == 'dpkt') { return(pProtoTunnel->uNumPktsDiscard); } if (iSelect == 'hmac') { return((pProtoTunnel->uHmacType << 4) | pProtoTunnel->uHmacSize); } if (iSelect == 'idle') { return(pProtoTunnel->iIdleCbRate); } if (iSelect == 'lprt') { return(pProtoTunnel->uTunnelPort); } if (iSelect == 'maxp') { return(PROTOTUNNEL_MAXPACKET); } if (iSelect == 'rcal') { return(pProtoTunnel->uNumRecvCalls); } if (iSelect == 'rmax') { return(pProtoTunnel->iMaxRecv); } if (iSelect == 'rprt') { if ((pBuf != NULL) && (iBufSize == sizeof(uint16_t))) { int32_t iTunnel = _ProtoTunnelIndexFromId(pProtoTunnel, (uint32_t)iValue); if (iTunnel != -1) { *(uint16_t *)pBuf = pProtoTunnel->Tunnels[iTunnel].Info.uRemotePort; return(0); } else { NetPrintf(("prototunnel: [%p] 'rprt' status selector called with unknown tunnelid=0x%08x\n", pProtoTunnel, iValue)); return(-1); } } } if (iSelect == 'rsub') { return(pProtoTunnel->uNumSubPktsRecvd); } if (iSelect == 'rtot') { return(pProtoTunnel->uNumPktsRecvd); } if (iSelect == 'rcvs') { if ((pBuf != NULL) && (iBufSize == sizeof(ProtoTunnelStatT)) && (iValue < pProtoTunnel->iMaxTunnels)) { _ProtoTunnelStatCalc(&pProtoTunnel->Tunnels[iValue], iSelect); ds_memcpy(pBuf, &pProtoTunnel->Tunnels[iValue].RecvStat, sizeof(ProtoTunnelStatT)); return(0); } } if (iSelect == 'snds') { if ((pBuf != NULL) && (iBufSize == sizeof(ProtoTunnelStatT)) && (iValue < pProtoTunnel->iMaxTunnels)) { _ProtoTunnelStatCalc(&pProtoTunnel->Tunnels[iValue], iSelect); ds_memcpy(pBuf, &pProtoTunnel->Tunnels[iValue].SendStat, sizeof(ProtoTunnelStatT)); return(0); } } if (iSelect == 'sock') { ds_memcpy(pBuf, &pProtoTunnel->pSocket, iBufSize); return(0); } if (iSelect == 'vers') { int32_t iResult = -1; int32_t iTunnel = _ProtoTunnelIndexFromId(pProtoTunnel, (uint32_t)iValue); if (iTunnel != -1) { iResult = (int32_t)pProtoTunnel->Tunnels[iTunnel].Info.uTunnelVers; } else { NetPrintf(("prototunnel: [%p] 'vers' status selector called with unknown tunnelid=0x%08x\n", pProtoTunnel, iValue)); } return(iResult); } if (iSelect == 'vset') { if (pBuf != NULL && iBufSize > 0) { ds_strnzcpy(pBuf, PROTOTUNNEL_VERSIONS, iBufSize); return(0); } } if (iSelect == 'vtop') { return(_ProtoTunnelVirtualToPhysical(pProtoTunnel, iValue, pBuf, iBufSize)); } // selector not supported return(-1); } /*F********************************************************************************/ /*! \Function ProtoTunnelControl \Description Control the module \Input *pProtoTunnel - pointer to module state \Input iControl - control selector \Input iValue - selector specific \Input iValue2 - selector specific \Input *pValue - selector specific \Output int32_t - selector result \Notes iControl can be one of the following: \verbatim 'bind' - recreate tunnel socket bound to specified port (iValue is port to bind to) 'bndr' - remove a server tunnel port mapping from list (iValue is port to remove) 'bnds' - recreate server tunnel socket bound to ephemeral port (iValue is ignored, iValue2 is remote server port) 'clid' - set local clientId for all tunnels 'drop' - drop next iValue packets (debug only; used to test crypt recovery) 'flsh' - flush the specified tunnelId 'hmac' - set hmac type (iValue) and size (iValue2) 'idle' - set idle callback rate in milliseconds (iValue; default is 100) 'rand' - set behavior to retry random port on bind failure via iValue, call before ProtoTunnelCreate 'rate' - set flush rate in milliseconds; defaults to 16ms 'rmax' - set maximum receive calls per call to _ProtoTunnelRecvCallback (iValue; default is 64) 'rprt' - set specified tunnel's remote port 'rrcb' - set raw receive callback 'rrud' - set raw receive user data 'sock' - set socket ref 'spam' - set verbosity level (debug only) 'tcid' - set per-tunnel local clientId override for specific tunnel \endverbatim Unrecognized selectors are passed through to SocketControl() \Version 12/02/2005 (jbrookes) */ /********************************************************************************F*/ int32_t ProtoTunnelControl(ProtoTunnelRefT *pProtoTunnel, int32_t iControl, int32_t iValue, int32_t iValue2, const void *pValue) { if (iControl == 'bind') { SocketT *pOldSocket = NULL, *pNewSocket = NULL; NetPrintf(("prototunnel: [%p] recreating tunnel socket bound to port %d\n", pProtoTunnel, iValue)); // early out if we already have socket bound to this port if (pProtoTunnel->uTunnelPort == (unsigned)iValue) { NetPrintf(("prototunnel: [%p] already have socket bound to port %d\n", pProtoTunnel, pProtoTunnel->uTunnelPort)); return(0); } // recreate tunnel socket bound to new port if ((pNewSocket = _ProtoTunnelSocketOpen(pProtoTunnel, iValue)) == NULL) { NetPrintf(("prototunnel: [%p] could not recreate tunnel socket\n", pProtoTunnel)); return(-1); } // acquire exclusive access to tunnel list NetCritEnter(&pProtoTunnel->TunnelsCritS); NetCritEnter(&pProtoTunnel->TunnelsCritR); // save old tunnel socket and replace with new socket if (pProtoTunnel->pSocket != NULL) { pOldSocket = pProtoTunnel->pSocket; } pProtoTunnel->pSocket = pNewSocket; // release exclusive access to tunnel list NetCritLeave(&pProtoTunnel->TunnelsCritR); NetCritLeave(&pProtoTunnel->TunnelsCritS); // reconfigure new tunnel socket pProtoTunnel->uTunnelPort = _ProtoTunnelSocketConfig(pProtoTunnel, pProtoTunnel->pSocket); // close old socket if (pOldSocket != NULL) { SocketClose(pOldSocket); } return(0); } if (iControl == 'bndr') { // del remote port from list if (!_ProtoTunnelServerPortListFunc(pProtoTunnel, (uint16_t)iValue, PROTOTUNNEL_PORTLIST_DEL)) { NetPrintf(("prototunnel: [%p] could not del port %d from server port list\n", pProtoTunnel, iValue)); } return(0); } if (iControl == 'bnds') { if (pProtoTunnel->pServerSocket == NULL) { SocketT *pSocket = NULL; // create tunnel socket (binding to an ephemeral port) NetPrintf(("prototunnel: [%p] creating server tunnel socket (binding to system selected ephemeral port) - uServerRemotePort = %d\n", pProtoTunnel, iValue)); if ((pSocket = _ProtoTunnelSocketOpen(pProtoTunnel, 0)) == NULL) { NetPrintf(("prototunnel: [%p] could not create server tunnel socket\n", pProtoTunnel)); return(-1); } // acquire exclusive access to tunnel list NetCritEnter(&pProtoTunnel->TunnelsCritS); NetCritEnter(&pProtoTunnel->TunnelsCritR); // set new socket pProtoTunnel->pServerSocket = pSocket; // release exclusive access to tunnel list NetCritLeave(&pProtoTunnel->TunnelsCritR); NetCritLeave(&pProtoTunnel->TunnelsCritS); // reconfigure new tunnel socket _ProtoTunnelSocketConfig(pProtoTunnel, pProtoTunnel->pServerSocket); } else { NetPrintf(("prototunnel: --- reusing server socket ---\n")); } // add remote port to list if (!_ProtoTunnelServerPortListFunc(pProtoTunnel, (uint16_t)iValue, PROTOTUNNEL_PORTLIST_ADD)) { NetPrintf(("prototunnel: [%p] could not add port to server port list\n", pProtoTunnel)); } return(0); } if (iControl == 'clid') { NetPrintf(("prototunnel: [%p] setting local clientId=0x%08x\n", pProtoTunnel, iValue)); pProtoTunnel->uLocalClientId = iValue; return(0); } #if DIRTYCODE_DEBUG if (iControl == 'drop') { pProtoTunnel->iPacketDrop = iValue; return(0); } #endif if ((iControl == 'flsh') || (iControl == 'rprt')) { ProtoTunnelT *pTunnel; int32_t iTunnel; uint32_t uCurTick = NetTick(); // acquire exclusive access to tunnel list NetCritEnter(&pProtoTunnel->TunnelsCritS); NetCritEnter(&pProtoTunnel->TunnelsCritR); // flush specified tunnel for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++) { pTunnel = &pProtoTunnel->Tunnels[iTunnel]; if (pTunnel->uVirtualAddr == (unsigned)iValue) { if (iControl == 'flsh') { NetPrintf(("prototunnel: [%p][%04d] explicitly flushing tunnel 0x%08x\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pTunnel->uVirtualAddr)); _ProtoTunnelBufferSend(pProtoTunnel, pTunnel, uCurTick); } if (iControl == 'rprt') { NetPrintf(("prototunnel: [%p][%04d] updating remote port for tunnel 0x%08x to %d\n", pProtoTunnel, (pTunnel - pProtoTunnel->Tunnels), pTunnel->uVirtualAddr, iValue2)); pTunnel->Info.uRemotePort = (unsigned)iValue2; } break; } } // release exclusive access to tunnel list NetCritLeave(&pProtoTunnel->TunnelsCritR); NetCritLeave(&pProtoTunnel->TunnelsCritS); // if we didn't find the tunnel if (iTunnel == pProtoTunnel->iMaxTunnels) { NetPrintf(("prototunnel: [%p] unable to find tunnel 0x%08x for '%c%c%c%c' operation\n", pProtoTunnel, iValue, (uint8_t)(iControl>>24), (uint8_t)(iControl>>16), (uint8_t)(iControl>>8), (uint8_t)iControl)); return(-1); } return(0); } if (iControl == 'hmac') { int32_t iHashSize; // validate hmac type if ((iValue <= CRYPTHASH_NULL) || (iValue >= CRYPTHASH_NUMHASHES)) { NetPrintf(("prototunnel: [%p] ignoring attempt to set invalid hmactype %d\n", pProtoTunnel, iValue)); return(-1); } // validate hmac size iHashSize = CryptHashGetSize((CryptHashTypeE)iValue); if (iValue2 > iHashSize) { NetPrintf(("prototunnel: [%p] hmacsize %d is larger than max hmactype size %d; truncating\n", pProtoTunnel, iValue2, iHashSize)); iValue2 = iHashSize; } if (iValue2 > PROTOTUNNEL_HMAC_MAXSIZE) { NetPrintf(("prototunnel: [%p] hmacsize %d is too large; truncating\n", pProtoTunnel, iValue2)); iValue2 = PROTOTUNNEL_HMAC_MAXSIZE; } NetPrintf(("prototunnel: [%p] setting hmactype=%d and hmacsize=%d\n", pProtoTunnel, iValue, iValue2)); pProtoTunnel->uHmacType = (uint8_t)iValue; pProtoTunnel->uHmacSize = (uint8_t)iValue2; return(0); } if (iControl == 'idle') { pProtoTunnel->iIdleCbRate = iValue; SocketCallback(pProtoTunnel->pSocket, CALLB_RECV, pProtoTunnel->iIdleCbRate, pProtoTunnel, &_ProtoTunnelRecvCallback); return(0); } if (iControl == 'rand') { NetPrintf(("prototunnel: setting retry random port on bind failure to %s\n", (uint8_t)iValue ? "TRUE" : "FALSE")); _ProtoTunnel_bRetryRandomOnFailure = (uint8_t)iValue; return(0); } if (iControl == 'rate') { pProtoTunnel->uFlushRate = iValue; return(0); } if (iControl == 'rmax') { pProtoTunnel->iMaxRecv = iValue; return(0); } if (iControl == 'rrcb') { NetPrintf(("prototunnel: [%p] 'rrcb' selector used to change raw recv callback from %p to %p\n", pProtoTunnel, pProtoTunnel->pRawRecvCallback, pValue)); pProtoTunnel->pRawRecvCallback = (RawRecvCallbackT *)pValue; return(0); } if (iControl == 'rrud') { NetPrintf(("prototunnel: [%p] 'rrud' selector used to change raw recv callback user data from %p to %p\n", pProtoTunnel, pProtoTunnel->pRawRecvUserData, pValue)); pProtoTunnel->pRawRecvUserData = (void *)pValue; return(0); } if (iControl == 'sock') { NetPrintf(("prototunnel: [%p] replacing tunnel socket\n", pProtoTunnel)); // acquire exclusive access to tunnel list NetCritEnter(&pProtoTunnel->TunnelsCritS); NetCritEnter(&pProtoTunnel->TunnelsCritR); // close current tunnel socket if (pProtoTunnel->pSocket != NULL) { SocketClose(pProtoTunnel->pSocket); pProtoTunnel->pSocket = NULL; } // write in the new socket and configure it pProtoTunnel->pSocket = (SocketT *)pValue; _ProtoTunnelSocketConfig(pProtoTunnel, pProtoTunnel->pSocket); // release exclusive access to tunnel list NetCritLeave(&pProtoTunnel->TunnelsCritR); NetCritLeave(&pProtoTunnel->TunnelsCritS); return(0); } if (iControl == 'spam') { pProtoTunnel->iVerbosity = iValue; return(0); } if (iControl == 'tcid') { int32_t iTunnel = _ProtoTunnelIndexFromId(pProtoTunnel, (uint32_t)iValue); if (iTunnel != -1) { NetPrintf(("prototunnel: [%p][%04d] changing local client id from 0x%08x to 0x%08x\n", pProtoTunnel, iTunnel, pProtoTunnel->Tunnels[iTunnel].uLocalClientId, iValue2)); pProtoTunnel->Tunnels[iTunnel].uLocalClientId = (uint32_t)iValue2; return(0); } else { NetPrintf(("prototunnel: [%p] 'tcid' control selector called with unknown tunnelid=0x%08x\n", pProtoTunnel, iValue)); return(-1); } } if (iControl == 'vers') { if (iValue < PROTOTUNNEL_VERSION_MIN) { NetPrintf(("prototunnel [%p] ignoring attempt to set invalid version %d.%d\n", pProtoTunnel, iValue>>8, iValue&0xff)); return(-1); } NetPrintf(("prototunnel: [%p] setting version to %d.%d\n", pProtoTunnel, iValue>>8, iValue&0xff)); pProtoTunnel->uVersion = (uint16_t)iValue; return(0); } // pass-through to SocketControl() return(SocketControl(pProtoTunnel->pSocket, iControl, iValue, NULL, NULL)); } /*F********************************************************************************/ /*! \Function ProtoTunnelUpdate \Description Update the module \Input *pProtoTunnel - pointer to module state \Version 12/02/2005 (jbrookes) */ /********************************************************************************F*/ void ProtoTunnelUpdate(ProtoTunnelRefT *pProtoTunnel) { uint32_t uCurTick = NetTick(); int32_t iTunnel; // time to flush? // acquire exclusive access to tunnel list NetCritEnter(&pProtoTunnel->TunnelsCritS); NetCritEnter(&pProtoTunnel->TunnelsCritR); // flush all tunnels // check individual flush timers, which are reset each send for (iTunnel = 0; iTunnel < pProtoTunnel->iMaxTunnels; iTunnel++) { ProtoTunnelT *pTunnel = &(pProtoTunnel->Tunnels[iTunnel]); if (pTunnel->uVirtualAddr && (NetTickDiff(uCurTick, pTunnel->uLastTunnelSend) > (signed)pProtoTunnel->uFlushRate)) { _ProtoTunnelBufferSend(pProtoTunnel, pTunnel, uCurTick); } } // release exclusive access to tunnel list NetCritLeave(&pProtoTunnel->TunnelsCritR); NetCritLeave(&pProtoTunnel->TunnelsCritS); } /*F*************************************************************************************/ /*! \Function ProtoTunnelRawSendto \Description Send data to a remote host over the prototunnel socket. The destination address is supplied along with the data. Important: remote host shall not be expecting tunneled data because that function bypasses all the tunneling logic on the sending side. \Input *pProtoTunnel - pointer to module state \Input *pBuf - the data to be sent \Input iLen - size of data \Input *pTo - the address to send to \Input iToLen - length of address \Output int32_t - number of bytes sent or standard network error code (SOCKERR_xxx) \Version 07/12/2011 (mclouatre) */ /************************************************************************************F*/ int32_t ProtoTunnelRawSendto(ProtoTunnelRefT *pProtoTunnel, const char *pBuf, int32_t iLen, const struct sockaddr *pTo, int32_t iToLen) { return(_ProtoTunnelSocketSendto(pProtoTunnel, pBuf, iLen, 0, pTo, iToLen)); }