RetroShare/libretroshare/src/pqi/pqissludp.cc

1308 lines
32 KiB
C++
Raw Normal View History

/*
* "$Id: pqissludp.cc,v 1.16 2007-02-18 21:46:49 rmf24 Exp $"
*
* 3P/PQI network interface for RetroShare.
*
* Copyright 2004-2006 by Robert Fernie.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License Version 2 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*
* Please report all bugs and problems to "retroshare@lunamutt.com".
*
*/
#include "pqi/pqissludp.h"
#include "pqi/pqiudpproxy.h"
#include "pqi/pqinetwork.h"
#include "tcponudp/tou.h"
#include "tcponudp/bio_tou.h"
#include <errno.h>
#include <openssl/err.h>
#include "pqi/pqidebug.h"
#include <sstream>
const int pqissludpzone = 3144;
static const int PQI_SSLUDP_STUN_TIMEOUT = 2;
static const int PQI_SSLUDP_STUN_ATTEMPTS = 15;
/* we need a long timeout to give the local address
* discovery a chance. This must also allow for the
* time for the peer to check for a proxy connect (before lad starts).
* Remote = Check Time + 40
*/
static const int PQI_SSLUDP_PROXY_CHECK_TIME = 29;
static const int PQI_SSLUDP_REMOTE_TIMEOUT = 70;
/* we need a long timeout to give the udpproxy time
* to find proxies (at startup)
* this is only an emergency timeout anyway
*/
static const int PQI_SSLUDP_PROXY_TIMEOUT = 180;
/* a final timeout, to ensure this never blocks completely
* 300 secs to complete udp/tcp/ssl connection.
* This is long as the udp connect can take some time.
*/
static const int PQI_SSLUDP_CONNECT_TIMEOUT = 300;
/********** PQI SSL UDP STUFF **************************************/
pqissludp::pqissludp(cert *c, PQInterface *parent, pqiudpproxy *prxy)
:pqissl(c, NULL, parent), tou_bio(NULL), udpproxy(prxy),
listen_checktime(0)
{
sslmode = PQISSL_PASSIVE;
stun_addr.sin_addr.s_addr = 0;
return;
}
pqissludp::~pqissludp()
{
pqioutput(PQL_ALERT, pqissludpzone,
"pqissludp::~pqissludp -> destroying pqissludp (+ pqiudproxy)");
/* must call reset from here, so that the
* virtual functions will still work.
* -> as they stop working in base class destructor.
*
* This means that reset() will be called twice, but this should
* be harmless.
*/
stoplistening(); /* remove from p3proxy listenqueue */
reset();
if (udpproxy)
{
delete udpproxy;
}
if (tou_bio) // this should be in the reset?
{
BIO_free(tou_bio);
}
return;
}
int pqissludp::reset()
{
/* and reset the udpproxy */
if (udpproxy)
{
udpproxy->reset();
}
/* reset for next time.*/
stun_attempts = 0;
return pqissl::reset();
}
void *pqissludp::generate_stun_pkt(struct sockaddr_in *stun_addr, int *len)
{
/* just the header */
void *stun_pkt = malloc(20);
((uint16_t *) stun_pkt)[0] = 0x0001;
((uint16_t *) stun_pkt)[1] = 0x0020; /* only header */
/* transaction id - should be random */
((uint32_t *) stun_pkt)[1] = 0x0020;
((uint32_t *) stun_pkt)[2] = 0x0121;
((uint32_t *) stun_pkt)[3] = 0x0111;
((uint32_t *) stun_pkt)[4] = 0x1010;
*len = 20;
return stun_pkt;
}
int pqissludp::getStunReturnedAddr(void *stun_pkt, int len, struct sockaddr_in *fw_addr)
{
if (((uint16_t *) stun_pkt)[0] != 0x0101)
{
/* not a response */
return 0;
}
/* iterate through the packet */
/* for now assume the address follows the header directly */
fw_addr->sin_family = AF_INET;
fw_addr->sin_addr.s_addr = ((uint32_t *) stun_pkt)[6];
fw_addr->sin_port = ((uint16_t *) stun_pkt)[11];
return 1;
}
// Udp Proxy service is started by the pqiudpproxy.
// connection is attempted by searching for common neighbours.
// Proxy Pkts are exchanged and an exchange of external addresses
// is done. these are passed to pqissludp, through a series
// of function calls. should all work perfectly.
//
int pqissludp::Reattempt_Connection()
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::Reattempt_Connection() Failed Doing nothing");
// notify the parent.
waiting = WAITING_NOT;
return -1;
}
/* <===================== UDP Difference *******************/
// The Proxy Version takes a few more step
//
// connectInterface is sent via message from the proxy.
// and is set here.
/* <===================== UDP Difference *******************/
int pqissludp::attach(struct sockaddr_in &addr)
{
if (waiting != WAITING_PROXY_CONNECT)
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::attach() not WAITING_PROXY_CONNECT");
/* should be attached already! */
return 1;
}
sockfd = tou_socket(0,0,0);
if (0 > sockfd)
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::attach() failed to create a socket");
return -1;
}
/* just fix the address to start with */
int err = -1;
int fails = 0;
while(0 > (err = tou_bind(sockfd, (struct sockaddr *) &addr, sizeof(addr))))
{
addr.sin_port = htons(ntohs(addr.sin_port) + 1);
std::ostringstream out;
out << "pqissludp::attach() Changing udp bind address to: ";
out << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port);
pqioutput(PQL_WARNING, pqissludpzone, out.str());
if (fails++ > 20)
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::attach() Too many tou_bind attempts");
net_internal_close(sockfd);
sockfd = -1;
return 0;
}
}
// setup remote address
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::attach() opening Local Udp Socket");
return 1;
}
// find a suitable STUN client.
// This is choosen from a list of neighbours. (AutoDiscovery)
// and failing a suitable STUN, uses googles STUN servers.
// send out message.
int pqissludp::Request_Proxy_Connection()
{
/* to start the whole thing rolling we need, to be connected
* (this don't match the fn name)
*/
/* so start a connection */
waiting = WAITING_PROXY_CONNECT;
proxy_timeout = time(NULL) + PQI_SSLUDP_PROXY_TIMEOUT;
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::Request_Proxy_Connection() connectattempt!");
return udpproxy -> connectattempt();
}
int pqissludp::Check_Proxy_Connection()
{
int mode;
if (udpproxy -> isConnected(mode))
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Check_Proxy_Connection() isConnected!");
sslmode = mode;
waiting = WAITING_PROXY_CONNECT;
/* This will switch into WAITING_LOCAL_ADDR */
return Request_Local_Address();
}
if (udpproxy -> hasFailed())
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Check_Proxy_Connection() hasFailed!");
/* failure */
if (parent())
{
/* only notify of failure, if its an active connect attempt.
* ie not if waiting == WAITING_NOT and we are listening...
*/
if (waiting == WAITING_PROXY_CONNECT)
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Check_Proxy_Connection() notifying parent - hasFailed!");
udpproxy->reset();
if (parent())
{
parent() -> notifyEvent(this, NET_CONNECT_FAILED);
}
}
else
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Check_Proxy_Connection() listen - not Connected....Ignore");
}
}
else
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Check_Proxy_Connection() no parent to notify - hasFailed!");
}
waiting = WAITING_FAIL_INTERFACE;
return -1;
}
/* finally a proxy timeout - shouldn't be needed, but you don't know....
*/
if (waiting == WAITING_PROXY_CONNECT)
{
if (proxy_timeout < time(NULL))
{
pqioutput(PQL_ALERT, pqissludpzone,
"pqissludp::Check_Proxy_Connection() Proxy Connect Timed Out!");
udpproxy->reset();
if (parent())
{
parent() -> notifyEvent(this, NET_CONNECT_FAILED);
}
waiting = WAITING_FAIL_INTERFACE;
return -1;
}
}
return 0;
}
int pqissludp::Request_Local_Address()
{
// reset of stun_addr, restarts attempts.
if (stun_addr.sin_addr.s_addr == 0)
{
stun_attempts = 0;
}
udpproxy->requestStunServer(stun_addr);
/* check its valid */
if (!isValidNet((&(stun_addr.sin_addr))))
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Request_Local_Address() No StunServers!");
/* must do this manually - as we don't know if ::reset() will send notification.
*/
udpproxy->reset();
if (sockfd > 0) /* might be valid - might not */
{
net_internal_close(sockfd);
}
waiting = WAITING_FAIL_INTERFACE;
if (parent())
{
// not enough stun attempts. FAILED (not UNREACHABLE)
parent() -> notifyEvent(this, NET_CONNECT_FAILED);
}
return -1;
}
/* add +1 to get correct stun addr */
stun_addr.sin_port = htons(ntohs(stun_addr.sin_port) + 1);
/*
* send of pkt.
*/
net_attempt = PQISSL_UDP_FLAG;
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Request_Local_Address() Opening Local Socket");
/* only if the first time */
if (waiting == WAITING_PROXY_CONNECT)
{
/* open socket. */
local_addr = sslccr -> getOwnCert() -> localaddr;
if (0 > attach(local_addr))
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Request_Local_Address() Failed to Attach");
udpproxy->reset();
waiting = WAITING_FAIL_INTERFACE;
parent() -> notifyEvent(this, NET_CONNECT_FAILED);
return -1;
}
}
/* then send packet */
int len;
void *stun_pkt = generate_stun_pkt(&stun_addr, &len);
if (!tou_sendto(sockfd, stun_pkt, len, 0,
(const sockaddr *) &stun_addr, sizeof(stun_addr)))
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Request_Local_Address() Failed to Stun");
free(stun_pkt);
return -1;
}
{
std::ostringstream out;
out << "pqissludp::Request_Local_Address() Sent StunPkt to: ";
out << inet_ntoa(stun_addr.sin_addr);
out << ":" << ntohs(stun_addr.sin_port);
pqioutput(PQL_WARNING, pqissludpzone, out.str());
}
free(stun_pkt);
waiting = WAITING_LOCAL_ADDR;
stun_timeout = time(NULL) + PQI_SSLUDP_STUN_TIMEOUT;
return 0;
}
int pqissludp::Determine_Local_Address()
{
/* have we recieved anything from the Stun server? */
/* now find the listenUdp port */
struct sockaddr_in stun_addr_in;
socklen_t addrlen = sizeof(stun_addr_in);
int size = 1000;
char data[size];
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::Determine_Local_Address() Waiting Local Addr - Can't do nothing,!");
size = tou_recvfrom(sockfd, data, size, 0,
(struct sockaddr *) &stun_addr_in, &addrlen);
if (0 < size)
{
{
std::ostringstream out;
out << "pqissludp::Determine_Local_Address() Received + Decoding Stun Reply: " << size;
pqioutput(PQL_DEBUG_BASIC, pqissludpzone, out.str());
}
/* get the firewall_address out.
*/
if (getStunReturnedAddr(data, size, &firewall_addr))
{
std::ostringstream out;
out << "pqissludp::Listen_for_Proxy() Got ExtAddr: ";
out << inet_ntoa(firewall_addr.sin_addr) << ":";
out << ntohs(firewall_addr.sin_port) << std::endl;
pqioutput(PQL_DEBUG_BASIC, pqissludpzone, out.str());
/* initiate (or complete) a connection via the proxy */
/* now tell the udpproxy our address */
udpproxy -> sendExternalAddress(firewall_addr);
waiting = WAITING_REMOTE_ADDR;
remote_timeout = time(NULL) + PQI_SSLUDP_REMOTE_TIMEOUT;
return Determine_Remote_Address();
}
else
{
std::ostringstream out;
out << "pqissludp::Listen_for_Proxy() Failed to Get StunRtn Addr";
pqioutput(PQL_DEBUG_BASIC, pqissludpzone, out.str());
}
}
// switched to time based timeout.
//if (stun_timeout++ > PQI_SSLUDP_STUN_TIMEOUT)
if (stun_timeout < time(NULL))
{
/* too many times? */
if (stun_attempts++ > PQI_SSLUDP_STUN_ATTEMPTS)
{
std::ostringstream out;
out << "pqissludp::Listen_for_Proxy() Too Many Stun Attempts";
out << std::endl;
out << "Will notifyEvent(NET_CONNECT_UNREACHABLE)";
out << std::endl;
pqioutput(PQL_ALERT, pqissludpzone, out.str());
reset();
return -1;
}
/* retry.
*/
return Request_Local_Address();
}
return 0;
}
int pqissludp::Determine_Remote_Address()
{
int ret;
int cMode;
if (waiting != WAITING_REMOTE_ADDR)
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Determine_Remote_Address() ERROR: waiting != REMOTE_ADDR!");
return -1;
}
if (0 > (ret = udpproxy -> gotRemoteAddress(remote_addr, cMode)))
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Determine_Remote_Address() FAILURE to Get Remote Address");
reset();
waiting = WAITING_FAIL_INTERFACE;
return -1;
}
if (ret == 0)
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::Determine_Remote_Address() Remote Address Acquistion in progress");
/* inprogress */
/* check if its timed out */
if (remote_timeout < time(NULL))
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Determine_Remote_Address() Timed out: NOTIFY of FAILURE");
reset();
waiting = WAITING_FAIL_INTERFACE;
return -1;
}
return 0;
}
/* success */
sslmode = cMode; /* set the connect mode */
std::ostringstream out;
out << "pqissludp::Determine_Remote_Address() Success:";
out << "Remote Address: " << inet_ntoa(remote_addr.sin_addr) << ":";
out << ntohs(remote_addr.sin_port);
out << " sslMode: " << (int) sslmode;
pqioutput(PQL_DEBUG_BASIC, pqissludpzone, out.str());
return Initiate_Connection();
}
int pqissludp::Initiate_Connection()
{
int err;
remote_addr.sin_family = AF_INET;
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::Initiate_Connection() Attempting Outgoing Connection....");
if (waiting != WAITING_REMOTE_ADDR)
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Initiate_Connection() Already Attempt in Progress!");
return -1;
}
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::Initiate_Connection() Opening Socket");
{
std::ostringstream out;
out << "pqissludp::Initiate_Connection() ";
out << "Connecting To: " << inet_ntoa(remote_addr.sin_addr) << ":";
out << ntohs(remote_addr.sin_port) << std::endl;
pqioutput(PQL_WARNING, pqissludpzone, out.str());
}
if (remote_addr.sin_addr.s_addr == 0)
{
std::ostringstream out;
out << "pqissludp::Initiate_Connection() ";
out << "Invalid (0.0.0.0) Remote Address,";
out << " Aborting Connect.";
out << std::endl;
pqioutput(PQL_WARNING, pqissludpzone, out.str());
waiting = WAITING_FAIL_INTERFACE;
reset();
return -1;
}
{
std::ostringstream out;
out << "Connecting to ";
out << sslcert -> Name() << " via ";
out << inet_ntoa(remote_addr.sin_addr);
out << ":" << ntohs(remote_addr.sin_port);
pqioutput(PQL_DEBUG_BASIC, pqissludpzone, out.str());
}
udp_connect_timeout = time(NULL) + PQI_SSLUDP_CONNECT_TIMEOUT;
/* <===================== UDP Difference *******************/
if (0 != (err = tou_connect(sockfd, (struct sockaddr *) &remote_addr, sizeof(remote_addr))))
/* <===================== UDP Difference *******************/
{
int tou_err = tou_errno(sockfd);
std::cerr << "pqissludp::Initiate_Connection() connect returns:";
std::cerr << err << " -> errno: " << tou_err << " error: ";
std::cerr << socket_errorType(tou_err) << std::endl;
std::ostringstream out;
out << "pqissludp::Initiate_Connection()";
if ((tou_err == EINPROGRESS) || (tou_err == EAGAIN))
{
// set state to waiting.....
waiting = WAITING_SOCK_CONNECT;
out << " EINPROGRESS Waiting for Socket Connection";
pqioutput(PQL_WARNING, pqissludpzone, out.str());
return 0;
}
else if ((tou_err == ENETUNREACH) || (tou_err == ETIMEDOUT))
{
out << "ENETUNREACHABLE: cert" << sslcert -> Name();
out << std::endl;
// Then send unreachable message.
waiting = WAITING_FAIL_INTERFACE;
net_unreachable |= net_attempt;
}
out << "Error: Connection Failed: " << tou_err;
out << " - " << socket_errorType(tou_err) << std::endl;
reset();
pqioutput(PQL_WARNING, pqissludpzone, out.str());
return -1;
}
else
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Init_Connection() connect returned 0");
}
waiting = WAITING_SOCK_CONNECT;
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::Initiate_Connection() Waiting for Socket Connect");
return 1;
}
/********* VERY DIFFERENT **********/
int pqissludp::Basic_Connection_Complete()
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::Basic_Connection_Complete()...");
if (time(NULL) > udp_connect_timeout)
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::Basic_Connection_Complete() Connectoin Timed Out!");
/* as sockfd is valid, this should close it all up */
reset();
}
if (waiting != WAITING_SOCK_CONNECT)
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::Basic_Connection_Complete() Wrong Mode");
return -1;
}
/* new approach is to check for an error */
/* check for an error */
int err;
if (0 != (err = tou_errno(sockfd)))
{
if (err == EINPROGRESS)
{
std::ostringstream out;
out << "pqissludp::Basic_Connection_Complete()";
out << "EINPROGRESS: cert" << sslcert -> Name();
pqioutput(PQL_WARNING, pqissludpzone, out.str());
}
else if ((err == ENETUNREACH) || (err == ETIMEDOUT))
{
std::ostringstream out;
out << "pqissludp::Basic_Connection_Complete()";
out << "ENETUNREACH/ETIMEDOUT: cert";
out << sslcert -> Name() << std::endl;
net_unreachable |= net_attempt;
reset();
// Then send unreachable message.
waiting = WAITING_FAIL_INTERFACE;
out << "pqissludp::Basic_Connection_Complete()";
out << "Error: Connection Failed: " << err;
out << " - " << socket_errorType(err);
pqioutput(PQL_WARNING, pqissludpzone, out.str());
return -1;
}
}
/* <===================== UDP Difference *******************/
if (tou_connected(sockfd))
/* <===================== UDP Difference *******************/
{
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Basic_Connection_Complete() Connection Complete!");
return 1;
}
else
{
// not ready return -1;
pqioutput(PQL_WARNING, pqissludpzone,
"pqissludp::Basic_Connection_Complete() Not Yet Ready!");
return 0;
}
return -1;
}
/* New Internal Functions required to generalise tcp/udp version
* of the programs
*/
// used everywhere
int pqissludp::net_internal_close(int fd)
{
pqioutput(PQL_ALERT, pqissludpzone,
"pqissludp::net_internal_close() -> tou_close()");
return tou_close(fd);
}
// install udp BIO.
int pqissludp::net_internal_SSL_set_fd(SSL *ssl, int fd)
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::net_internal_SSL_set_fd()");
/* create the bio's */
tou_bio =BIO_new(BIO_s_tou_socket());
/* attach the fd's to the BIO's */
BIO_set_fd(tou_bio, fd, BIO_NOCLOSE);
SSL_set_bio(ssl, tou_bio, tou_bio);
return 1;
}
int pqissludp::net_internal_fcntl_nonblock(int fd)
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::net_internal_fcntl_nonblock()");
return 0;
}
/* These are identical to pqinetssl version */
//int pqissludp::status()
int pqissludp::tick()
{
/* check the udpproxy, if we should be listening */
if ((waiting == WAITING_NOT) && (sslcert -> Listening()))
{
if (time(NULL) > listen_checktime)
{
listen_checktime = time(NULL) +
PQI_SSLUDP_PROXY_CHECK_TIME;
/* check the Proxy anyway (every xxx secs) */
Check_Proxy_Connection();
}
}
pqissl::tick();
udpproxy->tick();
return 1;
}
// listen fns call the udpproxy.
int pqissludp::listen()
{
{
std::ostringstream out;
out << "pqissludp::listen()";
pqioutput(PQL_ALERT, pqissludpzone, out.str());
}
return udpproxy->listen();
}
int pqissludp::stoplistening()
{
{
std::ostringstream out;
out << "pqissludp::stoplistening()";
pqioutput(PQL_ALERT, pqissludpzone, out.str());
}
return udpproxy->stoplistening();
}
/********** PQI STREAMER OVERLOADING *********************************/
bool pqissludp::moretoread()
{
{
std::ostringstream out;
out << "pqissludp::moretoread()";
out << " polling socket (" << sockfd << ")";
pqioutput(PQL_DEBUG_ALL, pqissludpzone, out.str());
}
/* check for more to read first ... if nothing... check error
*/
/* <===================== UDP Difference *******************/
if (tou_maxread(sockfd))
/* <===================== UDP Difference *******************/
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::moretoread() Data to Read!");
return 1;
}
/* else check the error */
pqioutput(PQL_DEBUG_ALL, pqissludpzone,
"pqissludp::moretoread() No Data to Read!");
int err;
if (0 != (err = tou_errno(sockfd)))
{
if ((err == EAGAIN) || (err == EINPROGRESS))
{
std::ostringstream out;
out << "pqissludp::moretoread() ";
out << "EAGAIN/EINPROGRESS: cert" << sslcert -> Name();
pqioutput(PQL_WARNING, pqissludpzone, out.str());
return 0;
}
else if ((err == ENETUNREACH) || (err == ETIMEDOUT))
{
std::ostringstream out;
out << "pqissludp::moretoread() ";
out << "ENETUNREACH/ETIMEDOUT: cert";
out << sslcert -> Name();
pqioutput(PQL_WARNING, pqissludpzone, out.str());
}
else if (err == EBADF)
{
std::ostringstream out;
out << "pqissludp::moretoread() ";
out << "EBADF: cert";
out << sslcert -> Name();
pqioutput(PQL_WARNING, pqissludpzone, out.str());
}
else
{
std::ostringstream out;
out << "pqissludp::moretoread() ";
out << " Unknown ERROR: " << err << ": cert";
out << sslcert -> Name();
pqioutput(PQL_WARNING, pqissludpzone, out.str());
}
reset();
return 0;
}
/* otherwise - not error - strange! */
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqissludp::moretoread() No Data + No Error (really nothing)");
return 0;
}
bool pqissludp::cansend()
{
pqioutput(PQL_DEBUG_ALL, pqissludpzone,
"pqissludp::cansend() polling socket!");
/* <===================== UDP Difference *******************/
return (0 < tou_maxwrite(sockfd));
/* <===================== UDP Difference *******************/
}
pqiudplistener::pqiudplistener(p3udpproxy *p, struct sockaddr_in addr)
:p3u(p), sockfd(-1), active(false), laststun(0), firstattempt(0),
lastattempt(0)
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqiudplistener::pqiudplistener()");
setListenAddr(addr);
setuplisten();
stunpkt = malloc(1024); /* 20 = size of a stun request */
stunpktlen = 1024;
}
int pqiudplistener::setListenAddr(struct sockaddr_in addr)
{
laddr = addr;
// increment the port by 1. (can't open on same)
laddr.sin_port = htons(ntohs(laddr.sin_port) + 1);
return 1;
}
int pqiudplistener::resetlisten()
{
if (sockfd > -1)
{
/* close it down */
tou_close(sockfd);
sockfd = -1;
}
active = false;
return 1;
}
int pqiudplistener::setuplisten()
{
sockfd = tou_socket(0,0,0);
if (0 == tou_bind(sockfd, (struct sockaddr *) &laddr, sizeof(laddr)))
{
active = true;
pqioutput(PQL_DEBUG_BASIC, pqissludpzone, "pqiudplistener::setuplisten Succeeded!");
return 1;
}
active = false;
pqioutput(PQL_DEBUG_BASIC, pqissludpzone, "pqiudplistener::setuplisten Failed!");
std::cerr << "pqiudplistener failed to bind to :" << inet_ntoa(laddr.sin_addr);
std::cerr << ":" << ntohs(laddr.sin_port) << std::endl;
return 0;
}
int pqiudplistener::status()
{
return 1;
}
int pqiudplistener::tick()
{
int dsize = 1024;
char data[dsize];
struct sockaddr_in addr, pot_ext_addr;
/* must check if address is okay.
*/
if (!active)
{
/* can't do much ... */
return 1;
}
serverStun();
if (recvfrom(data, &dsize, addr))
{
if (response(data, dsize, pot_ext_addr))
{
checkExtAddr(addr, data, dsize, pot_ext_addr);
}
else
{
reply(data, dsize, addr);
}
}
return 1;
}
const int MIN_STUN_PERIOD = 24 * 60 * 60; /* 1 day */
const int MIN_STUN_GAP = 10; /* seconds. */
const int MAX_STUN_ATTEMPTS = 60; /* 6 * 10 sec attempts */
const int STUN_RETRY_PERIOD = 10; // * 60; /* 20 minutes */
int pqiudplistener::serverStun()
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone, "pqiudplistener::serverStun()");
/* get the list from p3disc() */
int ts = time(NULL);
if (!firstattempt) firstattempt = ts;
/* if not stunned */
if ((!laststun) && (ts - firstattempt > MAX_STUN_ATTEMPTS))
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone, "pqiudplistener::Stun Paused!");
/* will fail until RETRY_PERIOD */
if (ts - firstattempt > STUN_RETRY_PERIOD)
{
firstattempt = ts;
}
return 0;
}
if (ts - laststun < MIN_STUN_PERIOD)
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone, "pqiudplistener::Stun Paused! 2");
return 0;
}
if (ts - lastattempt < MIN_STUN_GAP)
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone, "pqiudplistener::Stun Paused! 3");
return 0;
}
lastattempt = ts;
/* stun someone */
if (!p3u -> requestStunServer(stun_addr))
{
pqioutput(PQL_DEBUG_BASIC, pqissludpzone,
"pqiudplistener::serverStun() No Stun Server");
/* failed, wait a bit */
return 0;
}
/* send out a stun packet -> save in the local variable */
int tmplen = stunpktlen;
bool done = generate_stun_pkt(stunpkt, &tmplen);
if (!done)
return 0;
/* increment the port +1 */
stun_addr.sin_port = htons(ntohs(stun_addr.sin_port) + 1);
/* and send it off */
// int sentlen =
tou_sendto(sockfd, stunpkt, tmplen, 0,
(const struct sockaddr *) &stun_addr, sizeof(stun_addr));
std::ostringstream out;
out << "pqiudplistener::serverStun() Sent Stun Packet to:";
out << inet_ntoa(stun_addr.sin_addr) << ":" << ntohs(stun_addr.sin_port);
pqioutput(PQL_DEBUG_BASIC, pqissludpzone, out.str());
return 1;
}
bool pqiudplistener::response(void *stun_pkt, int size, struct sockaddr_in &addr)
{
/* check what type it is */
if (size < 28)
{
return false;
}
if (((uint16_t *) stun_pkt)[0] != 0x0101)
{
/* not a response */
return false;
}
/* iterate through the packet */
/* for now assume the address follows the header directly */
/* all stay in netbyteorder! */
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = ((uint32_t *) stun_pkt)[6];
addr.sin_port = ((uint16_t *) stun_pkt)[11];
std::ostringstream out;
out << "pqiudplistener::response() Recvd a Stun Response, ext_addr: ";
out << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port);
pqioutput(PQL_ALERT, pqissludpzone, out.str());
return true;
}
int pqiudplistener::checkExtAddr(struct sockaddr_in &src_addr,
void *data, int size, struct sockaddr_in &ext_addr)
{
/* display it */
{
std::ostringstream out;
out << "pqiudplistener::checkExtAddr(): received Stun Pkt" << std::endl;
out << "\t\text_addr: ";
out << inet_ntoa(ext_addr.sin_addr) << ":" << ntohs(ext_addr.sin_port);
out << std::endl;
out << "\t\tsrc_addr: ";
out << inet_ntoa(src_addr.sin_addr) << ":" << ntohs(src_addr.sin_port);
pqioutput(PQL_WARNING, pqissludpzone, out.str());
}
/* decide if its better than the current external address */
cert *own = getSSLRoot() -> getOwnCert();
/* if src is same network as local, or it is not external ... then ignore */
if ((sameNet(&(src_addr.sin_addr), &(own->localaddr.sin_addr))) ||
(!isExternalNet(&(src_addr.sin_addr))))
{
std::ostringstream out;
out << "Cannot use returned StunAddr:";
out << " srcAddr !Ext || sameNet(srcAddr, localAddr)";
pqioutput(PQL_WARNING, pqissludpzone, out.str());
return 0;
}
/* if extAddr is not ext, then don't accept */
if (!isExternalNet(&(ext_addr.sin_addr)))
{
std::ostringstream out;
out << "Cannot use returned StunAddr:";
out << " returnedAddr is !External";
pqioutput(PQL_WARNING, pqissludpzone, out.str());
return 0;
}
/* port is harder....
* there are a couple of cases.
* --- NOT Firewalled ---------------------
* (1) ext = local, port should = local.port.
* --- Firewalled ---------------------
* (2) own->Forwarded(), keep old port number.
* (3) behind firewall -> port is irrelevent, can't connect. put 0.
*/
/* default is case (3) */
unsigned short server_port = 0;
/* incase they have actually forwarded a port, set it correct! */
server_port = ntohs(ext_addr.sin_port) - 1;
if (isValidNet(&(own->serveraddr.sin_addr)))
{
/* Final extra check....
* if src Address is same network as current server addr...
* then its not far enough away
*/
/***************************
* ... but until network is well established we wont switch this
* rule on...
*
if (isSameNet(src_addr, own->serveraddr))
{
std::ostringstream out;
out << "Cannot use returned StunAddr:";
out << " sameNet(srcAddr, current srvAddr)";
pqioutput(PQL_WARNING, pqissludpzone, out.str());
return 0;
}
*
*
**************************/
/* port case (2) */
if (own->Forwarded())
{
/* this *MUST* have been set by the user, so
* keep the port number....
*/
server_port = ntohs(own->serveraddr.sin_port);
std::ostringstream out;
out << "We are marked as Forwarded(), using existing port: ";
out << server_port;
pqioutput(PQL_WARNING, pqissludpzone, out.str());
}
}
/* port case (1) */
if (0 == inaddr_cmp(ext_addr, own->localaddr))
{
server_port = ntohs(own->localaddr.sin_port);
{
std::ostringstream out;
out << "extAddr == localAddr, Not Firewalled() + using local port: ";
out << server_port;
pqioutput(PQL_WARNING, pqissludpzone, out.str());
}
/* if it is the same as local address -> then not firewalled */
if (own->Firewalled())
{
std::ostringstream out;
out << "We are currently Marked as Firewalled, changing to NotFirewalled()";
own->Firewalled(false);
pqioutput(PQL_WARNING, pqissludpzone, out.str());
}
}
/* otherwise we will take the addr */
own->serveraddr = ext_addr;
/* set the port number finally */
own->serveraddr.sin_port = htons(server_port);
/* save timestamp of successful stun */
laststun = time(NULL);
return 1;
}
/************************** Basic Functionality ******************/
int pqiudplistener::recvfrom(void *data, int *size, struct sockaddr_in &addr)
{
/* check the socket */
socklen_t addrlen = sizeof(addr);
int rsize = tou_recvfrom(sockfd, data, *size, 0, (struct sockaddr *) &addr, &addrlen);
if (rsize > 0)
{
std::ostringstream out;
out << "pqiudplistener::recvfrom() Recved a Pkt from: ";
out << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port);
pqioutput(PQL_ALERT, pqissludpzone, out.str());
*size = rsize;
return 1;
}
return 0;
}
int pqiudplistener::reply(void *data, int size, struct sockaddr_in &addr)
{
/* so we design a new packet with the external address in it */
int pktlen = 0;
void *pkt = generate_stun_reply(&addr, &pktlen);
/* and send it off */
int sentlen = tou_sendto(sockfd, pkt, pktlen, 0,
(const struct sockaddr *) &addr, sizeof(addr));
free(pkt);
/* display status */
std::ostringstream out;
out << "pqiudplistener::reply() Responding to a Stun Req, ext_addr: ";
out << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port);
pqioutput(PQL_ALERT, pqissludpzone, out.str());
return sentlen;
}
bool pqiudplistener::generate_stun_pkt(void *stun_pkt, int *len)
{
if (*len < 20)
{
return false;
}
/* just the header */
((uint16_t *) stun_pkt)[0] = 0x0001;
((uint16_t *) stun_pkt)[1] = 0x0020; /* only header */
/* transaction id - should be random */
((uint32_t *) stun_pkt)[1] = 0x0020;
((uint32_t *) stun_pkt)[2] = 0x0121;
((uint32_t *) stun_pkt)[3] = 0x0111;
((uint32_t *) stun_pkt)[4] = 0x1010;
*len = 20;
return true;
}
void *pqiudplistener::generate_stun_reply(struct sockaddr_in *stun_addr, int *len)
{
/* just the header */
void *stun_pkt = malloc(28);
((uint16_t *) stun_pkt)[0] = 0x0101;
((uint16_t *) stun_pkt)[1] = 0x0028; /* only header + 8 byte addr */
/* transaction id - should be random */
((uint32_t *) stun_pkt)[1] = 0x0020;
((uint32_t *) stun_pkt)[2] = 0x0121;
((uint32_t *) stun_pkt)[3] = 0x0111;
((uint32_t *) stun_pkt)[4] = 0x1010;
/* now add address
* 0 1 2 3
* <INET> <port>
* <inet address>
*/
((uint32_t *) stun_pkt)[6] = stun_addr->sin_addr.s_addr;
((uint16_t *) stun_pkt)[11] = stun_addr->sin_port;
*len = 28;
return stun_pkt;
}