/* This file is part of SLL. Copyright (C) 2008 Sebastien LUTTRINGER SLL 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; version 2 of the License. SLL 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 SLL; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "slm.hh" #include "connection.hh" #include "error.hh" #include #include #include #include #include #include /******************************************************************************* ** Class method ******************************************************************************/ unsigned long int Connection::getconnid() { static unsigned long int id = 1; return id++; } /******************************************************************************* ** Public method ******************************************************************************/ /** * Constructor * * @param fd socket of the connection (-1) is not exist */ Connection::Connection(int fd) : socket_fd_(fd), local_port_(-1), remote_port_(-1), id_(0) { pthread_mutex_init(&c_mutex_, 0); pthread_mutex_init(&r_mutex_, 0); pthread_mutex_init(&w_mutex_, 0); if (socket_fd_ >= 0) id_ = getconnid(); } /** * Destructor */ Connection::~Connection() { pthread_mutex_destroy(&c_mutex_); pthread_mutex_destroy(&r_mutex_); pthread_mutex_destroy(&w_mutex_); } /** * Disconnect socket */ void Connection::disconnect() { // lock connection mutex pthread_mutex_lock(&c_mutex_); if (socket_fd_ < 0) { pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, "No connection established but trying to disconnect"); } // lock all mutex pthread_mutex_lock(&r_mutex_); pthread_mutex_lock(&w_mutex_); disconnect_(); pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&c_mutex_); } /** * Create connection with @param addr on port @param port */ void Connection::connect(const char *addr, int port) { // check args if (addr == 0 || *addr == 0) throw Error(ERR_NET, "Trying to connect on a bad address"); if (port <= 0 || port >= 65536) throw Error(ERR_NET, "Trying to connect on a bad port number"); // take connection mutex pthread_mutex_lock(&c_mutex_); // check already existant connection if (socket_fd_ >= 0) { pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, "Connection already established but trying to connect"); } // take read and write mutex pthread_mutex_lock(&w_mutex_); pthread_mutex_lock(&r_mutex_); // retrieve remote host info struct hostent *h = gethostbyname(addr); if (h == 0) { pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, (string) "Unable to resolve: " + addr + ": " + hstrerror(h_errno)); } // create socket try { socket_(); } catch (...) { pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&c_mutex_); throw; } // fill sockaddr struct sockaddr_in daddr; daddr.sin_family = AF_INET; daddr.sin_port = htons(port); daddr.sin_addr = *((struct in_addr *) h->h_addr); memset(daddr.sin_zero, '\0', sizeof daddr.sin_zero); // connect if (::connect(socket_fd_, (struct sockaddr *) &daddr, sizeof daddr) == -1) { int errno__ = errno; disconnect_(); pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, (string) "Unable to connect to " + addr + ": " + strerror(errno__)); } // set infos try { setlocalip_(); setlocalport_(); setremoteip_(); setremoteport_(); } catch (...) { pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&c_mutex_); throw; } pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&c_mutex_); } /** * Listen on @param port with queue size of max @param max */ void Connection::listen(int port, int max) { // check arg if (port <= 0 || port >= 65536) throw Error(ERR_NET, "Trying to listen on a bad port number"); if (max <= 0) throw Error(ERR_NET, "Trying to listen with bad wait queue size"); // take connection mutex pthread_mutex_lock(&c_mutex_); if (socket_fd_ >= 0) { pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, "Connection already established but trying to listen"); } // lock read and write mutex pthread_mutex_lock(&r_mutex_); pthread_mutex_lock(&w_mutex_); // create socket try { socket_(); } catch (...) { pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&c_mutex_); throw; } // fill sockaddr struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(port); saddr.sin_addr.s_addr = INADDR_ANY; memset(saddr.sin_zero, '\0', sizeof saddr.sin_zero); // bind on socket if (bind(socket_fd_, (struct sockaddr *)&saddr, sizeof saddr) == -1) { disconnect_(); pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, (string) "Unable to bind: " + strerror(errno)); } // listen on socket if (::listen(socket_fd_, max) == -1) { disconnect_(); pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, (string) "Unable to listen: " + strerror(errno)); } // set all infos try { setlocalip_(); setlocalport_(); } catch (...) { pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&c_mutex_); throw; } pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&c_mutex_); } /** * Accept new connection on a listening socket * * @return null on error, else a new connection */ Connection *Connection::accept() { // accept is a read/write op pthread_mutex_lock(&c_mutex_); pthread_mutex_lock(&r_mutex_); pthread_mutex_lock(&w_mutex_); // get the socket fd int socket_fd = socket_fd_; pthread_mutex_unlock(&c_mutex_); // test socket validity if (socket_fd < 0) { pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&w_mutex_); throw Error(ERR_NET, "No connection established but trying to accept"); } struct sockaddr_in r_addr; socklen_t r_sin_size = sizeof r_addr; int r_fd; if ((r_fd = ::accept(socket_fd, (struct sockaddr *) &r_addr, &r_sin_size)) == -1) { int errno__ = errno; pthread_mutex_lock(&c_mutex_); disconnect_(); pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, (string) "accept: " + strerror(errno__)); } pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&w_mutex_); return new Connection(r_fd); } /** * Send data on @param buf of size @param len on socket */ void Connection::send(const char* buf, size_t len) { // lock mutex for operation pthread_mutex_lock(&c_mutex_); pthread_mutex_lock(&w_mutex_); // retreive socket_fd and free conn mutex int socket_fd = socket_fd_; pthread_mutex_unlock(&c_mutex_); // test socket validity if (socket_fd < 0) { pthread_mutex_unlock(&w_mutex_); throw Error(ERR_NET, "No connection established but trying to send data"); } // send is a write operation int ret = ::send(socket_fd_, buf, len, MSG_NOSIGNAL); // treat error if (ret == -1) { int errno__ = errno; pthread_mutex_lock(&c_mutex_); pthread_mutex_lock(&r_mutex_); disconnect_(); pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&c_mutex_); if (errno__ == ECONNRESET || errno__ == EPIPE) throw Error(ERR_NET, "Connection reset by peer"); else throw Error(ERR_NET, "send: " + (string) strerror(errno__)); } // release the mutex pthread_mutex_unlock(&w_mutex_); } /** * Receive raw data */ char *Connection::recv(size_t size) { assert(0); return 0; } /** * Receive a line */ string Connection::recvln() { // lock mutex for operation pthread_mutex_lock(&c_mutex_); pthread_mutex_lock(&r_mutex_); int socket_fd = socket_fd_; pthread_mutex_unlock(&c_mutex_); // test socket validity if (socket_fd < 0) { pthread_mutex_unlock(&r_mutex_); throw Error(ERR_NET, "No connection established but trying to receive line"); } do { // check EOL char size_t offset = rbuf_.find('\n'); if (offset != string::npos) { string s = rbuf_.substr(0, offset); rbuf_.erase(0, offset + 1); pthread_mutex_unlock(&r_mutex_); return s; } // read data static char local_buf[MAX_LINE_SIZE]; int ret = ::recv(socket_fd, local_buf, MAX_LINE_SIZE, 0); if (ret == -1 || ret == 0) { pthread_mutex_lock(&c_mutex_); pthread_mutex_lock(&w_mutex_); disconnect_(); pthread_mutex_unlock(&w_mutex_); pthread_mutex_unlock(&r_mutex_); pthread_mutex_unlock(&c_mutex_); *local_buf = 0; if (ret == 0) throw Error(ERR_NET, "Connection reset by peer"); else throw Error(ERR_NET, (string) "recvln: " + strerror(errno)); } local_buf[ret] = 0; // add read data in buffer rbuf_ += local_buf; } while (1); assert(1); } void Connection::flush() { assert(0); } string Connection::getlocalip() { // get local ip is a conn op pthread_mutex_lock(&c_mutex_); if (socket_fd_ < 0) { pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, "No connection established but trying to get local ip"); } if (local_ip_.empty()) try { setlocalip_(); } catch (...) { pthread_mutex_unlock(&c_mutex_); throw; } string ip = local_ip_; pthread_mutex_unlock(&c_mutex_); return ip; } string Connection::getlocalhostname() { pthread_mutex_lock(&c_mutex_); if (local_hostname_.empty()) { char buf[256]; if (::gethostname(buf, 256) != 0) { pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, (string) "Unable to get local hostname: " + strerror(errno)); } try { local_hostname_ = buf; } catch (...) { pthread_mutex_unlock(&c_mutex_); throw; } } pthread_mutex_unlock(&c_mutex_); return local_hostname_; } int Connection::getlocalport() { // get local port is a conn op pthread_mutex_lock(&c_mutex_); if (socket_fd_ < 0) { pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, "No connection established but trying to get local port"); } if (local_port_ == -1) try { setlocalport_(); } catch (...) { pthread_mutex_unlock(&c_mutex_); throw; } int port = local_port_; pthread_mutex_unlock(&c_mutex_); return port; } string Connection::getremoteip() { // get remote hostname is a conn op pthread_mutex_lock(&c_mutex_); if (socket_fd_ < 0) { pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, "No connection established but trying to get remote ip"); } if (remote_ip_.empty()) try { setremoteip_(); } catch (...) { pthread_mutex_unlock(&c_mutex_); throw; } string ip = remote_ip_; pthread_mutex_unlock(&c_mutex_); return ip; } string Connection::getremotehostname() { // get remote ip is a conn op pthread_mutex_lock(&c_mutex_); if (socket_fd_ < 0) { pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, "No connection established but trying to get remote ip"); } if (remote_hostname_.empty()) try {setremotehostname_(); } catch (...) { pthread_mutex_unlock(&c_mutex_); throw; } string hostname = remote_hostname_; pthread_mutex_unlock(&c_mutex_); return hostname; } int Connection::getremoteport() { // get remote port is a conn op pthread_mutex_lock(&c_mutex_); if (socket_fd_ < 0) { pthread_mutex_unlock(&c_mutex_); throw Error(ERR_NET, "No connection established but trying to get remote port"); } if (remote_port_ == -1) try {setremoteport_(); } catch (...) { pthread_mutex_unlock(&c_mutex_); throw; } int port = remote_port_; pthread_mutex_unlock(&c_mutex_); return port; } unsigned long int Connection::getid() { return id_; } int Connection::getsocket() { // get socket is a conn op pthread_mutex_lock(&c_mutex_); int socket_fd = socket_fd_; pthread_mutex_unlock(&c_mutex_); if (socket_fd < 0) throw Error(ERR_NET, "No connection established but trying to get socket fd"); return socket_fd; } /******************************************************************************* ** Protected method ******************************************************************************/