Skip to content
connection.cc 13 KiB
Newer Older
  This file is part of SLL.
  Copyright (C) 2008 Sebastien LUTTRINGER <contact@seblu.net>

  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 <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <poll.h>

/*******************************************************************************
 ** 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);

Seblu's avatar
Seblu committed
  if (socket_fd_ >= 0)
    id_ = getconnid();
}

/**
 * Destructor
 */
Connection::~Connection() {
  pthread_mutex_destroy(&c_mutex_);
  pthread_mutex_destroy(&r_mutex_);
  pthread_mutex_destroy(&w_mutex_);
}

Seblu's avatar
Seblu committed
/**
 * 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) {
Seblu's avatar
Seblu committed
  // check args
  if (addr == 0 || *addr == 0)
    throw Error(ERR_NET, "Trying to connect on a bad address");
Seblu's avatar
Seblu committed
  if (port <= 0 || port >= 65536)
    throw Error(ERR_NET, "Trying to connect on a bad port number");
Seblu's avatar
Seblu committed
  // take connection mutex
  pthread_mutex_lock(&c_mutex_);
Seblu's avatar
Seblu committed

  // 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_);
Seblu's avatar
Seblu committed
  pthread_mutex_lock(&r_mutex_);

  // retrieve remote host info
  struct hostent *h = gethostbyname(addr);
  if (h == 0) {
    pthread_mutex_unlock(&r_mutex_);
Seblu's avatar
Seblu committed
    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_);
Seblu's avatar
Seblu committed
    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) {
Seblu's avatar
Seblu committed
    int errno__ = errno;
    disconnect_();
    pthread_mutex_unlock(&r_mutex_);
Seblu's avatar
Seblu committed
    pthread_mutex_unlock(&w_mutex_);
    pthread_mutex_unlock(&c_mutex_);
Seblu's avatar
Seblu committed
    throw Error(ERR_NET, (string) "Unable to connect to " + addr  + ": " + strerror(errno__));
  }

  // set infos
Seblu's avatar
Seblu committed
  try {
    setlocalip_();
    setlocalport_();
    setremoteip_();
    setremoteport_();
  }
  catch (...) {
    pthread_mutex_unlock(&r_mutex_);
Seblu's avatar
Seblu committed
    pthread_mutex_unlock(&w_mutex_);
Seblu's avatar
Seblu committed
    pthread_mutex_unlock(&c_mutex_);
    throw;
  }

  pthread_mutex_unlock(&r_mutex_);
Seblu's avatar
Seblu committed
  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) {
Seblu's avatar
Seblu committed
  // 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");
Seblu's avatar
Seblu committed
  // take connection mutex
  pthread_mutex_lock(&c_mutex_);
Seblu's avatar
Seblu committed

  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
Seblu's avatar
Seblu committed
  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() {
Seblu's avatar
Seblu committed
  // accept is a read/write op
Seblu's avatar
Seblu committed
  pthread_mutex_lock(&c_mutex_);
Seblu's avatar
Seblu committed
  pthread_mutex_lock(&r_mutex_);
  pthread_mutex_lock(&w_mutex_);

  // get the socket fd
Seblu's avatar
Seblu committed
  int socket_fd = socket_fd_;
  pthread_mutex_unlock(&c_mutex_);

  // test socket validity
Seblu's avatar
Seblu committed
  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");
Seblu's avatar
Seblu committed
  }

  struct sockaddr_in r_addr;
  socklen_t r_sin_size = sizeof r_addr;
  int r_fd;

Seblu's avatar
Seblu committed
  if ((r_fd = ::accept(socket_fd, (struct sockaddr *) &r_addr, &r_sin_size)) == -1) {
Seblu's avatar
Seblu committed
    int errno__ = errno;
    pthread_mutex_lock(&c_mutex_);
    disconnect_();
    pthread_mutex_unlock(&r_mutex_);
    pthread_mutex_unlock(&w_mutex_);
    pthread_mutex_unlock(&c_mutex_);
Seblu's avatar
Seblu committed
    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) {
Seblu's avatar
Seblu committed
  // lock mutex for operation
  pthread_mutex_lock(&c_mutex_);
  pthread_mutex_lock(&w_mutex_);

Seblu's avatar
Seblu committed
  // retreive socket_fd and free conn mutex
  int socket_fd = socket_fd_;
  pthread_mutex_unlock(&c_mutex_);
Seblu's avatar
Seblu committed
  // test socket validity
  if (socket_fd < 0) {
    pthread_mutex_unlock(&w_mutex_);
    throw Error(ERR_NET, "No connection established but trying to send data");
  }
Seblu's avatar
Seblu committed
  // send is a write operation
  int ret = ::send(socket_fd_, buf, len, MSG_NOSIGNAL);

  // treat error
  if (ret == -1) {
Seblu's avatar
Seblu committed
    int errno__ = errno;
    pthread_mutex_lock(&c_mutex_);
    pthread_mutex_lock(&r_mutex_);
    disconnect_();
    pthread_mutex_unlock(&r_mutex_);
Seblu's avatar
Seblu committed
    pthread_mutex_unlock(&w_mutex_);
Seblu's avatar
Seblu committed
    pthread_mutex_unlock(&c_mutex_);
Seblu's avatar
Seblu committed
    if (errno__ == ECONNRESET || errno__ == EPIPE)
      throw Error(ERR_NET, "Connection reset by peer");
Seblu's avatar
Seblu committed
    else
      throw Error(ERR_NET, "send: " + (string) strerror(errno__));
Seblu's avatar
Seblu committed
  // release the mutex
  pthread_mutex_unlock(&w_mutex_);
}

Seblu's avatar
Seblu committed
/**
 * Receive raw data
 */
