/** ** \file SctpSocket.cpp ** \date 2006-09-04 ** \author grymse@alhem.net **/ /* Copyright (C) 2007-2011 Anders Hedstrom This library is made available under the terms of the GNU GPL, with the additional exemption that compiling, linking, and/or using OpenSSL is allowed. If you would like to use this library in a closed-source application, a separate license agreement is available. For information about the closed-source license agreement for the C++ sockets library, please visit http://www.alhem.net/Sockets/license.html and/or email license@alhem.net. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "SctpSocket.h" #ifdef USE_SCTP #include "Utility.h" #include "ISocketHandler.h" #include #include "Ipv4Address.h" #include "Ipv6Address.h" #ifdef ENABLE_EXCEPTIONS #include "Exception.h" #endif #ifdef SOCKETS_NAMESPACE namespace SOCKETS_NAMESPACE { #endif SctpSocket::SctpSocket(ISocketHandler& h,int type) : StreamSocket(h) ,m_type(type) ,m_buf(new char[SCTP_BUFSIZE_READ]) { if (type != SOCK_STREAM && type != SOCK_SEQPACKET) { } } SctpSocket::~SctpSocket() { delete[] m_buf; } int SctpSocket::Bind(const std::string& a,port_t p) { #ifdef ENABLE_IPV6 #ifdef IPPROTO_IPV6 if (IsIpv6()) { Ipv6Address ad(a, p); return Bind(ad); } #endif #endif Ipv4Address ad(a, p); return Bind(ad); } int SctpSocket::Bind(SocketAddress& ad) { if (!ad.IsValid()) { Handler().LogError(this, "SctpSocket", -1, "invalid address", LOG_LEVEL_ERROR); return -1; } if (GetSocket() == INVALID_SOCKET) { Attach(CreateSocket(ad.GetFamily(), m_type, "sctp")); } if (GetSocket() != INVALID_SOCKET) { int n = bind(GetSocket(), ad, ad); if (n == -1) { Handler().LogError(this, "SctpSocket", -1, "bind() failed", LOG_LEVEL_ERROR); #ifdef ENABLE_EXCEPTIONS throw Exception("bind() failed for SctpSocket, port: " + Utility::l2string(ad.GetPort())); #endif } return n; } return -1; } int SctpSocket::AddAddress(const std::string& a,port_t p) { #ifdef ENABLE_IPV6 #ifdef IPPROTO_IPV6 if (IsIpv6()) { Ipv6Address ad(a, p); return AddAddress(ad); } #endif #endif Ipv4Address ad(a, p); return AddAddress(ad); } int SctpSocket::AddAddress(SocketAddress& ad) { if (!ad.IsValid()) { Handler().LogError(this, "SctpSocket", -1, "invalid address", LOG_LEVEL_ERROR); return -1; } if (GetSocket() == INVALID_SOCKET) { Handler().LogError(this, "SctpSocket", -1, "AddAddress called with invalid file descriptor", LOG_LEVEL_ERROR); return -1; } int n = sctp_bindx(GetSocket(), ad, ad, SCTP_BINDX_ADD_ADDR); if (n == -1) { Handler().LogError(this, "SctpSocket", -1, "sctp_bindx() failed", LOG_LEVEL_ERROR); } return n; } int SctpSocket::RemoveAddress(const std::string& a,port_t p) { #ifdef ENABLE_IPV6 #ifdef IPPROTO_IPV6 if (IsIpv6()) { Ipv6Address ad(a, p); return RemoveAddress(ad); } #endif #endif Ipv4Address ad(a, p); return RemoveAddress(ad); } int SctpSocket::RemoveAddress(SocketAddress& ad) { if (!ad.IsValid()) { Handler().LogError(this, "SctpSocket", -1, "invalid address", LOG_LEVEL_ERROR); return -1; } if (GetSocket() == INVALID_SOCKET) { Handler().LogError(this, "SctpSocket", -1, "RemoveAddress called with invalid file descriptor", LOG_LEVEL_ERROR); return -1; } int n = sctp_bindx(GetSocket(), ad, ad, SCTP_BINDX_REM_ADDR); if (n == -1) { Handler().LogError(this, "SctpSocket", -1, "sctp_bindx() failed", LOG_LEVEL_ERROR); } return n; } int SctpSocket::Open(const std::string& a,port_t p) { #ifdef ENABLE_IPV6 #ifdef IPPROTO_IPV6 if (IsIpv6()) { Ipv6Address ad(a, p); return Open(ad); } #endif #endif Ipv4Address ad(a, p); return Open(ad); } int SctpSocket::Open(SocketAddress& ad) { if (!ad.IsValid()) { Handler().LogError(this, "SctpSocket", -1, "invalid address", LOG_LEVEL_ERROR); return -1; } if (GetSocket() == INVALID_SOCKET) { Attach(CreateSocket(ad.GetFamily(), m_type, "sctp")); } if (GetSocket() != INVALID_SOCKET) { if (!SetNonblocking(true)) { return -1; } int n = connect(GetSocket(), ad, ad); if (n == -1) { // check error code that means a connect is in progress #ifdef _WIN32 if (Errno == WSAEWOULDBLOCK) #else if (Errno == EINPROGRESS) #endif { Handler().LogError(this, "connect: connection pending", Errno, StrError(Errno), LOG_LEVEL_INFO); SetConnecting( true ); // this flag will control fd_set's } else { Handler().LogError(this, "SctpSocket", -1, "connect() failed", LOG_LEVEL_ERROR); } } return n; } return -1; } #ifndef SOLARIS int SctpSocket::AddConnection(const std::string& a,port_t p) { #ifdef ENABLE_IPV6 #ifdef IPPROTO_IPV6 if (IsIpv6()) { Ipv6Address ad(a, p); return AddConnection(ad); } #endif #endif Ipv4Address ad(a, p); return AddConnection(ad); } int SctpSocket::AddConnection(SocketAddress& ad) { if (!ad.IsValid()) { Handler().LogError(this, "SctpSocket", -1, "invalid address", LOG_LEVEL_ERROR); return -1; } if (GetSocket() == INVALID_SOCKET) { Handler().LogError(this, "SctpSocket", -1, "AddConnection called with invalid file descriptor", LOG_LEVEL_ERROR); return -1; } // int n = sctp_connectx(GetSocket(), ad, ad); int n = sctp_connectx(GetSocket(), ad, 1, NULL); if (n == -1) { Handler().LogError(this, "SctpSocket", -1, "sctp_connectx() failed", LOG_LEVEL_ERROR); } else { SetConnecting(); } return n; } #endif int SctpSocket::getpaddrs(sctp_assoc_t id,std::list& vec) { struct sockaddr *p = NULL; int n = sctp_getpaddrs(GetSocket(), id, &p); if (!n || n == -1) { Handler().LogError(this, "SctpSocket", -1, "sctp_getpaddrs failed", LOG_LEVEL_WARNING); return n; } for (int i = 0; i < n; i++) { vec.push_back(Utility::Sa2String(&p[i])); } sctp_freepaddrs(p); return n; } int SctpSocket::getladdrs(sctp_assoc_t id,std::list& vec) { struct sockaddr *p = NULL; int n = sctp_getladdrs(GetSocket(), id, &p); if (!n || n == -1) { Handler().LogError(this, "SctpSocket", -1, "sctp_getladdrs failed", LOG_LEVEL_WARNING); return n; } for (int i = 0; i < n; i++) { vec.push_back(Utility::Sa2String(&p[i])); } sctp_freeladdrs(p); return n; } int SctpSocket::PeelOff(sctp_assoc_t id) { int n = sctp_peeloff(GetSocket(), id); if (n == -1) { Handler().LogError(this, "SctpSocket", -1, "PeelOff failed", LOG_LEVEL_WARNING); return -1; } Socket *p = Create(); p -> Attach(n); p -> SetDeleteByHandler(); Handler().Add(p); return n; } void SctpSocket::OnRead() { /* int sctp_recvmsg(int sd, void * msg, size_t * len, struct sockaddr * from, socklen_t * fromlen, struct sctp_sndrcvinfo * sinfo, int * msg_flags); DESCRIPTION sctp_recvmsg is a wrapper library function that can be used to receive a message from a socket while using the advanced features of SCTP. sd is the socket descriptor on which the message pointed to by msg of length len is received. If from is not NULL, the source address of the message is filled in. The argument fromlen is a value-result parameter. initialized to the size of the buffer associated with from , and modified on return to indicate the actual size of the address stored. sinfo is a pointer to a sctp_sndrcvinfo structure to be filled upon receipt of the message. msg_flags is a pointer to a integer that is filled with any message flags like MSG_NOTIFICATION or MSG_EOR. */ struct sockaddr sa; socklen_t sa_len = 0; struct sctp_sndrcvinfo sinfo; int flags = 0; int n = sctp_recvmsg(GetSocket(), m_buf, SCTP_BUFSIZE_READ, &sa, &sa_len, &sinfo, &flags); if (n == -1) { Handler().LogError(this, "SctpSocket", Errno, StrError(Errno), LOG_LEVEL_FATAL); SetCloseAndDelete(); } else { OnReceiveMessage(m_buf, n, &sa, sa_len, &sinfo, flags); } } void SctpSocket::OnReceiveMessage(const char *buf,size_t sz,struct sockaddr *sa,socklen_t sa_len,struct sctp_sndrcvinfo *sinfo,int msg_flags) { } void SctpSocket::OnWrite() { if (Connecting()) { int err = SoError(); // don't reset connecting flag on error here, we want the OnConnectFailed timeout later on /// \todo add to read fd_set here if (!err) // ok { Set(!IsDisableRead(), false); SetConnecting(false); SetCallOnConnect(); return; } Handler().LogError(this, "sctp: connect failed", err, StrError(err), LOG_LEVEL_FATAL); Set(false, false); // no more monitoring because connection failed // failed #ifdef ENABLE_SOCKS4 if (Socks4()) { OnSocks4ConnectFailed(); return; } #endif if (GetConnectionRetry() == -1 || (GetConnectionRetry() && GetConnectionRetries() < GetConnectionRetry()) ) { // even though the connection failed at once, only retry after // the connection timeout. // should we even try to connect again, when CheckConnect returns // false it's because of a connection error - not a timeout... return; } SetConnecting(false); SetCloseAndDelete( true ); /// \todo state reason why connect failed OnConnectFailed(); return; } } void SctpSocket::OnConnectTimeout() { Handler().LogError(this, "connect", -1, "connect timeout", LOG_LEVEL_FATAL); #ifdef ENABLE_SOCKS4 if (Socks4()) { OnSocks4ConnectFailed(); // retry direct connection } else #endif if (GetConnectionRetry() == -1 || (GetConnectionRetry() && GetConnectionRetries() < GetConnectionRetry()) ) { IncreaseConnectionRetries(); // ask socket via OnConnectRetry callback if we should continue trying if (OnConnectRetry()) { SetRetryClientConnect(); } else { SetCloseAndDelete( true ); /// \todo state reason why connect failed OnConnectFailed(); } } else { SetCloseAndDelete(true); /// \todo state reason why connect failed OnConnectFailed(); } // SetConnecting(false); } #ifdef _WIN32 void SctpSocket::OnException() { if (Connecting()) { #ifdef ENABLE_SOCKS4 if (Socks4()) OnSocks4ConnectFailed(); else #endif if (GetConnectionRetry() == -1 || (GetConnectionRetry() && GetConnectionRetries() < GetConnectionRetry() )) { // even though the connection failed at once, only retry after // the connection timeout // should we even try to connect again, when CheckConnect returns // false it's because of a connection error - not a timeout... } else { SetConnecting(false); // tnx snibbe SetCloseAndDelete(); OnConnectFailed(); } return; } // %! exception doesn't always mean something bad happened, this code should be reworked // errno valid here? int err = SoError(); Handler().LogError(this, "exception on select", err, StrError(err), LOG_LEVEL_FATAL); SetCloseAndDelete(); } #endif // _WIN32 int SctpSocket::Protocol() { return IPPROTO_SCTP; } #ifdef SOCKETS_NAMESPACE } // namespace SOCKETS_NAMESPACE #endif #endif // USE_SCTP