Sending data over TCP should work now. Receiving is still MIA.
git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@19007 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -10,14 +10,20 @@
|
|||||||
#define TCP_INITIAL_RTT 120000000LL
|
#define TCP_INITIAL_RTT 120000000LL
|
||||||
|
|
||||||
// Estimate for Maximum segment lifetime in the internet
|
// Estimate for Maximum segment lifetime in the internet
|
||||||
#define TCP_MAX_SEGMENT_LIFETIME TCP_INITIAL_RTT
|
#define TCP_MAX_SEGMENT_LIFETIME (2 * TCP_INITIAL_RTT)
|
||||||
|
|
||||||
// keep maximum buffer sizes < max net_buffer size for now
|
// keep maximum buffer sizes < max net_buffer size for now
|
||||||
#define TCP_MAX_SEND_BUF 1024
|
#define TCP_MAX_SEND_BUF 1024
|
||||||
#define TCP_MAX_RECV_BUF TCP_MAX_SEND_BUF
|
#define TCP_MAX_RECV_BUF TCP_MAX_SEND_BUF
|
||||||
|
|
||||||
#define TCP_IS_GOOD_ACK(x) (fLastByteAckd > fNextByteToSend ? ((x) >= fLastByteAckd || (x) <= fNextByteToSend) : ((x) >= fLastByteAckd && (x) <= fNextByteToSend))
|
#define TCP_IS_GOOD_ACK(x) (fLastByteAckd > fNextByteToSend ? \
|
||||||
#define TCP_IS_GOOD_SEQ(x,y) (fNextByteToRead < fNextByteToRead + TCP_MAX_RECV_BUF ? (x) >= fNextByteToRead && (x) + (y) <= fNextByteToRead + TCP_MAX_RECV_BUF : ((x) >= fNextByteToRead || (x) <= fNextByteToRead + TCP_MAX_RECV_BUF) && ((x) + (y) >= fNextByteToRead || (x) + (y) <= fNextByteToRead + TCP_MAX_RECV_BUF))
|
((x) >= fLastByteAckd || (x) <= fNextByteToSend) : \
|
||||||
|
((x) >= fLastByteAckd && (x) <= fNextByteToSend))
|
||||||
|
|
||||||
|
#define TCP_IS_GOOD_SEQ(x,y) (fNextByteToRead < fNextByteToRead + TCP_MAX_RECV_BUF ? \
|
||||||
|
(x) >= fNextByteToRead && (x) + (y) <= fNextByteToRead + TCP_MAX_RECV_BUF : \
|
||||||
|
((x) >= fNextByteToRead || (x) <= fNextByteToRead + TCP_MAX_RECV_BUF) && \
|
||||||
|
((x) + (y) >= fNextByteToRead || (x) + (y) <= fNextByteToRead + TCP_MAX_RECV_BUF))
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const sockaddr *local;
|
const sockaddr *local;
|
||||||
@@ -41,9 +47,9 @@ public:
|
|||||||
status_t Shutdown(int direction);
|
status_t Shutdown(int direction);
|
||||||
status_t SendData(net_buffer *buffer);
|
status_t SendData(net_buffer *buffer);
|
||||||
status_t SendRoutedData(net_route *route, net_buffer *buffer);
|
status_t SendRoutedData(net_route *route, net_buffer *buffer);
|
||||||
status_t SendAvailable();
|
size_t SendAvailable();
|
||||||
status_t ReadData(size_t numBytes, uint32 flags, net_buffer **_buffer);
|
status_t ReadData(size_t numBytes, uint32 flags, net_buffer **_buffer);
|
||||||
status_t ReadAvailable();
|
size_t ReadAvailable();
|
||||||
|
|
||||||
status_t ReceiveData(net_buffer *buffer);
|
status_t ReceiveData(net_buffer *buffer);
|
||||||
|
|
||||||
@@ -77,12 +83,10 @@ private:
|
|||||||
uint32 fNextByteExpected;
|
uint32 fNextByteExpected;
|
||||||
uint32 fLastByteReceived;
|
uint32 fLastByteReceived;
|
||||||
|
|
||||||
uint32 fEffectiveWindow;
|
|
||||||
|
|
||||||
bigtime_t fAvgRTT;
|
bigtime_t fAvgRTT;
|
||||||
|
|
||||||
TCPSegment *fSent;
|
net_buffer *fSendBuffer;
|
||||||
TCPSegment *fReceived;
|
net_buffer *fReceiveBuffer;
|
||||||
|
|
||||||
TCPConnection *fHashLink;
|
TCPConnection *fHashLink;
|
||||||
tcp_state fState;
|
tcp_state fState;
|
||||||
@@ -115,12 +119,14 @@ TCPConnection::TCPSegment::~TCPSegment()
|
|||||||
TCPConnection::TCPConnection(net_socket *socket)
|
TCPConnection::TCPConnection(net_socket *socket)
|
||||||
:
|
:
|
||||||
fLastByteAckd(0), //system_time()),
|
fLastByteAckd(0), //system_time()),
|
||||||
fNextByteToSend(fLastByteAckd), //fLastByteAckd),
|
fNextByteToSend(fLastByteAckd),
|
||||||
fNextByteToWrite(fLastByteAckd + 1), //fLastByteAckd),
|
fNextByteToWrite(fLastByteAckd + 1),
|
||||||
fNextByteToRead(0),
|
fNextByteToRead(0),
|
||||||
fNextByteExpected(0),
|
fNextByteExpected(0),
|
||||||
fLastByteReceived(0),
|
fLastByteReceived(0),
|
||||||
fAvgRTT(TCP_INITIAL_RTT),
|
fAvgRTT(TCP_INITIAL_RTT),
|
||||||
|
fSendBuffer(NULL),
|
||||||
|
fReceiveBuffer(NULL),
|
||||||
fState(CLOSED),
|
fState(CLOSED),
|
||||||
fError(B_OK),
|
fError(B_OK),
|
||||||
fRoute(NULL)
|
fRoute(NULL)
|
||||||
@@ -216,6 +222,25 @@ TCPConnection::Connect(const struct sockaddr *address)
|
|||||||
}
|
}
|
||||||
TRACE(("TCP: Connect(): in state %d\n", fState));
|
TRACE(("TCP: Connect(): in state %d\n", fState));
|
||||||
|
|
||||||
|
// get a net_route if there isn't one
|
||||||
|
if (fRoute == NULL) {
|
||||||
|
fRoute = sDatalinkModule->get_route(sDomain, (sockaddr *)address);
|
||||||
|
TRACE(("TCP: Connect(): Using Route %p\n", fRoute));
|
||||||
|
if (fRoute == NULL) {
|
||||||
|
benaphore_unlock(&sTCPLock);
|
||||||
|
return ENETUNREACH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// need to associate this connection with a real address, not INADDR_ANY
|
||||||
|
if (sAddressModule->is_empty_address((sockaddr *)&socket->address, false)) {
|
||||||
|
TRACE(("TCP: Connect(): Local Address is INADDR_ANY\n"));
|
||||||
|
sAddressModule->set_to((sockaddr *)&socket->address, (sockaddr *)fRoute->interface->address);
|
||||||
|
// since most stacks terminate connections from port 0
|
||||||
|
// use port 40000 for now. This should be moved to Bind(), and Bind() called before Connect().
|
||||||
|
sAddressModule->set_port((sockaddr *)&socket->address, htons(40000));
|
||||||
|
}
|
||||||
|
|
||||||
// make sure connection does not already exist
|
// make sure connection does not already exist
|
||||||
tcp_connection_key key;
|
tcp_connection_key key;
|
||||||
key.local = (sockaddr *)&socket->address;
|
key.local = (sockaddr *)&socket->address;
|
||||||
@@ -227,35 +252,6 @@ TCPConnection::Connect(const struct sockaddr *address)
|
|||||||
TRACE(("TCP: Connect(): connecting...\n"));
|
TRACE(("TCP: Connect(): connecting...\n"));
|
||||||
|
|
||||||
status_t status;
|
status_t status;
|
||||||
// update the hash with the new key values
|
|
||||||
key.peer = (sockaddr *)&socket->peer;
|
|
||||||
if (hash_lookup(sTCPHash, &key) != NULL) {
|
|
||||||
status = hash_remove(sTCPHash, (void *)this);
|
|
||||||
if (status != B_OK) {
|
|
||||||
TRACE(("TCP: Connect(): Hash Error: %s\n", strerror(status)));
|
|
||||||
benaphore_unlock(&sTCPLock);
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TRACE(("TCP: Connect(): Connection not in hash yet!\n"));
|
|
||||||
// get a net_route if there isn't one
|
|
||||||
if (fRoute == NULL) {
|
|
||||||
fRoute = sDatalinkModule->get_route(sDomain, (sockaddr *)address);
|
|
||||||
TRACE(("TCP: Connect(): Using Route %p\n", fRoute));
|
|
||||||
if (fRoute == NULL) {
|
|
||||||
return ENETUNREACH;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sAddressModule->is_empty_address((sockaddr *)&socket->address, false)) {
|
|
||||||
TRACE(("TCP: Connect(): Local Address is INADDR_ANY\n"));
|
|
||||||
sAddressModule->set_to((sockaddr *)&socket->address, (sockaddr *)fRoute->interface->address);
|
|
||||||
// since most stacks terminate connections from port 0
|
|
||||||
// use port 40000 for now. This should be moved to Bind().
|
|
||||||
sAddressModule->set_port((sockaddr *)&socket->address, htons(40000));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sAddressModule->set_to((sockaddr *)&socket->peer, address);
|
sAddressModule->set_to((sockaddr *)&socket->peer, address);
|
||||||
status = hash_insert(sTCPHash, (void *)this);
|
status = hash_insert(sTCPHash, (void *)this);
|
||||||
if (status != B_OK) {
|
if (status != B_OK) {
|
||||||
@@ -346,6 +342,7 @@ status_t
|
|||||||
TCPConnection::Listen(int count)
|
TCPConnection::Listen(int count)
|
||||||
{
|
{
|
||||||
TRACE(("TCP:%p.Listen()\n", this));
|
TRACE(("TCP:%p.Listen()\n", this));
|
||||||
|
BenaphoreLocker lock(&fLock);
|
||||||
if (fState != CLOSED)
|
if (fState != CLOSED)
|
||||||
return B_ERROR;
|
return B_ERROR;
|
||||||
fState = LISTEN;
|
fState = LISTEN;
|
||||||
@@ -368,7 +365,19 @@ status_t
|
|||||||
TCPConnection::SendData(net_buffer *buffer)
|
TCPConnection::SendData(net_buffer *buffer)
|
||||||
{
|
{
|
||||||
TRACE(("TCP:%p.SendData()\n", this));
|
TRACE(("TCP:%p.SendData()\n", this));
|
||||||
return B_ERROR;
|
size_t bufSize = buffer->size;
|
||||||
|
BenaphoreLocker lock(&fLock);
|
||||||
|
if (fSendBuffer == NULL) {
|
||||||
|
fSendBuffer = buffer;
|
||||||
|
fNextByteToWrite += bufSize;
|
||||||
|
return Send(TCP_FLG_ACK, false);
|
||||||
|
} else {
|
||||||
|
status_t status = sBufferModule->merge(fSendBuffer, buffer, true);
|
||||||
|
if (status != B_OK)
|
||||||
|
return status;
|
||||||
|
fNextByteToWrite += bufSize;
|
||||||
|
return Send(TCP_FLG_ACK, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -376,16 +385,23 @@ status_t
|
|||||||
TCPConnection::SendRoutedData(net_route *route, net_buffer *buffer)
|
TCPConnection::SendRoutedData(net_route *route, net_buffer *buffer)
|
||||||
{
|
{
|
||||||
TRACE(("TCP:%p.SendRoutedData()\n", this));
|
TRACE(("TCP:%p.SendRoutedData()\n", this));
|
||||||
fRoute = route;
|
{
|
||||||
|
BenaphoreLocker lock(&fLock);
|
||||||
|
fRoute = route;
|
||||||
|
}
|
||||||
return SendData(buffer);
|
return SendData(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
size_t
|
||||||
TCPConnection::SendAvailable()
|
TCPConnection::SendAvailable()
|
||||||
{
|
{
|
||||||
TRACE(("TCP:%p.SendAvailable()\n", this));
|
TRACE(("TCP:%p.SendAvailable()\n", this));
|
||||||
return B_ERROR;
|
BenaphoreLocker lock(&fLock);
|
||||||
|
if (fSendBuffer != NULL)
|
||||||
|
return TCP_MAX_SEND_BUF - fSendBuffer->size;
|
||||||
|
else
|
||||||
|
return TCP_MAX_SEND_BUF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -397,11 +413,15 @@ TCPConnection::ReadData(size_t numBytes, uint32 flags, net_buffer **_buffer)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
size_t
|
||||||
TCPConnection::ReadAvailable()
|
TCPConnection::ReadAvailable()
|
||||||
{
|
{
|
||||||
TRACE(("TCP:%p.ReadAvailable()\n", this));
|
TRACE(("TCP:%p.ReadAvailable()\n", this));
|
||||||
return B_ERROR;
|
BenaphoreLocker lock(&fLock);
|
||||||
|
if (fReceiveBuffer != NULL)
|
||||||
|
return fReceiveBuffer->size;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -424,12 +444,15 @@ TCPConnection::ReceiveData(net_buffer *buffer)
|
|||||||
uint32 byteRcvd = ntohl(header.sequence_num);
|
uint32 byteRcvd = ntohl(header.sequence_num);
|
||||||
uint32 payloadLength = buffer->size - ((uint32)header.header_length << 2);
|
uint32 payloadLength = buffer->size - ((uint32)header.header_length << 2);
|
||||||
|
|
||||||
TRACE(("TCP: Receive(): Connection in state %d received packet %p with flags %X!\n", fState, buffer, header.flags));
|
TRACE(("TCP: ReceiveData(): Connection in state %d received packet %p with flags %X!\n", fState, buffer, header.flags));
|
||||||
switch (fState) {
|
switch (fState) {
|
||||||
case CLOSED:
|
case CLOSED:
|
||||||
case TIME_WAIT:
|
case TIME_WAIT:
|
||||||
// shouldn't happen. send RST
|
sBufferModule->free(buffer);
|
||||||
Reset(byteRcvd + payloadLength, 0);
|
if (header.flags & TCP_FLG_ACK)
|
||||||
|
return Reset(byteAckd, 0);
|
||||||
|
else
|
||||||
|
return Reset(0, byteRcvd + payloadLength);
|
||||||
break;
|
break;
|
||||||
case LISTEN:
|
case LISTEN:
|
||||||
// if packet is SYN, spawn new TCPConnection in SYN_RCVD state
|
// if packet is SYN, spawn new TCPConnection in SYN_RCVD state
|
||||||
@@ -439,38 +462,40 @@ TCPConnection::ReceiveData(net_buffer *buffer)
|
|||||||
// buffer.
|
// buffer.
|
||||||
// Otherwise, RST+ACK is sent.
|
// Otherwise, RST+ACK is sent.
|
||||||
// The current TCPConnection always remains in LISTEN state.
|
// The current TCPConnection always remains in LISTEN state.
|
||||||
|
return B_ERROR;
|
||||||
break;
|
break;
|
||||||
case SYN_SENT:
|
case SYN_SENT:
|
||||||
case SYN_RCVD:
|
|
||||||
// if packet is SYN+ACK, send ACK & enter ESTABLISHED state.
|
|
||||||
// if packet is SYN, send ACK & enter SYN_RCVD state.
|
|
||||||
if (header.flags & TCP_FLG_RST) {
|
if (header.flags & TCP_FLG_RST) {
|
||||||
fError = ECONNREFUSED;
|
fError = ECONNREFUSED;
|
||||||
fState = CLOSED;
|
fState = CLOSED;
|
||||||
return B_ERROR;
|
return B_ERROR;
|
||||||
}
|
}
|
||||||
if (TCP_IS_GOOD_ACK(byteAckd)) {
|
if (header.flags & TCP_FLG_ACK && !TCP_IS_GOOD_ACK(byteAckd))
|
||||||
if (header.flags & TCP_FLG_SYN) {
|
return Reset(byteAckd, 0);
|
||||||
fNextByteToRead =
|
if (header.flags & TCP_FLG_SYN) {
|
||||||
fNextByteExpected =
|
fNextByteToRead = fNextByteExpected = ntohl(header.sequence_num) + 1;
|
||||||
fLastByteReceived = ntohl(header.sequence_num) + 1;
|
flags |= TCP_FLG_ACK;
|
||||||
flags |= TCP_FLG_ACK;
|
fLastByteAckd = byteAckd;
|
||||||
if (header.flags & TCP_FLG_ACK) {
|
// cancel resend of this segment
|
||||||
fLastByteAckd = byteAckd;
|
if (header.flags & TCP_FLG_ACK)
|
||||||
// cancel resend of this segment
|
nextState = ESTABLISHED;
|
||||||
nextState = ESTABLISHED;
|
else {
|
||||||
} else {
|
nextState = SYN_RCVD;
|
||||||
flags |= TCP_FLG_SYN;
|
flags |= TCP_FLG_SYN;
|
||||||
nextState = SYN_RCVD;
|
|
||||||
}
|
|
||||||
status = Send(flags, false);
|
|
||||||
if (status == B_OK)
|
|
||||||
fState = nextState;
|
|
||||||
}
|
}
|
||||||
} else {
|
status = Send(flags, false);
|
||||||
Reset(byteRcvd + payloadLength, 0);
|
if (status == B_OK)
|
||||||
|
fState = nextState;
|
||||||
|
else
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case SYN_RCVD:
|
||||||
|
if (header.flags & TCP_FLG_ACK && TCP_IS_GOOD_ACK(byteAckd))
|
||||||
|
fState = ESTABLISHED;
|
||||||
|
else
|
||||||
|
Reset(byteAckd, 0);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// In a synchronized state.
|
// In a synchronized state.
|
||||||
// first check that the received sequence number is good
|
// first check that the received sequence number is good
|
||||||
@@ -539,7 +564,7 @@ TCPConnection::ReceiveData(net_buffer *buffer)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
TRACE(("TCP %p.Receive():Entering state %d\n", this, fState));
|
TRACE(("TCP %p.ReceiveData():Entering state %d\n", this, fState));
|
||||||
return B_OK;
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,7 +577,8 @@ TCPConnection::Reset(uint32 sequenceNum, uint32 acknowledgeNum)
|
|||||||
sAddressModule->set_to((sockaddr *)&reply_buf->source, (sockaddr *)&socket->address);
|
sAddressModule->set_to((sockaddr *)&reply_buf->source, (sockaddr *)&socket->address);
|
||||||
sAddressModule->set_to((sockaddr *)&reply_buf->destination, (sockaddr *)&socket->peer);
|
sAddressModule->set_to((sockaddr *)&reply_buf->destination, (sockaddr *)&socket->peer);
|
||||||
|
|
||||||
status_t status = tcp_segment(reply_buf, TCP_FLG_RST , sequenceNum, acknowledgeNum, 0);
|
uint16 flags = TCP_FLG_RST | acknowledgeNum == 0 ? 0 : TCP_FLG_ACK;
|
||||||
|
status_t status = tcp_segment(reply_buf, flags , sequenceNum, acknowledgeNum, 0);
|
||||||
if (status != B_OK) {
|
if (status != B_OK) {
|
||||||
sBufferModule->free(reply_buf);
|
sBufferModule->free(reply_buf);
|
||||||
return status;
|
return status;
|
||||||
@@ -590,13 +616,18 @@ TCPConnection::Send(uint16 flags, bool empty)
|
|||||||
TRACE(("TCP:%p.Send(%X,%s)\n", this, flags, empty ? "1" : "0"));
|
TRACE(("TCP:%p.Send(%X,%s)\n", this, flags, empty ? "1" : "0"));
|
||||||
|
|
||||||
net_buffer *buffer;
|
net_buffer *buffer;
|
||||||
if (1/*no data in send buffer*/ || empty) {
|
uint32 effectiveWindow = min_c(tcp_get_mtu(this, (sockaddr *)&socket->address), fNextByteToWrite - fNextByteToSend);
|
||||||
buffer = sBufferModule->create(512);
|
if (empty || effectiveWindow == 0 || fSendBuffer == NULL || fSendBuffer->size == 0) {
|
||||||
|
buffer = sBufferModule->create(256);
|
||||||
TRACE(("TCP: Sending Buffer %p\n", buffer));
|
TRACE(("TCP: Sending Buffer %p\n", buffer));
|
||||||
if (buffer == NULL)
|
if (buffer == NULL)
|
||||||
return ENOBUFS;
|
return ENOBUFS;
|
||||||
} else {
|
} else {
|
||||||
//TODO: create net_buffer from data in send buffer
|
buffer = fSendBuffer;
|
||||||
|
if (effectiveWindow == fSendBuffer->size)
|
||||||
|
fSendBuffer = NULL;
|
||||||
|
else
|
||||||
|
fSendBuffer = sBufferModule->split(fSendBuffer, effectiveWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
sAddressModule->set_to((sockaddr *)&buffer->source, (sockaddr *)&socket->address);
|
sAddressModule->set_to((sockaddr *)&buffer->source, (sockaddr *)&socket->address);
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ static benaphore sTCPLock;
|
|||||||
status_t
|
status_t
|
||||||
tcp_segment(net_buffer *buffer, uint16 flags, uint32 seq, uint32 ack, uint16 adv_win);
|
tcp_segment(net_buffer *buffer, uint16 flags, uint32 seq, uint32 ack, uint16 adv_win);
|
||||||
|
|
||||||
|
size_t
|
||||||
|
tcp_get_mtu(net_protocol *protocol, const struct sockaddr *address);
|
||||||
|
|
||||||
#include "tcp.h"
|
#include "tcp.h"
|
||||||
#include "TCPConnection.h"
|
#include "TCPConnection.h"
|
||||||
|
|
||||||
@@ -78,7 +81,7 @@ tcp_dump_hash(){
|
|||||||
hash_close(sTCPHash, &iterator, false);
|
hash_close(sTCPHash, &iterator, false);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
# define DUMP_TCP_HASH
|
# define DUMP_TCP_HASH 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@@ -189,7 +192,7 @@ tcp_shutdown(net_protocol *protocol, int direction)
|
|||||||
status_t
|
status_t
|
||||||
tcp_send_data(net_protocol *protocol, net_buffer *buffer)
|
tcp_send_data(net_protocol *protocol, net_buffer *buffer)
|
||||||
{
|
{
|
||||||
return protocol->next->module->send_data(protocol->next, buffer);
|
return ((TCPConnection *)protocol)->SendData(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -197,7 +200,7 @@ status_t
|
|||||||
tcp_send_routed_data(net_protocol *protocol, struct net_route *route,
|
tcp_send_routed_data(net_protocol *protocol, struct net_route *route,
|
||||||
net_buffer *buffer)
|
net_buffer *buffer)
|
||||||
{
|
{
|
||||||
return protocol->next->module->send_routed_data(protocol->next, route, buffer);
|
return ((TCPConnection *)protocol)->SendRoutedData(route, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user