char *Connection::recv(size_t size) {
  assert(0);
  return 0;
}

/**
 * Receive a line
 */
string Connection::recvln() {
Seblu's avatar
Seblu committed
  // 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");
Seblu's avatar
Seblu committed
  }

  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];
Seblu's avatar
Seblu committed
    int ret = ::recv(socket_fd, local_buf, MAX_LINE_SIZE, 0);
Seblu's avatar
Seblu committed
    if (ret == -1 || ret == 0) {
      pthread_mutex_lock(&c_mutex_);
      pthread_mutex_lock(&w_mutex_);
      disconnect_();
Seblu's avatar
Seblu committed
      pthread_mutex_unlock(&w_mutex_);
Seblu's avatar
Seblu committed
      pthread_mutex_unlock(&r_mutex_);
      pthread_mutex_unlock(&c_mutex_);

      *local_buf = 0;

Seblu's avatar
Seblu committed
      if (ret == 0)
	throw Error(ERR_NET, "Connection reset by peer");
      else
	throw Error(ERR_NET, (string) "recvln: " + strerror(errno));
Seblu's avatar
Seblu committed
    local_buf[ret] = 0;

    // add read data in buffer
    rbuf_ += local_buf;

  } while (1);
  assert(1);
}

Seblu's avatar
Seblu committed
void Connection::flush() {
  assert(0);
}

string Connection::getlocalip() {
Seblu's avatar
Seblu committed
  // 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");
Seblu's avatar
Seblu committed
  }
  if (local_ip_.empty())
Seblu's avatar
Seblu committed
    try { setlocalip_(); }
    catch (...) {
      pthread_mutex_unlock(&c_mutex_);
      throw;
    }
  string ip = local_ip_;
  pthread_mutex_unlock(&c_mutex_);
  return ip;
}

Seblu's avatar
Seblu committed
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() {
Seblu's avatar
Seblu committed
  // 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");
Seblu's avatar
Seblu committed
  }
  if (local_port_ == -1)
Seblu's avatar
Seblu committed
    try { setlocalport_(); }
    catch (...) {
      pthread_mutex_unlock(&c_mutex_);
      throw;
    }

  int port = local_port_;
  pthread_mutex_unlock(&c_mutex_);
  return port;
}

string Connection::getremoteip() {
Seblu's avatar
Seblu committed
  // get remote hostname is a conn op
Seblu's avatar
Seblu committed
  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");
Seblu's avatar
Seblu committed
  }
  if (remote_ip_.empty())
Seblu's avatar
Seblu committed
    try { setremoteip_(); }
    catch (...) {
      pthread_mutex_unlock(&c_mutex_);
      throw;
    }

  string ip = remote_ip_;
  pthread_mutex_unlock(&c_mutex_);
  return ip;
}

Seblu's avatar
Seblu committed
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() {
Seblu's avatar
Seblu committed
  // 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");
Seblu's avatar
Seblu committed
  }
  if (remote_port_ == -1)
Seblu's avatar
Seblu committed
    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() {
Seblu's avatar
Seblu committed
  // get socket is a conn op
  pthread_mutex_lock(&c_mutex_);
Seblu's avatar
Seblu committed
  int socket_fd = socket_fd_;
  pthread_mutex_unlock(&c_mutex_);
Seblu's avatar
Seblu committed

  if (socket_fd < 0)
    throw Error(ERR_NET, "No connection established but trying to get socket fd");

  return socket_fd;
}

/*******************************************************************************
 ** Protected method
 ******************************************************************************/