NORM-mirror/norp/src/common/norp.cpp

2805 lines
120 KiB
C++
Executable File

#include "norp.h"
#include "protoNet.h"
#include "protoDebug.h"
// Note this "PortPool" was a simple helper class
// for a test where we needed to traverse a
// NAT / firewall. We may _later_ refine this
// as an optional feature where "norp" can be
// configured to use a specific set of port
// numbers for NORM traffic.
// Uncomment this to enable new NORM port handling to improve NAT compatibility
#define NEW_PORT
PortPool::PortPool(UINT16 basePort)
: base_port(basePort)
{
memset(port_array, 0, 50*sizeof(bool));
}
UINT16 PortPool::GrabPort()
{
for (UINT16 i = 0; i < 50; i++)
{
if (!port_array[i])
{
port_array[i] = true;
return (base_port + i);
}
}
return 0; // no port was available
} // end PortPool::GrabPort()
void PortPool::ReleasePort(UINT16 thePort)
{
int index = thePort - base_port;
if ((index < 50) && (index >=0))
port_array[index] = false;
} // end PortPool::ReleasePort()
NorpMsg::NorpMsg(UINT32* bufferPtr, unsigned int numBytes, bool initFromBuffer, bool freeOnDestruct)
: ProtoPkt(bufferPtr, numBytes, freeOnDestruct)
{
if (initFromBuffer) InitFromBuffer(numBytes);
}
NorpMsg::~NorpMsg()
{
}
const double NorpSession::NORP_RTT_MIN = 1.0e-03;
const double NorpSession::NORP_RTT_MAX = 5.0; // TBD - assess these values (make runtime configurable?)
const double NorpSession::NORP_RTT_DEFAULT = 1.0;//e-01; // 100 msec default
const double Norp::DEFAULT_TX_RATE = 1.0e+06;
const double Norp::DEFAULT_PERSIST_INTERVAL = 60.0; // times out unsuccessful NORM delivery attempt after successive ACK_FAILURE
NorpSession::NorpSession(Norp& theController, UINT16 sessionId, NormNodeId originatorId)
: controller(theController), is_preset(false), socks_state(SOCKS_IDLE),
socks_client_socket(ProtoSocket::TCP), socks_remote_socket(ProtoSocket::TCP),
udp_relay_socket(ProtoSocket::UDP),
client_pending(0), client_index(0), remote_pending(0), remote_index(0),
norm_enable(true), norp_tx_socket(ProtoSocket::UDP), norp_rtt_estimate(NORP_RTT_DEFAULT),
norm_session(NORM_SESSION_INVALID), norm_tx_stream(NORM_OBJECT_INVALID),
norm_rx_stream(NORM_OBJECT_INVALID), norm_rx_pending(false), norm_sender_heard(false),
persist_interval(Norp::DEFAULT_PERSIST_INTERVAL), persist_start_time(0, 0),
norm_rate_min(-1.0), norm_rate_max(-1.0),
norm_segment_size(0), norm_stream_buffer_max(0), norm_stream_buffer_count(0),
norm_stream_bytes_remain(0), norm_watermark_pending(false)
{
memset(&session_id, 0, sizeof(Moniker));
session_id.originator = originatorId;
session_id.identifier = sessionId;
socks_client_socket.SetNotifier(&controller.GetSocketNotifier());
//socks_client_socket.SetListener(this, &NorpSession::OnSocksClientEvent);
socks_remote_socket.SetNotifier(&controller.GetSocketNotifier());
socks_remote_socket.SetListener(this, &NorpSession::OnSocksRemoteEvent);
udp_relay_socket.SetNotifier(&controller.GetSocketNotifier());
udp_relay_socket.SetListener(this, &NorpSession::OnUdpRelayEvent);
norp_tx_socket.SetUserData(this);
norp_tx_socket.SetNotifier(&controller.GetSocketNotifier());
norp_tx_socket.SetListener(&controller, &Norp::OnNorpSocketEvent);
norp_msg_timer.SetListener(this, &NorpSession::OnNorpMsgTimeout);
norp_msg_timer.SetInterval(controller.GetInitialRtt());
norp_msg_timer.SetRepeat(20);
norp_msg.AttachBuffer(norp_msg_buffer, NORP_BUFFER_SIZE, false);
close_timer.SetListener(this, &NorpSession::OnCloseTimeout);
close_timer.SetInterval(0.0);
close_timer.SetRepeat(0); // one-shot timer
}
NorpSession::~NorpSession()
{
socks_state = SOCKS_VOID;
Close();
}
void NorpSession::SetNormRateBounds(double rateMin, double rateMax)
{
norm_rate_min = rateMin;
norm_rate_max = rateMax;
if (NORM_SESSION_INVALID != norm_session)
{
NormSetTxRateBounds(norm_session, rateMin, rateMax);
if (Norp::NORM_FIXED == controller.GetNormCC())
{
// cumulative rate limit may override fixed session rate
// (i.e. to share pipe among multiple flows)
if ((rateMin >= 0.0) && (rateMin < controller.GetNormTxRate()))
NormSetTxRate(norm_session, rateMin);
else
NormSetTxRate(norm_session, controller.GetNormTxRate());
}
}
} // end NorpSession::SetNormRateBounds()
NormNodeId NorpSession::GetNodeId() const
{
return controller.GetNormNodeId();
} // end NorpSession::GetNodeId()
void NorpSession::ActivateTimer(ProtoTimer& theTimer)
{
controller.ActivateTimer(theTimer);
} // end NorpSession::ActivateTimer()
bool NorpSession::AcceptClientConnection(ProtoSocket& serverSocket, bool normEnable)
{
ASSERT(socks_client_socket.IsClosed());
if (!serverSocket.Accept(&socks_client_socket))
{
PLOG(PL_ERROR, "NorpSession::AcceptClientConnection() error: server socket accept failure\n");
return false;
}
// Assume the same notifier (for now)
socks_client_socket.SetListener(this, &NorpSession::OnSocksClientEvent);
norm_enable = normEnable;
// Init our socks_state
socks_state = SOCKS_GET_AUTH_REQ;
// Init client/remote buffer indices
client_pending = client_index = 0;
remote_pending = remote_index = 0;
return true;
} // end NorpSession::AcceptClientConnection()
bool NorpSession::AcceptPresetClientConnection(NorpPreset& preset, bool normEnable)
{
ASSERT(socks_client_socket.IsClosed());
if (!preset.AccessServerSocket().Accept(&socks_client_socket))
{
PLOG(PL_ERROR, "NorpSession::AcceptPresetClientConnection() error: server socket accept failure\n");
return false;
}
is_preset = true;
// Assume the same notifier (for now)
socks_client_socket.SetListener(this, &NorpSession::OnSocksClientEvent);
norm_enable = normEnable;
// Jump-start our socks_state to
socks_state = SOCKS_GET_REQUEST;
// Init client/remote buffer indices
client_pending = client_index = 0;
remote_pending = remote_index = 0;
if (norm_enable)
{
// Make and send SOCKS request to remote peer NORP server (peer will TCP connect to endpoint and reply via NORM)
ProtoPktSOCKS::Request request(client_buffer, 32, false);
request.SetVersion(5);
request.SetCommand(ProtoPktSOCKS::Request::CONNECT);
request.SetAddress(preset.GetDstAddr());
return PutRemoteRequest(request, preset.GetNorpAddr());
}
else
{
return ConnectToRemote(preset.GetDstAddr()); // do direct TCP connect to remote endpoint
}
return true;
} // end NorpSession::AcceptPresetSession()
void NorpSession::Shutdown()
{
// Initiate hard session shutdown, informing remote
if (IsRemoteSession())
{
norp_msg.Init();
if (IsRemoteOriginator())
norp_msg.SetType(NorpMsg::ORIG_END);
else
norp_msg.SetType(NorpMsg::CORR_END);
norp_msg.SetSessionId(GetSessionId());
norp_msg.SetNodeId(controller.GetNormNodeId());
// The remaining fields don't matter, so we don't send them!
norp_remote_addr.SetPort(controller.GetNorpPort());
if (!controller.SendMessage(norp_msg, norp_remote_addr))
PLOG(PL_ERROR, "NorpSession::Shutdown() warning: NORP message transmission failed (will try again)!\n");
// Set timer to repeat until ACK_END is received
if (norp_msg_timer.IsActive()) norp_msg_timer.Deactivate();
norp_msg_timer.SetInterval(2.0*norp_rtt_estimate); // use most recent RTT estimate
ActivateTimer(norp_msg_timer);
// Here we use ProtoSocket::Shutdown() (sockets get a DISCONNECT event upon shutdown completion)
// TBD - we may need to do something with input/output notification here?
if (socks_client_socket.IsOpen()) socks_client_socket.Close();
if (socks_remote_socket.IsOpen()) socks_remote_socket.Close();
socks_state = SOCKS_SHUTDOWN;
}
else
{
// No remote, so just do a hard Close()
Close();
}
} // end NorpSession::Shutdown()
// This returns "true" when the conditions of shutdown completion are met
bool NorpSession::ShutdownComplete() const
{
if (socks_remote_socket.IsOpen()) return false;
if (socks_client_socket.IsOpen()) return false;
if (NORM_OBJECT_INVALID != norm_tx_stream) return false;
if (NORM_OBJECT_INVALID != norm_rx_stream) return false;
return true;
} // end NorpSession::ShutdownComplete()
void NorpSession::Close()
{
if (NORM_SESSION_INVALID != norm_session)
{
NormDestroySession(norm_session);
norm_session = NORM_SESSION_INVALID;
}
norp_tx_socket.Close();
socks_client_socket.Close();
socks_remote_socket.Close();
udp_relay_socket.Close();
client_pending = client_index = 0;
remote_pending = remote_index = 0;
if (SOCKS_VOID != socks_state)
{
// We make the state void here because controller deletes us!
socks_state = SOCKS_VOID;
controller.OnSessionClose(*this);
}
} // end NorpSession::Close()
bool NorpSession::OnCloseTimeout(ProtoTimer& /*theTimer*/)
{
close_timer.Deactivate();
controller.OnSessionClose(*this);
return false; // because we've been deleted
} // end NorpSession::OnCloseTimeout()
void NorpSession::OnSocksClientEvent(ProtoSocket& theSocket,
ProtoSocket::Event theEvent)
{
PLOG(PL_DETAIL, "NorpSession::OnSocksClientEvent(");
switch (theEvent)
{
case ProtoSocket::INVALID_EVENT:
PLOG(PL_DETAIL, "INVALID_EVENT) ...\n");
break;
case ProtoSocket::CONNECT:
PLOG(PL_DETAIL, "CONNECT) ...\n");
break;
case ProtoSocket::ACCEPT:
PLOG(PL_DETAIL, "ACCEPT) ...\n"); // should never happen (was accepted on server socket) unless remote BIND
break;
case ProtoSocket::SEND:
PLOG(PL_DETAIL, "SEND) ...\n");
switch (socks_state)
{
case SOCKS_VOID:
case SOCKS_IDLE:
ASSERT(0);
break;
case SOCKS_GET_AUTH_REQ:
ASSERT(0);
break;
case SOCKS_PUT_AUTH_REP:
if (!PutClientAuthReply())
{
PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: PutClientAuthReply() failure!\n");
Close();
return;
}
break;
case SOCKS_GET_REQUEST:
ASSERT(0);
break;
case SOCKS_PUT_REQUEST:
ASSERT(0);
break;
case SOCKS_CONNECTING:
ASSERT(0);
break;
case SOCKS_GET_REPLY:
ASSERT(0);
break;
case SOCKS_PUT_REPLY:
if (!PutClientReply())
{
PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: PutClientReply() failure!\n");
Shutdown();
return;
}
break;
case SOCKS_CONNECTED:
if (!PutClientData())
{
PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: PutClientData() failure!\n");
Shutdown();
return;
}
break;
case SOCKS_SHUTDOWN:
ASSERT(0);
break;
} // end switch(socks_state)
break;
case ProtoSocket::RECV:
{
PLOG(PL_DETAIL, "RECV) ...\n");
switch (socks_state)
{
case SOCKS_VOID:
case SOCKS_IDLE: // initial, inactive state (socks_client_socket is closed)
ASSERT(0);
break;
case SOCKS_GET_AUTH_REQ: // first active state, getting auth request bytes from client
if (!GetClientAuthRequest())
{
PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: GetClientAuthRequest() failure!\n");
Close();
return;
}
break;
case SOCKS_GET_REQUEST: // next, accumulate SOCKS request bytes from client
if (!GetClientRequest())
{
PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: GetClientRequest() failure!\n");
Close();
return;
}
break;
case SOCKS_PUT_AUTH_REP:
case SOCKS_PUT_REQUEST:
case SOCKS_CONNECTING:
case SOCKS_PUT_REPLY:
case SOCKS_GET_REPLY:
case SOCKS_SHUTDOWN:
{
unsigned int numBytes = SOCKS_BUFFER_SIZE;
// do a recv() - probably means client disconnected (prematurely)
if (theSocket.Recv((char*)client_buffer, numBytes))
{
if ((SOCKS_SHUTDOWN != socks_state) && (0 != numBytes))
PLOG(PL_WARN, "NorpSession::OnSocksClientEvent() warning: unexpectedly received %u bytes from SOCKS client!\n", numBytes);
}
else
{
PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: unexpected receive error from SOCKS client!\n");
}
break;
}
case SOCKS_CONNECTED: // finally, reading/writing data from/to client
if (0 == client_pending) // we need this because we sometimes get extra notifications with old "protolib"
{
if (!GetClientData())
{
PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: GetClientData() failure!\n");
Shutdown();
}
}
break;
} // end switch(socks_state)
break;
}
case ProtoSocket::DISCONNECT:
PLOG(PL_DETAIL, "DISCONNECT) ...\n");
switch (socks_state)
{
case SOCKS_VOID:
case SOCKS_IDLE:
ASSERT(0); // will never happen
break;
case SOCKS_GET_AUTH_REQ:
case SOCKS_PUT_AUTH_REP:
case SOCKS_GET_REQUEST:
case SOCKS_PUT_REQUEST:
case SOCKS_CONNECTING:
case SOCKS_PUT_REPLY:
case SOCKS_GET_REPLY:
PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: premature SOCKS client disconnect!\n");
Shutdown(); // unexpected, premature client disconnect
break;
case SOCKS_CONNECTED:
// Client finished sending (and receiving) data
socks_state = SOCKS_SHUTDOWN;
if (0 == client_pending)
PutRemoteData(); // to shut down socks_remote_socket or norm_tx_stream
case SOCKS_SHUTDOWN:
theSocket.Close();
if (ShutdownComplete())
Close();
// else more shutdown actions pending
break;
}
break;
case ProtoSocket::EXCEPTION:
PLOG(PL_DETAIL, "EXCEPTION) ...\n");
ASSERT(0);
break;
case ProtoSocket::ERROR_:
PLOG(PL_DETAIL, "ERROR_) ...\n");
break;
} // end switch (theEvent)
} // end NorpSession::OnSocksClientEvent()
bool NorpSession::GetClientAuthRequest()
{
// This method incrementally receives/processes a SOCKS AuthRequest
// and updates our "socks_state" and buffer indices accordlingly
// ("numBytes" is set to how many needed for next socket read)
ASSERT(SOCKS_GET_AUTH_REQ == socks_state);
for(;;) // loops until auth request is read or no more input is ready
{
switch (client_index)
{
case 0: // need to read VERSION byte first
{
client_pending = 1;
break;
}
case 1: // validate VERSION and read NMETHODS byte
{
ProtoPktSOCKS::AuthRequest authReq(client_buffer, 1);
if (5 != authReq.GetVersion())
{
PLOG(PL_ERROR, "NorpSession::GetClientAuthRequest() received incompatible version %d request!\n", authReq.GetVersion());
return false;
}
client_pending = 2;
break;
}
case 2: // Check NMETHODS byte to see how much to read.
{
ProtoPktSOCKS::AuthRequest authReq(client_buffer, 2);
UINT8 numBytes = authReq.GetMethodCount();
if (0 == numBytes)
{
PLOG(PL_ERROR, "NorpSession::GetClientAuthRequest() received AuthRequest with zero methods!\n");
return false;
}
client_pending += numBytes;
break;
}
default:
{
if (client_index == client_pending)
{
PLOG(PL_DETAIL, "NorpSession::GetClientAuthRequest() session %u received SOCKS AuthRequest from client %s/%hu ...\n",
GetSessionId(), socks_client_socket.GetDestination().GetHostString(), socks_client_socket.GetDestination().GetPort());
// We have received the full SOCKS AuthRequest
// Find best authentication "method" (currently only AUTH_NONE)
ProtoPktSOCKS::AuthType authType = ProtoPktSOCKS::AUTH_INVALID;
ProtoPktSOCKS::AuthRequest authReq(client_buffer, client_index);
for (UINT8 i = 0; i < authReq.GetMethodCount(); i++)
{
if (ProtoPktSOCKS::AUTH_NONE == authReq.GetMethod(i))
{
authType = ProtoPktSOCKS::AUTH_NONE;
break;
}
}
if (ProtoPktSOCKS::AUTH_NONE == authType)
{
// Build our SOCKS AuthReply into "remote_buffer" for transmission to client
socks_state = SOCKS_PUT_AUTH_REP;
ProtoPktSOCKS::AuthReply authReply(remote_buffer, SOCKS_BUFFER_SIZE, false);
authReply.SetVersion(5);
authReply.SetMethod(ProtoPktSOCKS::AUTH_NONE);
remote_pending = authReply.GetLength();
remote_index = 0;
client_index = client_pending = 0;
// TBD - should we pause client input notification here?
return PutClientAuthReply();
}
else
{
PLOG(PL_ERROR, "NorpSession::GetClientAuthRequest() received AuthRequest with no compatible method!\n");
return false;
}
}
break;
}
} // end switch(client_rx_index)
// Read next chunk of AuthRequest (as long as socket has data to read)
unsigned int numBytes = client_pending - client_index;
if (!socks_client_socket.Recv(((char*)client_buffer)+client_index, numBytes))
{
PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: client socket recv() failure!\n");
return false;
}
client_index += numBytes;
if (client_index < client_pending) break; // no more to read, so wait until RECV notification for more
} // end for(;;)
return true;
} // end NorpSession::GetClientAuthRequest()
bool NorpSession::PutClientAuthReply()
{
ASSERT(SOCKS_PUT_AUTH_REP == socks_state);
// We have a SOCKS AuthReply in our "remote_buffer" pending transmission to the SOCKS client
unsigned int numBytes = remote_pending - remote_index;
if (socks_client_socket.Send(((char*)remote_buffer)+remote_index, numBytes))
{
remote_index += numBytes;
if (remote_index < remote_pending)
{
// Only partially sent, cue output notification to complete
socks_client_socket.StartOutputNotification();
}
else
{
// AuthReply was fully sent
socks_client_socket.StopOutputNotification();
socks_state = SOCKS_GET_REQUEST;
remote_pending = remote_index = 0;
client_pending = 1; // To get VERSION byte of impending SOCKS Request
client_index = 0;
}
return true;
}
else
{
PLOG(PL_ERROR, "NorpSession::PutClientAuthReply() error: auth reply send failure!\n");
return false;
}
} // end NorpSession::PutClientAuthReply()
bool NorpSession::GetClientRequest()
{
ASSERT(SOCKS_GET_REQUEST == socks_state);
// SOCKS5 request: VERSION + CMD + RESV + ADDR_TYP + DST.ADDR + DST.PORT
// bytes: 1 + 1 + 1 + 1 + N + 2
for(;;) // loops until client request is read or no more input is ready
{
switch (client_index)
{
case 0: // need to read VERSION byte first
{
client_pending = 1;
break;
}
case 1: // Validate VERSION, and get REQ, if applicable
{
ProtoPktSOCKS::Request req(client_buffer, 1);
if (5 != req.GetVersion())
{
PLOG(PL_ERROR, "NorpSession::GetClientRequest() received incompatible version %d request!\n", req.GetVersion());
return false;
}
client_pending = 4; // ask for bytes up through ADDR_TYP
break;
}
case 2: // Handle request based on command type
case 3:
{
// no change, just CMD and/or RESV byte coming in
ASSERT(4 == client_pending);
break;
}
case 4: // Process ADDR_TYP to see how many more to ask for
{
ProtoPktSOCKS::Request req(client_buffer, 4);
switch (req.GetAddressType())
{
case ProtoPktSOCKS::IPv4:
client_pending = 4 + (4 + 2); // IPv4 addr + port
break;
case ProtoPktSOCKS::NAME:
client_pending = 4 + 1; // ask for name length byte
break;
case ProtoPktSOCKS::IPv6:
client_pending = 4 + (16 + 2); // IPv6 addr + port
break;
default:
PLOG(PL_ERROR, "NorpSession::GetClientRequest() error: invalid address type!\n");
return false;
}
break;
}
case 5: // Process NAME length byte
{
ProtoPktSOCKS::Request req(client_buffer, 5);
if (ProtoPktSOCKS::NAME == req.GetAddressType())
client_pending = 5 + (req.GetAddressLength() + 2); // DNS name + port
break;
}
default:
{
if (client_index == client_pending)
{
// Entire SOCKS Request from local client has been received, so handle it ...
ProtoPktSOCKS::Request request(client_buffer, client_index);
client_pending = client_index = 0; // finished with client buffer for moment after handing request here
return OnClientRequest(request, socks_client_socket.GetDestination());
}
break;
}
}
// Read next chunk of client Request (as long as socket has data to read)
unsigned int numBytes = client_pending - client_index;
if (!socks_client_socket.Recv(((char*)client_buffer)+client_index, numBytes))
{
PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: client socket recv() failure!\n");
return false;
}
client_index += numBytes;
if (client_index < client_pending) break; // no more to read, so wait until RECV notification for more
} // end for(;;;)
return true;
} // end NorpSession::GetClientRequest()
bool NorpSession::OnClientRequest(const ProtoPktSOCKS::Request request, const ProtoAddress& srcAddr)
{
ProtoAddress destAddr;
if (!request.GetAddress(destAddr))
{
PLOG(PL_ERROR, "NorpSession::HandleSocksRequest() error: request with invalid destination address!\n");
return false;
}
switch (request.GetCommand())
{
case ProtoPktSOCKS::Request::CONNECT:
PLOG(PL_INFO, "norp: session %u recvd CONNECT request dst addr/port = %s/%hu\n", GetSessionId(), destAddr.GetHostString(), destAddr.GetPort());
if (norm_enable)
{
// TBD - Determine "route" for "destAddr" to potentially separate remote "norp" peer
// (for now we assume "norp" remote is collocated on "destAddr" endpoints unless
// the controller has been configured for a specific remote 'correspondent')
ProtoAddress norpAddr;
controller.GetRemoteNorpAddress(destAddr, norpAddr);
// Relay SOCKS request to remote peer NORP server (peer will TCP connect to endpoint and reply via NORM)
return PutRemoteRequest(request, norpAddr);
}
else
{
return ConnectToRemote(destAddr); // do direct TCP connect to remote endpoint
}
case ProtoPktSOCKS::Request::BIND:
PLOG(PL_INFO, "norp: session %u recvd BIND request dst addr/port = %s/%hu\n", GetSessionId(), destAddr.GetHostString(), destAddr.GetPort());
return BindRemote(destAddr.GetPort());
case ProtoPktSOCKS::Request::UDP_ASSOC:
PLOG(PL_INFO, "norp: session %u recvd UDP_ASSOC for %s/%hu\n", GetSessionId(), destAddr.GetHostString(), destAddr.GetPort());
// Save our UDP client's address (use the control socket addr if unspecified in request)
if (destAddr.IsUnspecified())
{
udp_client_addr = srcAddr;
udp_client_addr.SetPort(destAddr.GetPort());
}
else
{
udp_client_addr = destAddr;
}
return OpenUdpRelay();
default:
PLOG(PL_ERROR, "NorpSession::OnClientRequest() error: invalid request command!\n");
return false;
}
} // end NorpSession::OnClientRequest()
bool NorpSession::PutRemoteRequest(const ProtoPktSOCKS::Request& request, const ProtoAddress& remoteAddr)
{
// The "request" here sits in our "client_buffer". We know this buffer has room to append our
// "session_id.identifier" to the end. This "identifier" gives the command a unique key in
// the context of the NorpSession "originator" NormNodeId to recognize if it is a repeated
// request command, etc.
if (NORM_SESSION_INVALID == norm_session)
{
// This creates the client-side NORM session for a connection.
// (TBD) Create single-socket NORM session using an ephemeral port and connect() to the remote
// NORP server NORM port. (Use NormChangeDestination() after NormStartReceiver() to
// configure this newly-create NormSession as needed)
#ifdef NEW_PORT
// We use port zero to get session that will bound to an ephemeral port (single port for tx and rx) upon NormStartReceiver
norm_session = NormCreateSession(controller.GetNormInstance(), remoteAddr.GetHostString(), 0, controller.GetNormNodeId());
#else
norm_session = NormCreateSession(controller.GetNormInstance(), remoteAddr.GetHostString(), controller.GetNormPort(), controller.GetNormNodeId());
#endif
if (NORM_SESSION_INVALID == norm_session)
{
PLOG(PL_ERROR, "NorpSession::PutRemoteRequest() NormCreateSession() failure!\n");
return false;
}
NormSetMessageTrace(norm_session, controller.GetNormTrace());
NormSetUserData(norm_session, this);
NormSetDefaultSyncPolicy(norm_session, NORM_SYNC_STREAM);
// We use a 2-step sender/receiver startup process by first opening an idle, "tx-only" NormSession to
// send the Request command to the remote "controller" NormSession (The remote will get our "tx port"
// info as part of this request sent). Our controller will get the REQ_ACK and/or SOCKS_REPLY message,
// then inform us, and so we will get the remote "tx port" info so we can then start our own receiver
// and "connect" our NORM rx_socket to the remote tx_socket. The sender here is made "idle" by disabling
// disabling the normal NORM GRTT probing. We don't re-activate that until we know the remote end has
// a receiver running (This is done upon REQ_ACK or SOCKS_REPLY reception when we also start our own NORM
// receiver).
char ifaceName[64];
ifaceName[63] = '\0';
if (ProtoNet::GetInterfaceName(controller.GetProxyAddress(), ifaceName, 63))
NormSetMulticastInterface(norm_session, ifaceName);
else
PLOG(PL_ERROR, "NorpSession::PutRemoteRequest() warning: unable to get interface name for address %s\n", controller.GetProxyAddress().GetHostString());
switch (controller.GetNormCC())
{
case Norp::NORM_CC: // default TCP-friendly congestion control
// do nothing, this is NORM's default mode
break;
case Norp::NORM_CCE: // "wireless-ready" ECN-only congestion control
NormSetEcnSupport(norm_session, true, true);
break;
case Norp::NORM_CCL: // "loss tolerant", non-ECN congestion control
NormSetEcnSupport(norm_session, false, false, true);
break;
case Norp::NORM_FIXED: // fixed-rate operation
// if cumulative rate limit has been imposed, it can override the fixed rate
// (i.e. to share pipe among multiple flows)
if ((norm_rate_min >= 0) && (norm_rate_min < controller.GetNormTxRate()))
NormSetTxRate(norm_session, norm_rate_min);
else
NormSetTxRate(norm_session, controller.GetNormTxRate());
break;
}
if ((norm_rate_min >= 0.0) || (norm_rate_max >= 0.0))
NormSetTxRateBounds(norm_session, norm_rate_min, norm_rate_max);
// Even though we call "NormStartSender()" to get a "tx port" value, we disable GRTT probing and thus
// defer "real" sender startup until we get an ACK to our request (see "OriginatorStartNorm()")
NormSetTxOnly(norm_session, true);
NormSetGrttProbingMode(norm_session, NORM_PROBE_NONE); // no probing until remote receiver is started
if (!NormStartSender(norm_session, GetSessionId(), NORM_BUFFER_SIZE, controller.GetNormSegmentSize(),
controller.GetNormBlockSize(), controller.GetNormParityCount()))
{
PLOG(PL_ERROR, "NorpSession::PutRemoteRequest() error: NormStartSender() failure!\n");
return false;
}
#ifdef NEW_PORT
// Now call NormChangeDestination() to set proper destination port and "connect" to it
NormChangeDestination(norm_session, remoteAddr.GetHostString(), controller.GetNormPort(), true);
#endif // NEW_PORT
//NormSetTxSocketBuffer(norm_session, 4096);
NormSetFlowControl(norm_session, 0.0); // disable timer-based flow control since we are ACK-limiting writes to stream
norm_segment_size = controller.GetNormSegmentSize();
norm_stream_buffer_max = ComputeNormStreamBufferSegmentCount(NORM_BUFFER_SIZE, norm_segment_size, controller.GetNormBlockSize());
norm_stream_buffer_max -= controller.GetNormBlockSize(); // a little safety margin
norm_stream_buffer_count = 0;
norm_stream_bytes_remain = 0;
norm_watermark_pending = false;
PLOG(PL_INFO, "norp: originator %lu created NORM session with dest %s/%hu (srcPort %hu)\n", controller.GetNormNodeId(),
remoteAddr.GetHostString(), controller.GetNormPort(), NormGetTxPort(norm_session));
// We open a UDP socket for NORP signaling here and "connect() it so we can get ICMP error messages
// (I.e., an ICMP "port unreachable" indicates the remote destination is not NORP-enabled)
if (!norp_tx_socket.Open())
{
PLOG(PL_ERROR, "NorpSession::PutRemoteRequest() error: unable to open norp_tx_socket!\n");
return false;
}
norp_remote_addr = remoteAddr;
norp_remote_addr.SetPort(controller.GetNorpPort());
if (!norp_tx_socket.Connect(norp_remote_addr))
{
PLOG(PL_ERROR, "NorpSession::PutRemoteRequest() error: unable to open and connect norp_tx_socket!\n");
return false;
}
}
socks_client_socket.StopInputNotification(); // don't accept more from client until connected
PLOG(PL_INFO, "norp: node %u relaying SOCKS request to NORP destination %s/%hu\n", controller.GetNormNodeId(),
remoteAddr.GetHostString(), controller.GetNorpPort());
// Put the SOCKS request into a NorpMsg and relay it to remote NORP server
// (Note that the request is "robustly" transmitted via periodic repetition until a request acknowledgment (REQ_ACK) is received
ASSERT(!norp_msg_timer.IsActive());
norp_msg.SetType(NorpMsg::SOCKS_REQ);
norp_msg.SetSessionId(GetSessionId());
norp_msg.SetSourcePort(NormGetTxPort(norm_session)); // Our NORM tx port number (so remote can "connect()"
norp_msg.SetDestinationPort(NormGetRxPort(norm_session)); // Our NORM rx port (for info)
norp_msg.SetNodeId(controller.GetNormNodeId()); // our NORM node id
norp_msg.SetContent((const char*)request.GetBuffer(), request.GetLength());
struct timeval currentTime;
ProtoSystemTime(currentTime);
norp_msg.SetTimestamp(currentTime);
if (!SendMessage(norp_msg))
PLOG(PL_ERROR, "NorpSession::PutRemoteRequest() warning: NORP message transmission failed (will try again)!\n");
norp_msg_timer.SetInterval(2.0 * norp_rtt_estimate);
ActivateTimer(norp_msg_timer);
socks_state = SOCKS_PUT_REQUEST;
return true;
} // end NorpSession::PutRemoteRequest()
bool NorpSession::MakeDirectConnect()
{
TRACE("MAKING DIRECT CONNECTION ...");
// This assumes our "norp_msg" previously sent in attempt to connect
// to a remote "norp" peer is cached (per PutRemoveRequest()
if (NORM_SESSION_INVALID != norm_session)
{
NormDestroySession(norm_session);
norm_session = NORM_SESSION_INVALID;
}
if (norp_msg_timer.IsActive()) norp_msg_timer.Deactivate();
if (NorpMsg::SOCKS_REQ != norp_msg.GetType())
{
PLOG(PL_ERROR, "NorpSession::MakeDirectConnect() error: invalid norp_msg enqueued!\n");
return false;
}
ProtoPktSOCKS::Request req(norp_msg.AccessContentPtr(), norp_msg.GetContentLength(), true);
ProtoAddress destAddr;
if (!req.GetAddress(destAddr))
{
PLOG(PL_ERROR, "NorpSession::MakeDirectConnect() error: norp_msg SOCKS_REQ with invalid destination address enqueued!\n");
return false;
}
return ConnectToRemote(destAddr);
} // end NorpSession::MakeDirectConnect()
bool NorpSession::OnRemoteRequestAcknowledgment(const NorpMsg& theMsg, const ProtoAddress& senderAddr)
{
// Note we update our RTT estimate on ACKs received
ProtoTime recvTime;
recvTime.GetCurrentTime();
ProtoTime sentTime;
theMsg.GetTimestamp(sentTime);
if (SOCKS_PUT_REQUEST == socks_state)
{
norp_rtt_estimate = ProtoTime::Delta(recvTime, sentTime); // initial rtt received
if (!OriginatorStartNorm(senderAddr, theMsg.GetNodeId(), theMsg.GetSourcePort(), theMsg.GetDestinationPort()))
{
PLOG(PL_ERROR, "NorpSession::OnRemoteRequestAcknowledgment() error: OriginatorStartNormSender() failure!\n");
return false;
}
// Note that successful OriginatorStartNorm() sets socks_state to SOCKS_CONNECTING
}
else
{
// Just use duplicative ACKs to update RTT estimate via EWMA
ASSERT(norp_rtt_estimate > 0.0);
double rttNew = ProtoTime::Delta(recvTime, sentTime);
norp_rtt_estimate = 0.5*(norp_rtt_estimate + rttNew); // TBD - is this a reasonable "smoothing" factor
}
// Note it's OK if a bona fide measured RTT is less than NORP_RTT_MIN
if (norp_rtt_estimate <= 0.0)
{
norp_rtt_estimate = NORP_RTT_MIN;
}
else if (norp_rtt_estimate > NORP_RTT_MAX)
{
PLOG(PL_WARN, "NorpSession::OnRemoteRequestAcknowledgment() warning: measured RTT exceeds max value of %lf sec.\n", NORP_RTT_MAX);
norp_rtt_estimate = NORP_RTT_MAX;
}
return true;
} // end NorpSession::OnRemoteRequestAcknowledgment()
bool NorpSession::OnRemoteReply(const NorpMsg& theMsg, const ProtoAddress& senderAddr)
{
ProtoPktSOCKS::Reply reply(theMsg.AccessContentPtr(), theMsg.GetContentLength());
PLOG(PL_INFO, "NorpSession::OnRemoteReply() session %hu reply type %d\n", GetSessionId(), reply.GetType());
if (ProtoPktSOCKS::Reply::SUCCESS == reply.GetType())
{
if (SOCKS_PUT_REQUEST == socks_state)
{
// Somehow we missed the NORP REQUEST_ACK and the reply is being received first
// (TBD - should we init RTT here like above???)
if (!OriginatorStartNorm(senderAddr, theMsg.GetNodeId(), theMsg.GetSourcePort(), theMsg.GetDestinationPort()))
{
PLOG(PL_ERROR, "NorpSession::OnRemoteReply() error: OriginatorStartNormSender() failure!\n");
return false;
}
// Note that successful OriginatorStartNorm() sets socks_state to SOCKS_CONNECTING
}
if (is_preset)
{
// Go straight to connected state and start
// accepting recv data from client or remote
client_index = client_pending = 0;
socks_client_socket.StartInputNotification();
remote_index = remote_pending = 0;
socks_state = SOCKS_CONNECTED;
}
else if (SOCKS_CONNECTING == socks_state)
{
// TBD - we may want to put the reply in the client_buffer instead???
// (i.e. in case we start receiving remote data before reply is fully sent?)
socks_state = SOCKS_PUT_REPLY;
memcpy(remote_buffer, (char*)reply.GetBuffer(), reply.GetLength());
remote_pending = reply.GetLength();
remote_index = 0;
PLOG(PL_INFO, "NorpSession::OnRemoteReply() originator relaying reply to SOCKS client ...\n");
if (!PutClientReply())
{
PLOG(PL_ERROR, "NorpSession::OnRemoteReply() error: unable to relay SOCKS reply to client!\n");
return false;
}
}
// Send an ACK to the received reply
UINT32 buffer[32/4];
NorpMsg ack(buffer, 32, false);
ack.SetType(NorpMsg::REP_ACK);
ack.SetSessionId(GetSessionId()); // our session id (note it was set by request originator)
ack.SetSourcePort(NormGetTxPort(norm_session)); // our NORM tx port number (so remote can "connect()"
ack.SetDestinationPort(NormGetRxPort(norm_session)); // our NORM rx port (for info)
struct timeval sentTime;
theMsg.GetTimestamp(sentTime);
ack.SetTimestamp(sentTime); // echo the sender's timestamp for their RTT measurement
ack.SetNodeId(controller.GetNormNodeId()); // our NORM node id
if (!controller.SendMessage(ack, senderAddr))
PLOG(PL_ERROR, "NorpSession::OnRemoteReply() warning: unsable to transmit NORP REP_ACK message!\n");
}
else
{
PLOG(PL_ERROR, "NorpSession::OnRemoteReply() received negative reply ...\n");
return false;
}
return true;
} // end NorpSession::OnRemoteReply()
// Note the "socks_state" MUST be SOCKS_PUT_REQUEST when this is invoked
// (starts associated NORM session upon receipt of request ack or reply)
bool NorpSession::OriginatorStartNorm(const ProtoAddress& senderAddr,
NormNodeId corrNormId,
UINT16 normSrcPort,
UINT16 normDstPort)
{
ASSERT(SOCKS_PUT_REQUEST == socks_state);
if (norp_msg_timer.IsActive()) norp_msg_timer.Deactivate(); // cancels ongoing request retransmission, if applicable
// TBD - log the norp_msg_timer repeat count?
PLOG(PL_INFO, "norp: originator %u starting NORM sender probing (and receiver), connected to remote NORM source port %hu ...\n",
controller.GetNormNodeId(), normSrcPort);
NormSetGrttEstimate(norm_session, norp_rtt_estimate); // init NORM with value learned from NORP setup handshake
NormSetRxPortReuse(norm_session, true, NULL, senderAddr.GetHostString(), normSrcPort);
if (!NormStartReceiver(norm_session, NORM_BUFFER_SIZE))
{
PLOG(PL_ERROR, "NorpSession::OriginatorStartNorm() error: NormStartReceiver() failure!\n");
return false;
}
// The "NormChangeDestination()" call here makes sure our NORM session sends its packets
// to the destination that the remote correspondent server wants us to use for NORM
NormChangeDestination(norm_session, senderAddr.GetHostString(), normDstPort);
if (Norp::NORM_FIXED != controller.GetNormCC())
NormSetCongestionControl(norm_session, true); // Note this also re-enables GRTT probing that was disabled at session creation
else
NormSetGrttProbingMode(norm_session, NORM_PROBE_ACTIVE); // re-enables previously suspended GRTT probing
if (NORM_OBJECT_INVALID == (norm_tx_stream = NormStreamOpen(norm_session, NORM_BUFFER_SIZE)))
{
PLOG(PL_ERROR, "NorpSession::OriginatorStartNorm() NormStreamOpen() failure!\n");
return false;
}
if (!NormAddAckingNode(norm_session, corrNormId))
{
PLOG(PL_ERROR, "NorpSession::OriginatorStartNorm() NormAddAckingNode() failure!\n");
return false;
}
socks_state = SOCKS_CONNECTING;
return true;
} // end NorpSession::OriginatorStartNorm()
bool NorpSession::OnRemoteRequest(const NorpMsg& theMsg, const ProtoAddress& senderAddr)
{
ProtoPktSOCKS::Request request(theMsg.AccessContentPtr(), theMsg.GetContentLength(), true);
PLOG(PL_INFO, "NorpSession::OnRemoteRequest() session %hu request type %d (source port %hu)\n",
GetSessionId(), request.GetCommand(), theMsg.GetSourcePort());
if (NORM_SESSION_INVALID == norm_session)
{
// 0) Store the sender address to transmit REPLY to when connected to remote
norp_remote_addr = senderAddr;
// 1) Create a NormSession with request sender as session (destination) address
UINT16 normSessionPort = controller.GetNormPort();
norm_session = NormCreateSession(controller.GetNormInstance(), senderAddr.GetHostString(), normSessionPort, controller.GetNormNodeId());
#ifdef NEW_PORT
NormSetTxPort(norm_session, normSessionPort); // single port
#endif // NEW_PORT
if (NORM_SESSION_INVALID == norm_session)
{
PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() NormCreateSession() failure!\n");
return false;
}
NormSetMessageTrace(norm_session, controller.GetNormTrace());
// Set the NormSession user data so NORM API events can be directed to this session
NormSetUserData(norm_session, this);
NormSetDefaultSyncPolicy(norm_session, NORM_SYNC_STREAM);
// Start our NORM session as a receiver (we start the sender later when we get a REPLY_ACK, or NORM_DATA)
// (First, enable port reuse and "connect()" to the sender's NORM source addr/port)
char ifaceName[64];
ifaceName[63] = '\0';
if (ProtoNet::GetInterfaceName(controller.GetProxyAddress(), ifaceName, 63))
NormSetMulticastInterface(norm_session, ifaceName);
else
PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() warning: unable to get interface name for address %s\n", controller.GetProxyAddress().GetHostString());
switch (controller.GetNormCC())
{
case Norp::NORM_CC: // default TCP-friendly congestion control
// do nothing, this is NORM's default mode
break;
case Norp::NORM_CCE: // "wireless-ready" ECN-only congestion control
NormSetEcnSupport(norm_session, true, true);
break;
case Norp::NORM_CCL: // "loss tolerant", non-ECN congestion control
NormSetEcnSupport(norm_session, false, false, true);
break;
case Norp::NORM_FIXED: // fixed-rate transmission (no congestion control)
// if cumulative rate limit has been imposed, it can override the fixed rate
// (i.e. to share pipe among multiple flows)
if ((norm_rate_min >= 0) && (norm_rate_min < controller.GetNormTxRate()))
NormSetTxRate(norm_session, norm_rate_min);
else
NormSetTxRate(norm_session, controller.GetNormTxRate());
break;
}
if ((norm_rate_min >= 0.0) || (norm_rate_max >= 0.0))
NormSetTxRateBounds(norm_session, norm_rate_min, norm_rate_max);
// The "NormSetRxPortReuse()" call here "connects" us to the remote NORM sender source addr/port
// (this creates a proper binding since we reuse the same port number for multiple NORM sessions)
#ifdef NEW_PORT
NormSetRxPortReuse(norm_session, true); // defer connecting to remote sender until to REMOTE_SENDER_NEW to get correct port
#else
NormSetRxPortReuse(norm_session, true, NULL, senderAddr.GetHostString(), theMsg.GetSourcePort());
#endif // if/else NEW_PORT
if (!NormStartReceiver(norm_session, NORM_BUFFER_SIZE))
{
PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: NormStartReceiver() failure!\n");
return false;
}
// This call makes sure we send our NORM packets to the originator's NORM port (i.e, if different than ours)
NormChangeDestination(norm_session, senderAddr.GetHostString(), theMsg.GetDestinationPort());
if (!NormAddAckingNode(norm_session, theMsg.GetNodeId()))
{
PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() NormAddAckingNode() failure!\n");
return false;
}
PLOG(PL_INFO, "norp: correspondent %lu created NORM session with dest %s/%hu (srcPort %hu) connected to NORM source port %hu\n",
controller.GetNormNodeId(), senderAddr.GetHostString(), controller.GetNormPort(), NormGetTxPort(norm_session), theMsg.GetSourcePort());
// 2) Initiate TCP connection to indicated endpoint address/port (should we do this first before setting up NORM?)
ProtoAddress destAddr;
if (!request.GetAddress(destAddr))
{
PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: request with invalid address!\n");
return false;
}
switch (request.GetCommand())
{
case ProtoPktSOCKS::Request::CONNECT:
PLOG(PL_INFO, "norp: session %u recvd remote CONNECT request dst add/port = %s/%hu\n", GetSessionId(), destAddr.GetHostString(), destAddr.GetPort());
if (!ConnectToRemote(destAddr))
{
PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: ConnectToRemote() failure\n");
// TBD - send failure reply
return false;
}
break;
case ProtoPktSOCKS::Request::BIND:
PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: remote SOCKS BIND not supported\n");
// TBD - send negative reply
return false;
case ProtoPktSOCKS::Request::UDP_ASSOC:
PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: remote SOCKS UDP_ASSOC not supported\n");
// TBD - send negative reply
return false;
default:
PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: invalid request command!\n");
return false;
}
} // end if (NORM_SESSION_INVALID == norm_session)
else
{
// TBD - verify this request matches our existing session info
}
// Note the "ConnectToRemote()" call above transitioned us to SOCKS_CONNECTING state
if (SOCKS_CONNECTING == socks_state)
{
ASSERT(!norp_msg_timer.IsActive());
// 3) Send a NormCmd request acknowledged (REQ_ACK) to "squelch" further request resends from remote
// (This is sent as a one-shot in response to NorpMsg::SOCKS_REQ messages)
UINT32 buffer[32/4];
NorpMsg ack(buffer, 32, false);
ack.SetType(NorpMsg::REQ_ACK);
ack.SetSessionId(GetSessionId());
ack.SetNodeId(controller.GetNormNodeId()); // our NORM node id
ack.SetSourcePort(NormGetTxPort(norm_session)); // Our NORM tx port number (so remote can "connect()"
ack.SetDestinationPort(NormGetRxPort(norm_session)); // Our NORM rx port (let's remote where to send NORM pkts)
struct timeval sentTime;
theMsg.GetTimestamp(sentTime);
ack.SetTimestamp(sentTime);
if (!controller.SendMessage(ack, senderAddr))
PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() warning: NORP REQ_ACK message transmission failed (will try again)!\n");
}
else if (SOCKS_PUT_REPLY == socks_state)
{
// The reply already sent upon connect was evidently not
// received (nor the REQ_ACK message(s) that were sent)
// Note calling this also resets the reply robust retransmission process
if (!PutRemoteReply())
{
PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: unable to send SOCKS_REPLY\n");
return false;
}
}
else if (SOCKS_CONNECTED == socks_state)
{
// Evidently a late arriving request, so we just ignore it
PLOG(PL_INFO, "NorpSession::OnRemoteRequest() late arriving remote SOCK_REQUEST\n");
}
return true;
} // end NorpSession::OnRemoteRequest()
bool NorpSession::PutRemoteReply()
{
// Send SOCKS reply to remote NORP server via NORM command
ASSERT(IsRemoteSession());
norp_msg.Init();
norp_msg.SetType(NorpMsg::SOCKS_REP);
norp_msg.SetSessionId(GetSessionId()); // our session id (note was selected by request originator)
norp_msg.SetSourcePort(NormGetTxPort(norm_session)); // our NORM tx port number (so remote can "connect()"
norp_msg.SetDestinationPort(NormGetRxPort(norm_session)); // our NORM rx port (for info)
norp_msg.SetNodeId(controller.GetNormNodeId()); // our NORM node id
ProtoPktSOCKS::Reply reply(norp_msg.AccessContentPtr(), norp_msg.GetBufferLength() - norp_msg.GetLength(), false);
reply.SetVersion(5);
reply.SetType(ProtoPktSOCKS::Reply::SUCCESS);
reply.SetAddress(socks_remote_socket.GetSourceAddr());
norp_msg.SetContentLength(reply.GetLength());
// If an existing ack or reply cmd is pending, we cancel it. This
// resets the robust transmission of the reply
if (norp_msg_timer.IsActive()) norp_msg_timer.Deactivate();
struct timeval currentTime;
ProtoSystemTime(currentTime);
norp_msg.SetTimestamp(currentTime);
if (!controller.SendMessage(norp_msg, norp_remote_addr))
PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() warning: NORP REQ_ACK message transmission failed (will try again)!\n");
norp_msg_timer.SetInterval(2.0 * norp_rtt_estimate);
ActivateTimer(norp_msg_timer);
socks_state = SOCKS_PUT_REPLY;
return true;
} // end NorpSession::PutRemoteReply()
bool NorpSession::OnRemoteReplyAcknowledgment(const NorpMsg& theMsg)
{
// Note we update our RTT estimate in either case
ProtoTime recvTime;
recvTime.GetCurrentTime();
ProtoTime sentTime;
theMsg.GetTimestamp(sentTime);
if (SOCKS_PUT_REPLY == socks_state)
{
// Initial rtt measurement
norp_rtt_estimate = ProtoTime::Delta(recvTime, sentTime);
PLOG(PL_INFO, "norp node %u starting sender probing (and sender) ...\n", controller.GetNormNodeId());
if (Norp::NORM_FIXED != controller.GetNormCC())
NormSetCongestionControl(norm_session, true);
if (!NormStartSender(norm_session, GetSessionId(), NORM_BUFFER_SIZE, controller.GetNormSegmentSize(),
controller.GetNormBlockSize(), controller.GetNormParityCount()))
{
PLOG(PL_ERROR, "NorpSession::OnRemoteReplyAcknowledgment() NormStartSender() failure!\n");
return false;
}
//NormSetTxSocketBuffer(norm_session, 4096);
NormSetFlowControl(norm_session, 0.0); // disable timer-based flow control since we are ACK-limiting writes to stream
norm_segment_size = controller.GetNormSegmentSize();
norm_stream_buffer_max = ComputeNormStreamBufferSegmentCount(NORM_BUFFER_SIZE, norm_segment_size, controller.GetNormBlockSize());
norm_stream_buffer_max -= controller.GetNormBlockSize(); // a little safety margin
norm_stream_buffer_count = 0;
norm_stream_bytes_remain = 0;
norm_watermark_pending = false;
if (NORM_OBJECT_INVALID == (norm_tx_stream = NormStreamOpen(norm_session, NORM_BUFFER_SIZE)))
{
PLOG(PL_ERROR, "NorpSession::OnRemoteReplyAcknowledgment() NormStreamOpen() failure!\n");
return false;
}
if (norp_msg_timer.IsActive()) norp_msg_timer.Deactivate(); // This cancels the robust (repeated) remote reply transmission
socks_remote_socket.StartInputNotification(); // enable flow of data from remote TCP socket
socks_state = SOCKS_CONNECTED;
remote_index = remote_pending = 0;
}
else
{
ASSERT(norp_rtt_estimate > 0.0);
// Only use duplicative ACKs to update RTT estimate via EWMA
double rttNew = ProtoTime::Delta(recvTime, sentTime);
norp_rtt_estimate = 0.5*(norp_rtt_estimate + rttNew); // TBD - is this a reasonable "smoothing" factor
}
// Note it's OK if a bona fide measured RTT is less than NORP_RTT_MIN
if (norp_rtt_estimate <= 0.0)
{
norp_rtt_estimate = NORP_RTT_MIN;
}
else if (norp_rtt_estimate > NORP_RTT_MAX)
{
PLOG(PL_WARN, "NorpSession::OnRemoteReplyAcknowledgment() warning: measured RTT exceeds max value of %lf sec.\n", NORP_RTT_MAX);
norp_rtt_estimate = NORP_RTT_MAX;
}
return true;
} // end NorpSession::OnRemoteReplyAcknowledgment()
bool NorpSession::ConnectToRemote(const ProtoAddress& destAddr)
{
// Make a direct TCP connection to remote destAddr
ASSERT(socks_remote_socket.IsClosed());
if (!socks_remote_socket.Connect(destAddr))
{
PLOG(PL_ERROR, "NorpSession::ConnectToRemote() error: remote socket connect() failure!\n");
return false;
}
ASSERT(!socks_remote_socket.IsConnected()); // TBD - handle immediate connection case???
PLOG(PL_DETAIL, "NorpSession::ConnectToRemote() connection to %s/%hu initiated (connected:%d)...\n",
destAddr.GetHostString(), destAddr.GetPort(), socks_remote_socket.IsConnected());
if (socks_client_socket.IsOpen())
socks_client_socket.StopInputNotification(); // don't accept more from client until connected
socks_state = SOCKS_CONNECTING;
return true;
} // end NorpSession::ConnectToRemote()
bool NorpSession::BindRemote(UINT16 bindPort)
{
// Open our remote socket in unbound state
if (!socks_remote_socket.Open(0, proxy_addr.GetType(), false))
{
PLOG(PL_ERROR, "NorpSession::BindRemote() error: unable to open socket for remote connection\n");
return false;
}
// Typically, the BIND request should be for port zero (any available port)
// But, in the case that it is not, we set port reuse for when the norp server
// is set up as a "local, loopback" server
if (0 != bindPort) socks_remote_socket.SetReuse(true);
bool result = socks_remote_socket.Bind(bindPort, &proxy_addr);
if (!result) result = socks_remote_socket.Bind(0, &proxy_addr);
if (!result)
{
PLOG(PL_ERROR, "NorpSession::BindRemote() error: unable to bind for remote connection\n");
return false;
}
if (!socks_remote_socket.Listen())
{
PLOG(PL_ERROR, "NorpSession::BindRemote() error: unable to listen for remote connection\n");
return false;
}
// Construct the first reply to inform client of bind addr
ProtoPktSOCKS::Reply reply(remote_buffer, SOCKS_BUFFER_SIZE, false);
reply.SetVersion(5);
reply.SetType(ProtoPktSOCKS::Reply::SUCCESS);
ProtoAddress bindAddr = proxy_addr;
bindAddr.SetPort(socks_remote_socket.GetPort());
PLOG(PL_INFO,"norp proxy listening on addr/port = %s/%hu\n", bindAddr.GetHostString(), bindAddr.GetPort());
reply.SetAddress(bindAddr);
remote_pending = reply.GetLength();
remote_index = 0;
socks_state = SOCKS_PUT_REPLY;
return PutClientReply();
} // NorpSession::BindRemote()
bool NorpSession::OpenUdpRelay()
{
// 1) open udp_relay_socket
if (!udp_relay_socket.Open())
{
PLOG(PL_ERROR, "NorpSession::OpenUdpRelay() error: udp_relay_socket open failed!\n");
return false;
}
// 2) Build the SOCKS reply into the remote_buffer ...
ProtoPktSOCKS::Reply reply(remote_buffer, SOCKS_BUFFER_SIZE, false);
reply.SetVersion(5);
reply.SetType(ProtoPktSOCKS::Reply::SUCCESS);
// We use the bind (local source) addr of our client socket as the SOCKS reply BIND.ADDR
// and the port number of our udp relay socket as the SOCKS reply BIND.PORT
ProtoAddress bindAddr = socks_client_socket.GetSourceAddr();
bindAddr.SetPort(udp_relay_socket.GetPort());
reply.SetAddress(bindAddr);
PLOG(PL_INFO, "udp relay bind addr = %s/%hu\n", bindAddr.GetHostString(), bindAddr.GetPort());
remote_pending = reply.GetLength();
remote_index = 0;
socks_state = SOCKS_PUT_REPLY;
return PutClientReply();
} // end NorpSession::OpenUdpRelay()
bool NorpSession::PutClientReply()
{
// Transmit SOCKS reply that's in "remote_buffer" to local SOCKS client
// (This buffering and indexing allows for possibly multiple TCP sockets
// writes to send the reply, even though that is not very likely given a
// SOCKS reply is smaller than the minimum IP MTU of 512 bytes)
ASSERT(SOCKS_PUT_REPLY == socks_state);
unsigned int numBytes = remote_pending - remote_index;
if (socks_client_socket.Send(((char*)remote_buffer)+remote_index, numBytes))
{
remote_index += numBytes;
if (remote_index < remote_pending)
{
// Still more to send, keep output notification going
socks_client_socket.StartOutputNotification();
}
else
{
// Reply fully sent, now transition to SOCKS_CONNECTED state
PLOG(PL_DETAIL, "NorpSession::PutClientReply() session %hu:%08x SOCKS reply sent to client ...\n",
GetSessionId(), GetOriginatorId());
remote_index = remote_pending = 0;
socks_client_socket.StopOutputNotification();
// Start accepting recv data from client or remote
client_index = client_pending = 0;
socks_client_socket.StartInputNotification();
if (socks_remote_socket.IsOpen())
socks_remote_socket.StartInputNotification();
socks_state = SOCKS_CONNECTED;
}
}
else
{
PLOG(PL_ERROR, "NorpSession::PutClientReply() error: remote socket send failure\n");
return false;
}
return true;
} // end NorpSession::PutClientReply()
bool NorpSession::GetClientData()
{
if (0 != client_pending) return true;
// Get whatever data is ready from client and start sending to remote
unsigned int numBytes = SOCKS_BUFFER_SIZE;
if (IsRemoteSession() && !IsRemoteOriginator())
{
// We're the remote correspondent, so get client data from remote originator (via NORM)
if (norm_rx_pending)
{
ASSERT(0 == client_pending);
if (NormStreamRead(norm_rx_stream, (char*)client_buffer, &numBytes))
{
client_pending = numBytes;
client_index = 0;
if (numBytes < SOCKS_BUFFER_SIZE) norm_rx_pending = false;
PLOG(PL_DETAIL, "NorpSession::GetClientData() correspondent read %lu bytes from NORM stream ..\n", numBytes);
if (0 != numBytes)
return PutRemoteData();
// else wait for NORM_RX_OBJECT_UPDATED notification
}
else
{
PLOG(PL_ERROR, "NorpSession::GetClientData() NormStreamRead() error: break in stream!\n");
return false;
}
}
// else wait for NORM_RX_OBJECT_UPDATED notification
}
else
{
// Originator server so get data from SOCKS client via TCP
ASSERT(0 == client_pending);
if (socks_client_socket.Recv((char*)client_buffer, numBytes))
{
client_pending = numBytes;
client_index = 0;
if (0 != numBytes)
{
PLOG(PL_DETAIL, "NorpSession::GetClientData() originator read %lu bytes from SOCKS client ...\n", numBytes);
socks_client_socket.StopInputNotification();
return PutRemoteData();
}
// else socket will close itself when appropriate
}
else
{
PLOG(PL_ERROR, "NorpSession::GetClientData() client socket recv failure!\n");
return false;
}
}
return true;
} // end NorpSession::GetClientData()
bool NorpSession::PutClientData()
{
// Transmit data that's in "remote_buffer" to client
unsigned int numBytes = remote_pending - remote_index;
if (IsRemoteSession() && !IsRemoteOriginator())
{
if (0 != numBytes)
{
numBytes = WriteToNormStream(((char*)remote_buffer)+remote_index, numBytes);
}
PLOG(PL_DETAIL, "correspondent wrote %u bytes to NORM stream ...\n", numBytes);
remote_index += numBytes;
if (remote_index == remote_pending)
{
// All pending client data has been enqueued to remote
remote_index = remote_pending = 0;
if (SOCKS_CONNECTED == socks_state)
{
FlushNormStream(false, NORM_FLUSH_ACTIVE);
socks_remote_socket.StartInputNotification();
}
else
{
ASSERT(SOCKS_SHUTDOWN == socks_state);
// No more data to put, so gracefully close norm_tx_stream
NormStreamClose(norm_tx_stream, true); // Note our "trick" here to do a graceful close, _then_ watermark
NormSetWatermark(norm_session, norm_tx_stream, true); // future NORM API will add "bool watermark" option to graceful close
}
}
// else wait for NORM_TX_QUEUE_VACANCY / EMPTY or watermark acknowledgemnt completion
}
else
{
// Relay data to local SOCKS client via TCP
if (0 != numBytes)
{
if (!socks_client_socket.Send(((char*)remote_buffer)+remote_index, numBytes))
{
if (SOCKS_CONNECTED == socks_state)
{
PLOG(PL_ERROR, "NorpSession::PutClientData() error: remote socket send failure\n");
return false;
}
else // SOCKS_SHUTDOWN == socks_state
{
PLOG(PL_WARN, "NorpSession::PutClientData() warning: remote socket send failure\n");
return true;
}
}
}
PLOG(PL_DETAIL, "originator wrote %lu bytes to client TCP socket ...\n", numBytes);
remote_index += numBytes;
if (remote_index < remote_pending)
{
// Still more data to send, keep output notification going
socks_client_socket.StartOutputNotification();
}
else
{
// Data fully sent, re-enable remote_socket input notifcation
remote_index = remote_pending = 0;
socks_client_socket.StopOutputNotification();
if (SOCKS_CONNECTED == socks_state)
{
if (IsRemoteSession())
{
// We're the remote session originator, so get more data if ready
if (norm_rx_pending)
return GetRemoteData();
// else wait for NORM_RX_OBJECT_UPDATED notification
}
else
{
// Restart accepting recv data from remote
socks_remote_socket.StartInputNotification();
}
}
else // SOCKS_SHUTDOWN == socks_state
{
if (socks_client_socket.IsConnected())
{
socks_client_socket.Shutdown();
}
else
{
socks_client_socket.Close();
if (ShutdownComplete()) Shutdown();
}
}
}
}
return true;
} // end NorpSession::PutClientData()
void NorpSession::OnSocksRemoteEvent(ProtoSocket& theSocket,
ProtoSocket::Event theEvent)
{
PLOG(PL_DETAIL, "NorpSession::OnSocksRemoteEvent(");
switch (theEvent)
{
case ProtoSocket::INVALID_EVENT:
{
// should never happen, just for completeness of switch cases
PLOG(PL_DETAIL, "INVALID_EVENT) ...\n");
break;
}
case ProtoSocket::CONNECT:
{
PLOG(PL_DETAIL, "CONNECT) ...\n");
ASSERT(SOCKS_CONNECTING == socks_state);
PLOG(PL_DETAIL, "NorpSession::OnSocksRemoteEvent(CONNECT) session %hu:%08x connected to destination %s/%hu ...\n",
GetSessionId(), GetOriginatorId(),
socks_remote_socket.GetDestination().GetHostString(),
socks_remote_socket.GetDestination().GetPort());
if (is_preset)
{
// Go straight to connected state and start
// accepting recv data from client or remote
client_index = client_pending = 0;
socks_client_socket.StartInputNotification();
remote_index = remote_pending = 0;
socks_state = SOCKS_CONNECTED;
}
else
{
// Don't get data from remote until reply sent to client
socks_remote_socket.StopInputNotification();
socks_state = SOCKS_PUT_REPLY;
if (IsRemoteSession())
{
if (!PutRemoteReply())
{
PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent(CONNECT) error: SOCKS CONNECT remote reply send failure!\n");
Shutdown();
}
}
else
{
// Build reply in "remote_buffer" to send to client
ProtoPktSOCKS::Reply reply(remote_buffer, SOCKS_BUFFER_SIZE, false);
reply.SetVersion(5);
reply.SetType(ProtoPktSOCKS::Reply::SUCCESS);
reply.SetAddress(socks_remote_socket.GetSourceAddr());
remote_pending = reply.GetLength();
remote_index = 0;
// Send SOCKS reply to locally connected client
if (!PutClientReply())
{
PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent(CONNECT) error: SOCKS CONNECT local reply send failure!\n");
Shutdown();
}
}
}
break;
}
case ProtoSocket::ACCEPT:
{
PLOG(PL_DETAIL, "ACCEPT) ...\n");
// This will happen for a listening socket put in place for BIND request
// (we need to send a second reply indicating the remote connection accepted)
ASSERT(SOCKS_CONNECTED == socks_state);
if (!socks_remote_socket.Accept())
{
PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent(ACCEPT) error: SOCKS BIND accept failure!\n");
Shutdown();
break;
}
// TBD - should check that the accepted remote dst addr/port is what it's supposed to be
ProtoPktSOCKS::Reply reply(remote_buffer, SOCKS_BUFFER_SIZE, false);
reply.SetVersion(5);
reply.SetType(ProtoPktSOCKS::Reply::SUCCESS);
reply.SetAddress(socks_remote_socket.GetDestination());
remote_pending = reply.GetLength();
remote_index = 0;
socks_state = SOCKS_PUT_REPLY;
if (!PutClientReply())
{
PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent(ACCEPT) error: SOCKS BIND reply send failure!\n");
Shutdown();
}
break;
}
case ProtoSocket::SEND:
PLOG(PL_DETAIL, "SEND) ...\n");
// write pending data from client_buffer to remote
if (SOCKS_PUT_REPLY == socks_state)
{
if (!PutRemoteReply())
{
PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent() error: PutRemoteReply() failure!\n");
Shutdown();
}
}
else if (!PutRemoteData())
{
PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent() error: PutRemoteData() failure!\n");
Shutdown();
}
break;
case ProtoSocket::RECV:
{
PLOG(PL_DETAIL, "RECV) ...\n");
if (0 == remote_pending) // we need this check because of occasional extra notifications with old "protolib"
{
if (!GetRemoteData())
{
PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent(SEND) error: GetRemoteData() failure!\n");
Shutdown();
}
}
break;
}
case ProtoSocket::DISCONNECT:
PLOG(PL_DETAIL, "DISCONNECT) ...\n");
switch (socks_state)
{
case SOCKS_VOID:
case SOCKS_IDLE:
case SOCKS_GET_AUTH_REQ:
case SOCKS_PUT_AUTH_REP:
case SOCKS_GET_REQUEST:
case SOCKS_PUT_REQUEST:
ASSERT(0);
break;
case SOCKS_CONNECTING:
case SOCKS_PUT_REPLY:
case SOCKS_GET_REPLY:
PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent() error: premature SOCKS client disconnect!\n");
Shutdown(); // unexpected, premature client disconnect
break;
case SOCKS_CONNECTED:
socks_state = SOCKS_SHUTDOWN;
if (0 == remote_pending)
PutClientData(); // to shutdown NORM stream or socks_client_socket
case SOCKS_SHUTDOWN:
// Client finished sending (and receiving) data
theSocket.Close();
if (ShutdownComplete())
Close();
// else further shutdown actions pending
break;
}
break;
case ProtoSocket::EXCEPTION:
PLOG(PL_DETAIL, "EXCEPTION) ...\n"); // shouldn't happen
break;
case ProtoSocket::ERROR_:
PLOG(PL_DETAIL, "ERROR_) ...\n");
Shutdown();
break;
}
} // end NorpSession::OnSocksRemoteEvent()
bool NorpSession::GetRemoteData()
{
// Receive data from remote socket into remote_buffer
// Get some data from remote and start sending it to the client
ASSERT(0 == remote_pending);
unsigned int numBytes = SOCKS_BUFFER_SIZE;
if (IsRemoteSession() && IsRemoteOriginator())
{
// We're the remote correspondent (i.e. not the remote session originator)
if (norm_rx_pending)
{
ASSERT(0 == remote_pending);
if (NormStreamRead(norm_rx_stream, (char*)remote_buffer, &numBytes))
{
remote_pending = numBytes;
remote_index = 0;
if (numBytes < SOCKS_BUFFER_SIZE) norm_rx_pending = false;
if (0 != numBytes)
return PutClientData();
// else wait for NORM_RX_OBJECT_UPDATED notification
}
else
{
PLOG(PL_ERROR, "NorpSession::GetRemoteData() NormStreamRead() error: break in stream!\n");
return false;
}
}
// else wait for NORM_RX_OBJECT_UPDATED notification
}
else
{
// Remote correspondent or direct-connect, so use TCP
ASSERT(0 == remote_pending);
if (socks_remote_socket.Recv((char*)remote_buffer, numBytes))
{
remote_pending = numBytes;
remote_index = 0;
if (0 != numBytes)
{
PLOG(PL_DETAIL, "NorpSession::GetClientData() originator read %lu bytes from remote ...\n", numBytes);
socks_remote_socket.StopInputNotification();
return PutClientData();
}
// else socket will disconnect itself and session will close
}
else
{
PLOG(PL_ERROR, "NorpSession::GetRemoteData() error: remote socket recv failure!\n");
return false;
}
}
return true;
} // end NorpSession::GetRemoteData()
bool NorpSession::PutRemoteData()
{
// Transmit data in "client_buffer" to remote
unsigned int numBytes = client_pending - client_index;
if (IsRemoteSession() && IsRemoteOriginator())
{
if (0 != numBytes)
numBytes = WriteToNormStream(((char*)client_buffer)+client_index, numBytes);
PLOG(PL_DETAIL, "originator wrote %u bytes to NORM stream ...\n", numBytes);
client_index += numBytes;
if (client_index == client_pending)
{
// All pending client data has been enqueued to remote
client_index = client_pending = 0;
if (SOCKS_CONNECTED == socks_state)
{
FlushNormStream(false, NORM_FLUSH_ACTIVE);
socks_client_socket.StartInputNotification(); // to get more data
}
else
{
ASSERT(SOCKS_SHUTDOWN == socks_state);
// gracefully close our tx stream
NormStreamClose(norm_tx_stream, true); // Note our "trick" here to do a graceful close, _then_ watermark
NormSetWatermark(norm_session, norm_tx_stream, true); // future NORM API will add "bool watermark" option to graceful close
}
}
// else wait for NORM_TX_QUEUE_VACANCY / EMPTY or watermark acknowledgment completion
}
else
{
// Use TCP to send data directly to remote endpoint
if (0 != numBytes)
{
if (!socks_remote_socket.Send(((char*)client_buffer)+client_index, numBytes))
{
PLOG(PL_ERROR, "NorpSession::PutRemoteData() error: remote TCP socket send failure\n");
return false;
}
}
PLOG(PL_DETAIL, "%s wrote %lu bytes to remote TCP socket ...\n", IsRemoteSession() ? "correspondent" : "server", numBytes);
client_index += numBytes;
if (client_index < client_pending)
{
socks_remote_socket.StartOutputNotification();
}
else
{
// All pending client data has been sent to remote
socks_remote_socket.StopOutputNotification();
client_index = client_pending = 0;
if (SOCKS_CONNECTED == socks_state)
{
if (IsRemoteSession())
{
// We're the remote correspondent (i.e. not the remote session originator)
if (norm_rx_pending)
return GetClientData();
// else wait for NORM_RX_OBJECT_UPDATED notification
}
else
{
socks_client_socket.StartInputNotification(); // prompt for more from client
}
}
else
{
ASSERT(SOCKS_SHUTDOWN == socks_state);
if (socks_remote_socket.IsConnected())
{
socks_remote_socket.Shutdown();
}
else
{
socks_remote_socket.Close();
if (ShutdownComplete()) Shutdown();
}
}
}
}
return true;
} // end NorpSession::PutRemoteData()
void NorpSession::OnUdpRelayEvent(ProtoSocket& theSocket,
ProtoSocket::Event theEvent)
{
switch (theEvent)
{
case ProtoSocket::RECV:
{
UINT32 buffer[8192/4];
ProtoAddress srcAddr;
char* bufPtr = ((char*)buffer) + 22; // allows room for IPv6 header to be applied
unsigned int numBytes = (8192 - 22);
while (udp_relay_socket.RecvFrom(bufPtr, numBytes, srcAddr))
{
if (0 == numBytes) break; // no more data to read
if (srcAddr.IsEqual(udp_client_addr))
{
// Packet is from client, so relay it ...
// (First need to parse SOCKS header to get destination, etc
// The cast up to UINT32* here is safe because UdpRequest uses byte boundaries
ProtoPktSOCKS::UdpRequest udpReq((UINT32*)bufPtr, numBytes);
if (0 != udpReq.GetFrag())
{
PLOG(PL_ERROR, "NorpSession::OnUdpRelayEvent() received fragmented packet from client!"
" (fragmentation not yet supported.)\n");
numBytes = (8192 - 22); // reset "numBytes"
continue; // get next packet
}
ProtoAddress dstAddr;
if (!udpReq.GetAddress(dstAddr))
{
PLOG(PL_ERROR, "NorpSession::OnUdpRelayEvent() received packet with bad destination address from client!\n");
numBytes = 8192;
continue; // get next packet
}
// TBD - for NORM relaying we need to establish a new child NorpSession to hand
// the packet stream off to for each unique UDP destination. Since UDP is
// connectionless, we will need to have some rules of motion regarding
// timeout, etc of the child session? We will have to buffer incoming UDP
// packets while the child NorpSession is being setup. We could use the new
// NormSocket APIs to signal setup with a SOCKS handshake relayed in-band
// at the NORM connection startup ....
// Relay the packet (TBD - add buffering, etc?)
unsigned int bytesSent = udpReq.GetDataLength();
if (!udp_relay_socket.SendTo(udpReq.GetDataPtr(), bytesSent, dstAddr) || (0 == bytesSent))
{
PLOG(PL_ERROR, "NorpSession::OnUdpRelayEvent() error: unable to send packet to %s/%hu!\n",
dstAddr.GetHostString(), dstAddr.GetPort());
numBytes = (8192 - 22); // reset "numBytes"
continue; // get next packet
}
}
else
{
// TBD - should we make sure the dest addr was for our client?
// Relay inbound packet to SOCKS client
// Note we left room for our SOCKS UdpRequest header to be applied in front of payload data
unsigned int headerOffset = 0;
if (ProtoAddress::IPv4 == srcAddr.GetType())
headerOffset = 12; // IPv4 addr is 12 bytes shorter than IPv6
else if (ProtoAddress::IPv6 != srcAddr.GetType())
ASSERT(0); // can only receive IPv4 or IPv6 packets1
char* reqPtr = ((char*)buffer) + headerOffset;
// The cast up to UINT32* here is safe because UdpRequest uses byte boundaries
ProtoPktSOCKS::UdpRequest udpReq((UINT32*)reqPtr, numBytes + 22 - headerOffset, false);
udpReq.SetFragment(0);
udpReq.SetAddress(srcAddr);
udpReq.SetDataLength(numBytes);
// Send to client
unsigned int bytesSent = udpReq.GetLength();
if (!udp_relay_socket.SendTo(reqPtr, bytesSent, udp_client_addr) || (0 == bytesSent))
{
PLOG(PL_ERROR, "NorpSession::OnUdpRelayEvent() error: unable to relay inbound packet to %s/%hu!\n",
udp_client_addr.GetHostString(), udp_client_addr.GetPort());
numBytes = (8192 - 22);
continue; // get next packet
}
}
numBytes = (8192 - 22); // reset "numBytes"
}
break;
}
default:
PLOG(PL_DETAIL, "NorpSession::OnUdpRelayEvent() unhandled event type %d\n", theEvent);
break;
} // end switch(theEvent)
} // end NorpSession::OnUdpRelayEvent()
bool NorpSession::GetNormNodeAddress(NormNodeHandle nodeHandle, ProtoAddress& theAddr)
{
char addrBuffer[16];
unsigned int addrLen = 16;
UINT16 portNum;
if (NormNodeGetAddress(nodeHandle, addrBuffer, &addrLen, &portNum))
{
if (4 == addrLen)
theAddr.SetRawHostAddress(ProtoAddress::IPv4, addrBuffer, 4);
else
theAddr.SetRawHostAddress(ProtoAddress::IPv6, addrBuffer, 16);
theAddr.SetPort(portNum);
return true;
}
else
{
PLOG(PL_ERROR, "NorpSession::GetNormNodeAddress() error: NormNodeGetAddress() failed!\n");
theAddr.Invalidate();
return false;
}
} // end NorpSession::GetNormNodeAddress()
unsigned int NorpSession::ComputeNormStreamBufferSegmentCount(unsigned int bufferBytes, UINT16 segmentSize, UINT16 blockSize)
{
// This same computation is performed in NormStreamObject::Open() in "normObject.cpp"
unsigned int numBlocks = bufferBytes / (blockSize * segmentSize);
if (numBlocks < 2) numBlocks = 2; // NORM enforces a 2-block minimum buffer size
return (numBlocks * blockSize);
} // end NorpSession::ComputeNormStreamBufferSegmentCount()
unsigned int NorpSession::WriteToNormStream(const char* buffer,
unsigned int numBytes)
{
// This method uses NormStreamWrite(), but limits writes by explicit ACK-based flow control status
if (norm_stream_buffer_count < norm_stream_buffer_max)
{
// 1) How many buffer bytes are available?
unsigned int bytesAvailable = norm_segment_size * (norm_stream_buffer_max - norm_stream_buffer_count);
bytesAvailable -= norm_stream_bytes_remain; // unflushed segment portiomn
if (numBytes <= bytesAvailable)
{
unsigned int totalBytes = numBytes + norm_stream_bytes_remain;
unsigned int numSegments = totalBytes / norm_segment_size;
norm_stream_bytes_remain = totalBytes % norm_segment_size;
norm_stream_buffer_count += numSegments;
}
else
{
numBytes = bytesAvailable;
norm_stream_buffer_count = norm_stream_buffer_max;
}
// 2) Write to the stream
unsigned int bytesWritten = NormStreamWrite(norm_tx_stream, buffer, numBytes);
//ASSERT(bytesWritten == numBytes); // this could happen if timer-based flow control is left enabled
// or if stream is closing ... ???
// We had a "stream is closing" case here somehow? Need to make sure we don't try
// to write to norm stream
// 3) Do we need to issue a watermark ACK request?
if (!norm_watermark_pending && (norm_stream_buffer_count >= (norm_stream_buffer_max >> 1)))
{
//TRACE("NorpSession::WriteToNormStream() initiating watermark ACK request (buffer count:%lu max:%lu usage:%u)...\n",
// norm_stream_buffer_count, norm_stream_buffer_max, NormStreamGetBufferUsage(norm_tx_stream));
NormSetWatermark(norm_session, norm_tx_stream);
norm_watermark_pending = true;
}
return bytesWritten;
}
else
{
PLOG(PL_DETAIL, "NorpSession::WriteToNormStream() is blocked pending acknowledgment from receiver\n");
return 0;
}
} // end NorpSession::WriteToNormStream()
void NorpSession::FlushNormStream(bool eom, NormFlushMode flushMode)
{
// NormStreamFlush always will transmit pending runt segments, if applicable
// (thus we need to manage our buffer counting accordingly if pending bytes remain)
NormStreamFlush(norm_tx_stream, eom, flushMode);
if (0 != norm_stream_bytes_remain)
{
norm_stream_buffer_count++;
norm_stream_bytes_remain = 0;
if (!norm_watermark_pending && (norm_stream_buffer_count >= (norm_stream_buffer_max >> 1)))
{
//TRACE("NorpSession::FlushNormStream() initiating watermark ACK request (buffer count:%lu max:%lu usage:%u)...\n",
// norm_stream_buffer_count, norm_stream_buffer_max, NormStreamGetBufferUsage(norm_tx_stream));
NormSetWatermark(norm_session, norm_tx_stream, true);
norm_watermark_pending = true;
}
}
} // end NorpSession::FlushNormStream()
bool NorpSession::OnNorpMsgTimeout(ProtoTimer& theTimer)
{
PLOG(PL_DETAIL, "NorpSession::OnNorpMsgTimeout() repeat count = %d\n", theTimer.GetRepeatCount());
if (0 != theTimer.GetRepeatCount())
{
struct timeval currentTime;
ProtoSystemTime(currentTime);
norp_msg.SetTimestamp(currentTime);
if (SOCKS_PUT_REQUEST == socks_state)
{
if (!SendMessage(norp_msg))
PLOG(PL_ERROR, "NorpSession::OnNorpMsgTimeout() warning: unable to send message (retries remaining %d)\n", theTimer.GetRepeatCount());
}
else
{
if (!controller.SendMessage(norp_msg, norp_remote_addr))
PLOG(PL_ERROR, "NorpSession::OnNorpMsgTimeout() warning: unable to send message (retries remaining %d)\n", theTimer.GetRepeatCount());
}
}
else
{
PLOG(PL_ERROR, "NorpSession::OnNorpMsgTimeout() error: message retransmission timeout with no reply\n");
switch (socks_state)
{
case SOCKS_PUT_REQUEST:
if (!MakeDirectConnect())
{
PLOG(PL_ERROR, "NorpSession::OnNorpMsgTimeout() error: also unable to make direct TCP connection\n");
Shutdown();
}
return false; // MakeDirectConnect() kills timer
break;
case SOCKS_PUT_REPLY:
Shutdown();
break;
case SOCKS_SHUTDOWN:
Close();
return false;
default:
ASSERT(0); // shouldn't be messaging in any other states
break;
}
}
return true;
} // end NorpSession::OnNorpMsgTimeout()
void NorpSession::OnNormEvent(NormEvent& theEvent)
{
PLOG(PL_DETAIL, "NorpSession::OnNormEvent(");
switch (theEvent.type)
{
case NORM_TX_QUEUE_VACANCY:
PLOG(PL_DETAIL, "NORM_TX_QUEUE_VACANCY)\n");
case NORM_TX_QUEUE_EMPTY:
if (NORM_TX_QUEUE_EMPTY == theEvent.type) PLOG(PL_DETAIL, "NORM_TX_QUEUE_EMPTY)\n");
if (IsRemoteOriginator())
{
// If there's pending data from the SOCKS client, try to send it
if (0 != client_pending)
{
if (!PutRemoteData())
{
PLOG(PL_ERROR, "NorpSession::OnNormEvent(TX_VACANCY) error: PutRemoteData() failure!\n");
Shutdown();
}
}
}
else
{
if (0 != remote_pending)
{
if (!PutClientData())
{
PLOG(PL_ERROR, "NorpSession::OnNormEvent(TX_VACANCY) error: PutClientData() failure!\n");
Shutdown();
}
}
}
break;
case NORM_TX_FLUSH_COMPLETED:
PLOG(PL_DETAIL, "NORM_TX_FLUSH_COMPLETED)\n");
break;
case NORM_TX_WATERMARK_COMPLETED:
PLOG(PL_DETAIL, "NORM_TX_WATERMARK_COMPLETED)\n");
if (NORM_ACK_SUCCESS == NormGetAckingStatus(norm_session))
{
PLOG(PL_DETAIL, " acking status: SUCCESS\n");
//TRACE("successful watermark ACK (buffer count:%lu max:%lu)\n", norm_stream_buffer_count, norm_stream_buffer_max);
norm_watermark_pending = false;
norm_stream_buffer_count -= (norm_stream_buffer_max >> 1);
//TRACE(" (count reduced to %lu)\n", norm_stream_buffer_count);
// Use as prompt to send pending data to NORM stream
if (IsRemoteOriginator())
{
// If there's pending data from the SOCKS client, try to send it
if (0 != client_pending)
{
if (!PutRemoteData())
{
PLOG(PL_ERROR, "NorpSession::OnNormEvent(TX_VACANCY) error: PutRemoteData() failure!\n");
Shutdown();
}
}
}
else
{
if (0 != remote_pending)
{
if (!PutClientData())
{
PLOG(PL_ERROR, "NorpSession::OnNormEvent(TX_VACANCY) error: PutClientData() failure!\n");
Shutdown();
}
}
}
if (!persist_start_time.IsZero())
persist_start_time.Zeroize(); // resets persistence timeout
}
else if (NORM_ACK_FAILURE == NormGetAckingStatus(norm_session))
{
PLOG(PL_DETAIL, " acking status: FAILURE\n");
if (persist_interval >= 0.0)
{
// Finite persistence will be observed
if (persist_start_time.IsZero())
{
// Initialize persistence timeout
persist_start_time.GetCurrentTime();
}
else
{
ProtoTime currentTime;
currentTime.GetCurrentTime();
if ((currentTime - persist_start_time) > persist_interval)
{
NormStreamClose(norm_tx_stream);
norm_tx_stream = NORM_OBJECT_INVALID;
Shutdown();
break;
}
}
}
NormResetWatermark(norm_session);
}
break;
case NORM_TX_CMD_SENT:
PLOG(PL_DETAIL, "NORM_TX_CMD_SENT)\n");
break;
case NORM_TX_OBJECT_SENT:
PLOG(PL_DETAIL, "NORM_TX_OBJECT_SENT)\n");
break;
case NORM_TX_OBJECT_PURGED:
PLOG(PL_DETAIL, "NORM_TX_OBJECT_PURGED)\n");
if (theEvent.object == norm_tx_stream)
{
ASSERT(SOCKS_SHUTDOWN == socks_state);
// do we need to call NormStreamClose() here?
NormStreamClose(norm_tx_stream);
norm_tx_stream = NORM_OBJECT_INVALID;
if (ShutdownComplete()) Shutdown();
}
break;
case NORM_TX_RATE_CHANGED:
PLOG(PL_DETAIL, "NORM_TX_RATE_CHANGED)\n");
break;
case NORM_LOCAL_SENDER_CLOSED:
PLOG(PL_DETAIL, "NORM_LOCAL_SENDER_CLOSED)\n");
break;
case NORM_REMOTE_SENDER_NEW:
{
PLOG(PL_DETAIL, "NORM_REMOTE_SENDER_NEW)\n");
char addrBuffer[16];
unsigned int addrLen = 16;
UINT16 sndrPort;
NormNodeGetAddress(theEvent.sender, addrBuffer, &addrLen, &sndrPort);
ProtoAddress sndrAddr;
switch (addrLen)
{
case 4:
sndrAddr.SetRawHostAddress(ProtoAddress::IPv4, addrBuffer, 4);
break;
case 16:
sndrAddr.SetRawHostAddress(ProtoAddress::IPv6, addrBuffer, 16);
break;
default:
TRACE("invalid remote sender address ?!\n");
break;
}
#ifdef NEW_PORT
TRACE("new remote sender address %s/%hu (orig:%d) ...\n", sndrAddr.GetHostString(), sndrPort, IsRemoteOriginator());
if (!IsRemoteOriginator() && !norm_sender_heard)
{
NormChangeDestination(theEvent.session, sndrAddr.GetHostString(), sndrPort, true);
norm_sender_heard = true;
//NormSetRxPortReuse(theEvent.session, true, NULL, sndrAddr.GetHostString(), sndrPort);
}
#endif // NEW_PORT
break;
}
case NORM_REMOTE_SENDER_ACTIVE:
PLOG(PL_DETAIL, "NORM_REMOTE_SENDER_ACTIVE)\n");
break;
case NORM_REMOTE_SENDER_INACTIVE:
PLOG(PL_DETAIL, "NORM_REMOTE_SENDER_INACTIVE)\n");
break;
case NORM_REMOTE_SENDER_PURGED:
PLOG(PL_DETAIL, "NORM_REMOTE_SENDER_PURGED)\n");
break;
case NORM_RX_CMD_NEW:
PLOG(PL_DETAIL, "NORM_RX_CMD_NEW)\n");
break;
case NORM_RX_OBJECT_NEW:
PLOG(PL_DETAIL, "NORM_RX_OBJECT_NEW)\n");
// TBD - confirm it's a stream, etc?
norm_rx_stream = theEvent.object;
break;
case NORM_RX_OBJECT_INFO:
PLOG(PL_DETAIL, "NORM_RX_OBJECT_INFO)\n");
break;
case NORM_RX_OBJECT_UPDATED:
PLOG(PL_DETAIL, "NORM_RX_OBJECT_UPDATED)\n");
ASSERT(theEvent.object == norm_rx_stream);
norm_rx_pending = true; // signals there is NORM rx data ready for reading
if (IsRemoteOriginator())
{
// we're the remote session originator (read data from NORM and put to local SOCKS client)
if (0 == remote_pending)
{
// try to read more data from NORM into "remote_buffer" and put to SOCKS client
if (!GetRemoteData())
{
PLOG(PL_ERROR, "NorpSession::OnNormEvent(RX_UPDATE) error: GetRemoteData() failure!\n");
Shutdown(); // TBD - shut down more gracefully (informing remote)
}
}
// else there's still data in remote_buffer pending relay to SOCKS client
// TBD - should we go ahead and append more data to remote_buffer?
// (for now, PutClientData() will call GetRemoteData() for more when relay to client completes
}
else
{
// we're the remote correspondent (read data from NORM and put to remote)
if ((0 == client_pending) && ((SOCKS_CONNECTED == socks_state) || (SOCKS_SHUTDOWN == socks_state)))
{
// try to read more data from NORM into "client_buffer" and put to SOCKS destination
if (!GetClientData())
{
PLOG(PL_ERROR, "NorpSession::OnNormEvent(RX_UPDATE) error: GetRemoteData() failure!\n");
Shutdown(); // TBD - shut down more gracefully (informing remote)
}
}
// else there's still data in client_buffer pending relay to TCP destination
// TBD - should we go ahead and append more data to client_buffer?
// (for now, PutRemoteData() will call GetClientData() for more when relay to remote completes
}
break;
case NORM_RX_OBJECT_COMPLETED:
PLOG(PL_DETAIL, "NORM_RX_OBJECT_COMPLETED)\n");
if (IsRemoteOriginator())
{
if (SOCKS_CONNECTED == socks_state)
{
socks_state = SOCKS_SHUTDOWN;
// Make sure our socks_client_socket gets shut down
if (0 == remote_pending)
PutClientData(); // will shutdown our socks_client_socket
// else socks_client_socket output notification will call PutClientData()
// TBD - if the remote obj is completed, then there's no use in pushing more data their way?
// Gracefully shut down our norm_tx_stream (requesting an ACK from the correspondent)
NormStreamClose(norm_tx_stream, true); // Note our "trick" here to do a graceful close, _then_ watermark
NormSetWatermark(norm_session, norm_tx_stream, true); // future NORM API will add "bool watermark" option to graceful close
}
else
{
ASSERT(!socks_client_socket.IsOpen());
}
}
else
{
if (SOCKS_CONNECTED == socks_state)
{
socks_state = SOCKS_SHUTDOWN;
if (0 == client_pending)
PutRemoteData(); // will shutdown our socks_remote_socket
// else socks_remote_socket output notification will call PutClientData()
// Gracefully shut down our norm_tx_stream (requesting an ACK from the originator)
NormStreamClose(norm_tx_stream, true); // Note our "trick" here to do a graceful close, _then_ watermark
NormSetWatermark(norm_session, norm_tx_stream, true); // future NORM API will add "bool watermark" option to graceful close
}
else
{
ASSERT(!socks_remote_socket.IsOpen());
}
}
norm_rx_stream = NORM_OBJECT_INVALID;
norm_rx_pending = false;
if (ShutdownComplete()) Close();
break;
case NORM_SEND_ERROR:
PLOG(PL_ERROR, "NorpSession::OnNormEvent(NORM_SEND_ERROR) error ...\n");//: remote session unexpectedly closed!\n");
Close();
break;
case NORM_RX_OBJECT_ABORTED:
PLOG(PL_DETAIL, "NORM_RX_OBJECT_ABORTED)\n");
break;
case NORM_GRTT_UPDATED:
PLOG(PL_DETAIL, "NORM_GRTT_UPDATED)\n");
break;
case NORM_CC_ACTIVE:
PLOG(PL_DETAIL, "NORM_CC_ACTIVE)\n");
break;
case NORM_CC_INACTIVE:
PLOG(PL_DETAIL, "NORM_CC_INACTIVE)\n");
break;
case NORM_EVENT_INVALID:
PLOG(PL_DETAIL, "NORM_EVENT_INVALID)\n");
break;
default:
PLOG(PL_DETAIL, "UNKNOWN event %d)\n", theEvent.type);
break;
}
} // end NorpSession::OnNormEvent()
NorpPreset::NorpPreset(Norp& norpController, UINT16 tcpPort, const ProtoAddress& dstAddr, const ProtoAddress& norpAddr)
: controller(norpController), tcp_port(tcpPort), dst_addr(dstAddr), norp_addr(norpAddr), server_socket(ProtoSocket::TCP)
{
server_socket.SetNotifier(&(controller.GetSocketNotifier()));
server_socket.SetListener(this, &NorpPreset::OnSocketEvent);
}
NorpPreset::~NorpPreset()
{
if (server_socket.IsOpen()) server_socket.Close();
}
bool NorpPreset::Listen()
{
if (!server_socket.Listen(tcp_port))
{
PLOG(PL_ERROR, "NorpPreset::Listen() error: unable to establish listening TCP socket on port %hu\n", tcp_port);
return false;
}
return true;
} // end NorpPreset::Listen();
void NorpPreset::OnSocketEvent(ProtoSocket& theSocket, ProtoSocket::Event theEvent)
{
// TBD - dispatch ACCEPT events up to controller to create a corresponding
// jump-started NorpSession
PLOG(PL_DETAIL, "NorpPreset::OnSocketEvent(");
switch (theEvent)
{
case ProtoSocket::INVALID_EVENT:
PLOG(PL_DETAIL, "INVALID_EVENT) ...\n");
break;
case ProtoSocket::CONNECT:
PLOG(PL_DETAIL, "CONNECT) ...\n");
break;
case ProtoSocket::ACCEPT:
{
PLOG(PL_DETAIL, "ACCEPT) ...\n");
if (!controller.AcceptPresetClientConnection(*this))
PLOG(PL_ERROR, "Norp::OnSocketEvent(ACCEPT) error: unable to create new NorpSession\n");
break;
}
case ProtoSocket::SEND:
PLOG(PL_DETAIL, "SEND) ...\n");
break;
case ProtoSocket::RECV:
{
PLOG(PL_DETAIL, "RECV) ...\n");
break;
}
case ProtoSocket::DISCONNECT:
PLOG(PL_DETAIL, "DISCONNECT) ...\n");
break;
case ProtoSocket::EXCEPTION:
PLOG(PL_DETAIL, "EXCEPTION) ...\n");
break;
case ProtoSocket::ERROR_:
PLOG(PL_DETAIL, "ERROR_) ...\n");
break;
}
} // end NorpPreset::OnSocketEvent()
Norp::Norp(ProtoDispatcher& theDispatcher)
: dispatcher(theDispatcher), socks_server_socket(ProtoSocket::TCP), socks_port(DEFAULT_SOCKS_PORT),
next_session_id(1), session_count(0), norp_rx_socket(ProtoSocket::UDP), norp_local_port(DEFAULT_NORP_PORT),
norp_remote_port(DEFAULT_NORP_PORT), norp_rtt_init(NorpSession::NORP_RTT_DEFAULT),
norm_enable(true), norm_instance(NORM_INSTANCE_INVALID), norm_node_id(NORM_NODE_ANY),
norm_port(DEFAULT_NORM_PORT), norm_cc_mode(NORM_CC), norm_tx_rate(DEFAULT_TX_RATE), norm_tx_limit(-1.0),
norm_segment_size(1400), norm_block_size(64), norm_parity_count(0), norm_parity_auto(0), norm_trace(false)
// ,port_pool(9000)
{
socks_server_socket.SetNotifier(&GetSocketNotifier());
socks_server_socket.SetListener(this, &Norp::OnSocksServerEvent);
norp_rx_socket.SetNotifier(&GetSocketNotifier());
norp_rx_socket.SetListener(this, &Norp::OnNorpSocketEvent);
}
Norp::~Norp()
{
StopServer();
}
bool Norp::SendMessage(const NorpMsg& msg, const ProtoAddress& dstAddr)
{
unsigned int numBytes = msg.GetLength();
if (norp_rx_socket.SendTo((const char*)msg.GetBuffer(), numBytes, dstAddr))
{
if (0 == numBytes)
{
PLOG(PL_WARN, "Norp::SendMessage() norp_rx_socket.SendTo() error: %s\n", GetErrorString());
return false;
}
}
else
{
PLOG(PL_ERROR, "Norp::SendMessage() norp_rx_socket.SendTo() error: %s\n", GetErrorString());
return false;
}
return true;
} // end Norp::SendMessage()
bool Norp::StartServer(bool normEnable)
{
if (!socks_server_socket.Listen(socks_port))
{
PLOG(PL_ERROR, "Norp::StartServer() error: socks server socket listen(%d) error\n", socks_port);
return false;
}
norm_enable = normEnable;
if (normEnable)
{
norm_instance = NormCreateInstance();
if (NORM_INSTANCE_INVALID == norm_instance)
{
PLOG(PL_ERROR, "Norp::StartServer() error: NormCreateInstance() failure!\n");
StopServer();
return false;
}
NormDescriptor normDescriptor = NormGetDescriptor(norm_instance);
if (!dispatcher.InstallGenericInput(normDescriptor, NormEventCallback, this))
{
PLOG(PL_ERROR, "Norp::StartServer() error: unable to install NORM event notification!\n");
StopServer();
return false;
}
if (!norp_rx_socket.Open(norp_local_port))
{
PLOG(PL_ERROR, "Norp::StartServer() error: unable to open NORP signaling socket!\n");
StopServer();
return false;
}
if (NORM_NODE_ANY == norm_node_id)
norm_node_id = proxy_addr.GetEndIdentifier(); // TBD - make sure proxy_addr.IsValid() ???
}
else
{
norm_node_id = NORM_NODE_NONE;
}
PLOG(PL_INFO, "norp: created controller on NORP port %hu with local NORM node id: %08x\n", norp_local_port, norm_node_id);
return true;
} // end Norp::StartServer()
void Norp::StopServer()
{
norp_rx_socket.Close();
socks_server_socket.Close();
session_list.Destroy();
preset_list.Destroy();
if (NORM_INSTANCE_INVALID != norm_instance)
{
dispatcher.RemoveGenericInput(NormGetDescriptor(norm_instance));
NormDestroyInstance(norm_instance);
norm_instance = NORM_INSTANCE_INVALID;
}
} // end Norp::StopServer()
void Norp::OnSessionClose(NorpSession& theSession)
{
RemoveSession(theSession);
PLOG(PL_INFO, "norp: deleting closed session %u\n", theSession.GetSessionId());
delete &theSession;
} // end Norp::OnSessionClose();
bool Norp::AcceptPresetClientConnection(NorpPreset& preset)
{
// Create a new NorpSession for this prest client connection
NorpSession* norpSession = new NorpSession(*this, next_session_id++);
if (NULL == norpSession)
{
PLOG(PL_ERROR, "Norp::AcceptPresetClientConnection() new NorpSession error: %s\n", GetErrorString());
return false;
}
if (!norpSession->AcceptPresetClientConnection(preset, norm_enable))
{
PLOG(PL_ERROR, "Norp::AcceptPresetClientConnection() error: unable to accept connection\n");
delete norpSession;
return false;
}
norpSession->SetProxyAddress(proxy_addr);
AddSession(*norpSession);
PLOG(PL_INFO, "norp: session %hu accepted preset connection from %s\n", norpSession->GetSessionId(), norpSession->GetClientAddress().GetHostString());
return true;
} // end Norp::AcceptPresetClientConnection()
void Norp::OnSocksServerEvent(ProtoSocket& theSocket,
ProtoSocket::Event theEvent)
{
PLOG(PL_DETAIL, "Norp::OnServerSocketEvent(");
switch (theEvent)
{
case ProtoSocket::INVALID_EVENT:
PLOG(PL_DETAIL, "INVALID_EVENT) ...\n");
break;
case ProtoSocket::CONNECT:
PLOG(PL_DETAIL, "CONNECT) ...\n");
break;
case ProtoSocket::ACCEPT:
{
PLOG(PL_DETAIL, "ACCEPT) ...\n");
// Create a new NorpSession for this potential client
NorpSession* norpSession = new NorpSession(*this, next_session_id++);
if (NULL == norpSession)
{
PLOG(PL_ERROR, "Norp::OnSocksServerEvent() new NorpSession error: %s\n", GetErrorString());
break;
}
if (!norpSession->AcceptClientConnection(socks_server_socket, norm_enable))
// Accept the socket connection into this session's "socks_client_socket"
//if (!socks_server_socket.Accept(&norpSession->AccessSocksClientSocket()))
{
PLOG(PL_ERROR, "Norp::OnSocksServerEvent(ACCEPT) error accepting connection\n");
delete norpSession;
break;
}
norpSession->SetProxyAddress(proxy_addr);
AddSession(*norpSession);
PLOG(PL_INFO, "norp: session %hu accepted connection from %s\n", norpSession->GetSessionId(), norpSession->GetClientAddress().GetHostString());
break;
}
case ProtoSocket::SEND:
PLOG(PL_DETAIL, "SEND) ...\n");
break;
case ProtoSocket::RECV:
{
PLOG(PL_DETAIL, "RECV) ...\n");
break;
}
case ProtoSocket::DISCONNECT:
PLOG(PL_DETAIL, "DISCONNECT) ...\n");
break;
case ProtoSocket::EXCEPTION:
PLOG(PL_DETAIL, "EXCEPTION) ...\n");
break;
case ProtoSocket::ERROR_:
PLOG(PL_DETAIL, "ERROR_) ...\n");
break;
}
} // end Norp::OnSocksServerEvent()
void Norp::AddSession(NorpSession& session)
{
session_list.Insert(session);
session_count++;
if (norm_tx_limit >= 0.0)
{
double lowerLimit = 0.9 * (norm_tx_limit / (double)session_count);
NorpSessionList::Iterator iterator(session_list);
NorpSession* nextSession;
while (NULL != (nextSession = iterator.GetNextItem()))
nextSession->SetNormRateBounds(lowerLimit, norm_tx_limit);
}
} // end Norp::AddSession()
void Norp::RemoveSession(NorpSession& session)
{
session_list.Remove(session);
session_count--;
if ((session_count > 0) && (norm_tx_limit >= 0.0))
{
double lowerLimit = 0.9 * (norm_tx_limit / (double)session_count);
NorpSessionList::Iterator iterator(session_list);
NorpSession* nextSession;
while (NULL != (nextSession = iterator.GetNextItem()))
nextSession->SetNormRateBounds(lowerLimit, norm_tx_limit);
}
} // end Norp::RemoveSession()
bool Norp::AddPreset(UINT16 tcpPort, const ProtoAddress& dstAddr, const ProtoAddress& norpAddr)
{
NorpPreset* preset = new NorpPreset(*this, tcpPort, dstAddr, norpAddr);
if (NULL == preset)
{
PLOG(PL_ERROR, "Norp::AddPreset() new NorpPreset error: %s\n", GetErrorString());
return false;
}
if (!preset->Listen())
{
PLOG(PL_ERROR, "Norp::AddPreset() error: unable to create listening TCP socket\n");
delete preset;
return false;
}
preset_list.Insert(*preset);
return true;
} // end Norp::AddPreset()
void Norp::OnNorpSocketEvent(ProtoSocket& theSocket,
ProtoSocket::Event theEvent)
{
if (ProtoSocket::RECV == theEvent)
{
UINT32 buffer[1024/4];
ProtoAddress senderAddr;
for(;;)
{
unsigned int numBytes = 1024;
if (!theSocket.RecvFrom((char*)buffer, numBytes, senderAddr))
{
NorpSession* theSession = (NorpSession*)(theSocket.GetUserData());
if ((NULL != theSession) && (NorpSession::SOCKS_PUT_REQUEST == theSession->GetSocksState()))
{
// Assume it is an ICMP "port unreachable" error in response to NORP request
PLOG(PL_INFO, "Norp::OnNorpSocketEvent() remote NORP server port unreachable?\n");
if (!theSession->MakeDirectConnect())
{
PLOG(PL_ERROR, "Norp::OnNorpSocketEvent(RECV) error: unable to make direct connection\n");
theSession->Shutdown();
}
break;
}
}
if (0 == numBytes) break; // nothing left to read
// TBD - make sure received command is at least 20 bytes (NORP header)
NorpMsg msg(buffer, numBytes);
switch (msg.GetType())
{
case NorpMsg::SOCKS_REQ:
{
PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received SOCKS_REQ session %d:%hu\n", msg.GetNodeId(), msg.GetSessionId());
OnRemoteRequest(msg, senderAddr);
break;
}
case NorpMsg::REQ_ACK:
{
PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received REQ_ACK session %d:%hu\n", msg.GetNodeId(), msg.GetSessionId());
NorpSession* session = session_list.FindSession(msg.GetSessionId());
// TBD - confirm the session found is for the given node id, etc
if (NULL != session)
{
if (!session->OnRemoteRequestAcknowledgment(msg, senderAddr))
session->Shutdown();
}
else
{
PLOG(PL_ERROR, "Norp::OnNorpSocketEvent(RECV) warning: received remote REQ_ACK for unknown session\n");
}
break;
}
case NorpMsg::SOCKS_REP:
{
PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received SOCKS_REP session %d:%hu (length:%d)\n", msg.GetNodeId(), msg.GetSessionId(), msg.GetLength());
NorpSession* session = session_list.FindSession(msg.GetSessionId());
if (NULL != session)
{
// TBD - confirm the session found is for the given node id, etc
if (!session->OnRemoteReply(msg, senderAddr))
session->Shutdown();
}
else
{
PLOG(PL_ERROR, "Norp::OnNorpSocketEvent(RECV) warning: received remote SOCKS_REPLY for unknown session\n");
}
break;
}
case NorpMsg::REP_ACK:
{
PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received REP_ACK session %d:%hu\n", msg.GetNodeId(), msg.GetSessionId());
NorpSession* session = session_list.FindSession(msg.GetSessionId(), msg.GetNodeId());
if (NULL != session)
{
if (!session->OnRemoteReplyAcknowledgment(msg))
session->Shutdown();
}
else
{
PLOG(PL_ERROR, "Norp::OnNorpSocketEvent(RECV) warning: received remote REP_ACK for unknown session\n");
}
break;
}
case NorpMsg::ORIG_END:
{
PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received ORIG_END session %d:%hu\n", msg.GetNodeId(), msg.GetSessionId());
NorpSession* session = session_list.FindSession(msg.GetSessionId(), msg.GetNodeId());
if (NULL != session)
{
// TBD - mroe gently end things (e.g. let socket shutdowns commence)???
session->Close(); // the session will make an upcall and get itself removed/deleted
}
UINT32 buffer[20/sizeof(UINT32)];
NorpMsg ack(buffer, 20, false);
ack.SetType(NorpMsg::ACK_END);
ack.SetSessionId(msg.GetSessionId());
ack.SetNodeId(NORM_NODE_NONE);
if (!SendMessage(ack, senderAddr))
PLOG(PL_ERROR, "Norp::OnNorpSocketEvent(RECV) warning: unable to send ACK_END message to originator\n");
break;
}
case NorpMsg::CORR_END:
{
PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received CORR_END session %d:%hu\n", msg.GetNodeId(), msg.GetSessionId());
NorpSession* session = session_list.FindSession(msg.GetSessionId(), NORM_NODE_NONE);
if (NULL != session)
{
// TBD - mroe gently end things (e.g. let socket shutdowns commence)???
session->Close(); // the session will make an upcall and get itself removed/deleted
}
UINT32 buffer[20/sizeof(UINT32)];
NorpMsg ack(buffer, 20, false);
ack.SetType(NorpMsg::ACK_END);
ack.SetSessionId(msg.GetSessionId());
ack.SetNodeId(GetNormNodeId());
if (!SendMessage(ack, senderAddr))
PLOG(PL_ERROR, "Norp::OnNorpSocketEvent(RECV) warning: unable to send ACK_END message to correspondent\n");
break;
}
case NorpMsg::ACK_END:
{
PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received ACK_END session %d:%hu\n", msg.GetNodeId(), msg.GetSessionId());
NorpSession* session = session_list.FindSession(msg.GetSessionId(), msg.GetNodeId());
if (NULL != session) session->Close(); // the session will make an upcall and get itself removed/deleted
break;
}
default:
{
PLOG(PL_ERROR, "Norp::OnNormEvent(NORM_RX_CMD_NEW) error: received invalid NORP command: %d\n", msg.GetType());
break;
}
} // end switch(msg.GetType())
numBytes = 1024;
} // end while(theSocket.RecvFrom())
} // end if (ProtoSocket::RECV == theEvent)
else
{
PLOG(PL_DETAIL, "unhandled NORP socket event!\n");
}
} // end Norp::OnNorpSocketEvent()
void Norp::OnRemoteRequest(const NorpMsg& msg, const ProtoAddress& senderAddr)
{
NorpSession* session = session_list.FindSession(msg.GetSessionId(), msg.GetNodeId());
if (NULL == session)
{
PLOG(PL_INFO, "norp: new remote session id %hu:%08x\n", msg.GetSessionId(), msg.GetNodeId());
// It's a new remote session
session = new NorpSession(*this, msg.GetSessionId(), msg.GetNodeId());
if (NULL == session)
{
PLOG(PL_ERROR, "Norp::OnRemoteRequest() new NorpSession error: %s\n", GetErrorString());
// TBD - should we somehow signal the remote of our failure?
// (just let it time out for now)
return;
}
AddSession(*session);
}
if (!session->OnRemoteRequest(msg, senderAddr))
{
PLOG(PL_ERROR, "Norp::OnRemoteRequest() error: unable to initiate new remote NorpSession\n");
// TBD - signal failure to remote
RemoveSession(*session);
delete session;
}
} // end Norp::OnRemoteRequest()
void Norp::NormEventCallback(ProtoDispatcher::Descriptor descriptor,
ProtoDispatcher::Event theEvent,
const void* userData)
{
if (ProtoDispatcher::EVENT_INPUT == theEvent)
{
Norp* norp = (Norp*)(userData);
norp->OnNormEvent();
}
else
{
ASSERT(0); // should never happen
}
} // end Norp::NormEventCallback()
void Norp::OnNormEvent()
{
NormEvent theEvent;
// We make a looping, non-blocking call to NormGetNextEvent() here
// to consume all available NORM notification events
while (NormGetNextEvent(norm_instance, &theEvent, false))
{
PLOG(PL_DETAIL, "Norp::OnNormEvent(");
// See if this event is for a "child" NorpSession
if (NORM_SESSION_INVALID != theEvent.session)
{
NorpSession* norpSession = (NorpSession*)NormGetUserData(theEvent.session);
if (NULL != norpSession)
{
PLOG(PL_DETAIL, "session %hu event)\n", norpSession->GetSessionId());
norpSession->OnNormEvent(theEvent);
continue;
}
}
else
{
//ASSERT(0); // should never happen
}
}
} // end Norp::OnNormEvent()