Kawe Mazidjatari b3a68ed095 Add EABase, EAThread and DirtySDK to R5sdk
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
2024-04-05 18:29:03 +02:00

3315 lines
132 KiB
C

/*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 <string.h>
#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<<PROTOTUNNEL_CRYPTBITS));
CryptArc4Advance(&CryptRecvState, iRecvDiff<<PROTOTUNNEL_CRYPTBITS);
uRecvOffsetState = uRecvOffset;
// remember we synced the state
bSynced = TRUE;
}
// reset offset to count bytes added from this packet
uRecvOffset = 0;
// get ident
uIdent = PROTOTUNNEL_GetIdentFromPacket(pPacketData);
// does the packet include handshake data?
if (uIdent & PROTOTUNNEL_IDENT_HANDSHAKE)
{
ProtoTunnelHandshakeT Handshake;
uVersion = uIdent & ~PROTOTUNNEL_IDENT_HANDSHAKE;
NetPrintf(("prototunnel: [%p][%04d] handshake packet version %d.%d\n", pProtoTunnel, (pTunnel - pProtoTunnel->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));
}