Logo Search packages:      
Sourcecode: netdiag version File versions

session.c

/*
 *    Copyright (c) 1999-2003 Rinet Corp., Novosibirsk, Russia
 *
 * Redistribution and use in source forms, with and without modification,
 * are permitted provided that this entire comment appears intact.
 *
 * THIS SOURCE CODE IS PROVIDED ``AS IS'' WITHOUT ANY WARRANTIES OF ANY KIND.
 */

#ifdef      HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include "session.h"
#include "events.h"     /* just for tv_sub() */

#define     dprintf(x)  /* nope */

#ifndef     BUF_SIZE
#define     BUF_SIZE    8192
#endif
#ifndef     MAX_STR_LEN
#define     MAX_STR_LEN 1500  /* must be vastly smaller then BUF_SIZE */
#endif

#ifdef      O_NONBLOCK
#define     ASYNC_MODE  O_NONBLOCK
#elif O_NDELAY
#define     ASYNC_MODE  O_NDELAY
#elif FNDELAY
#define     ASYNC_MODE  FNDELAY
#elif O_ASYNC
#define     ASYNC_MODE  O_ASYNC
#elif
#error the fcntl argument to turn ON/OFF non-blocking I/O is unknown
#endif

static int session_read(SESSION *sd);

static SESSION *first_session = 0;  /* first network session in table */

typedef     struct session_binder_ent {
      void (*notify)(void *arg);    /* call it before free */
      void *arg;
      struct session_binder_ent *next;
} SESSION_BINDER;


SESSION *
session_open(sock, peer, type)
      int sock;
      const struct sockaddr *peer;
      SessionType type;
{
      SESSION *sd, *prev = 0, *next = 0;
      static u_long sid = 0;

      /*
       * Search for first empty or last session slot.
       */
      for (sd = first_session; sd; sd = sd->next) {
            if (!sd->sid) {
                  next = sd->next;
                  break;
            }
            prev = sd;
      }
      if (!sd && (sd = (SESSION *)malloc(sizeof(SESSION))) == 0)
            return 0;
      memset(sd, 0, sizeof(SESSION));

      if (++sid == 0) sid++; /* prevent 0 sid */
      sd->sid = sid;
      sd->sock = sock;
      if (peer)
            memcpy(&sd->peer, peer, sizeof(struct sockaddr));
      else  memset(&sd->peer, 0, sizeof(sd->peer));
      memset(&sd->from, 0, sizeof(sd->from));

      sd->type = type;

      /* make chain */
      if (next) sd->next = next;
      else if (prev) prev->next = sd;
      if (!first_session) first_session = sd;

      if (session_start(sd) < 0) {
            sd->sid = 0; /* this slot may be recycled later */
            sd = 0;
      }
      return sd;
}

int
session_start(sd)
      SESSION *sd;
{
      int af;

      /* sanity check */
      if (!sd) {
            errno = EINVAL;
            return -1;
      }
      errno = EBADF;
      if (sd->sock != -1 &&
          (sd->type == PlainFile || socket_peer((struct sockaddr *)&sd->peer, sd->sock) != -1)) {
            /* already connected for example by accept() */

            socket_nonblock(sd->sock, 0);
            if (sd->type == TextStream)
                  socket_keepalive(sd->sock, 1);
            return 0;
      }

      af = sd->peer.ss_family;
      if (!af) af = AF_INET; /* by default */

      if (errno == EBADF || errno == ENOTSOCK) {
            switch (sd->type) {
            case PlainFile:
                  sd->sock = -1;
                  errno = EINVAL;
                  break;
            case TextStream:
                  sd->sock = socket(af, SOCK_STREAM, 0);
                  break;
            case DataSequence:
                  sd->sock = socket(af, SOCK_DGRAM, 0);
                  break;
            /* XXX other session types would be added here */
            }
            if (sd->sock == -1)
                  return -1;

            errno = ENOTCONN;
      }
      if (errno == ENOTCONN) {
            /*
             * Make socket `connected' for any type, so error on this
             * socket will be returned asynchronously without timing out.
             */
            socket_nonblock(sd->sock, 1);

            if (!sd->peer.ss_family) {
                  errno = 0;
                  return 0;
            }

            if (connect(sd->sock, (struct sockaddr *)&sd->peer, sizeof(struct sockaddr)) != -1 ||
                errno == EINPROGRESS)
                  return 0;
      }
      /* prevent lost of unused socket */
      session_stop(sd);

      return -1;
}

int
session_sock(sd)
      SESSION *sd;
{
      return (sd ? sd->sock : -1);
}

unsigned
session_settimeout(sd, timeout)
      SESSION *sd;
      unsigned timeout;
{
      unsigned prev;

      if (!sd || !sd->sid) return 0;

      prev = sd->timeout;
      sd->timeout = timeout;

      if (sd->timeout < 1)
            timerclear(&sd->expire);

      return prev;
}

void
session_setcallback(sd, connected, read_error, read_data)
      SESSION *sd;
      void (*connected)(SESSION *sd);
      void (*read_error)(SESSION *sd, int error);
      void (*read_data)(SESSION *sd, const unsigned char *data, int len);
{
      if (sd && sd->sid) {
            if (connected && sd->type == TextStream) {
                  sd->connected = connected;
            }
            if (read_error)
                  sd->read_error = read_error;
            if (read_data)
                  sd->read_data = read_data;
      }
}

void
session_setcookie(sd, cookie)
      SESSION *sd;
      const void *cookie;
{
      if (sd && sd->sid) sd->cookie = cookie;
}

const void *
session_cookie(sd)
      SESSION *sd;
{
      return ((sd && sd->sid) ? sd->cookie : 0);
}

void
session_stop(sd)
      SESSION *sd;
{
      if (!sd) return;

      if (sd->sock != -1) {
            close(sd->sock);
            sd->sock = -1;
      }
      if (sd->buf) {
            free(sd->buf);
            sd->buf = 0;
      }
      timerclear(&sd->expire);
}

int
session_idle(sd)
      SESSION *sd;
{
      if (!sd || !sd->sid) return -1;
      return (sd->sock != -1 ? 0 : 1);
}

int
session_bind(sd, notify, arg)
      SESSION *sd;
      void (*notify)(void *arg);
      void *arg;
{
      SESSION_BINDER *curr, *last = 0;

      if (!sd || !notify || !arg) {
            errno = EINVAL;
            return -1;
      }
      /* prevent dups and find last */
      for (curr = sd->sb; curr; curr = curr->next) {
            if (curr->notify == notify && curr->arg == arg)
                  return 0;
            last = curr;
      }
      if ((curr = (SESSION_BINDER *)malloc(sizeof(SESSION_BINDER))) == 0)
            return -1;
      curr->notify = notify;
      curr->arg = arg;
      curr->next = 0;
      if (last)
            last->next = curr;
      else  sd->sb = curr;
      return 0;
}

void
session_unbind(sd, notify, arg)
      SESSION *sd;
      void (*notify)(void *arg);
      void *arg;
{
      SESSION_BINDER *curr, *prev, *next;

      curr = (sd ? sd->sb : 0);
      prev = 0;
      while (curr) {
            if ((!notify && !arg) ||
                (curr->notify == notify && curr->arg == arg)) {
                  next = curr->next;
                  if (prev)
                        prev->next = next;
                  else  sd->sb = next;
                  free(curr);
                  curr = next;
            } else {
                  prev = curr;
                  curr = curr->next;
            }
      }
}

/*
 * This function free all memory only when free_sd = 0 else it just reset
 * session id and does not free memory. The mean of this behaving is to
 * reuse/recycle session slots without new malloc (avoiding it overhead).
 */
void
session_free(free_sd)
      SESSION *free_sd;
{
      SESSION *sd, *prev, *next;
      SESSION_BINDER *sb;

      sd = first_session;
      prev = next = 0;
      while (sd) {
            if (!free_sd || sd == free_sd) {
                  if (!free_sd) {
                        next = sd->next;
                        if (prev)
                              prev->next = next;
                        else  first_session = next;
                  }

                  for (sb = sd->sb; sb; sb = sb->next) {
                        if (sb->notify && sb->arg)
                              (*sb->notify)(sb->arg);
                  }
                  session_stop(sd);
                  session_unbind(sd, 0, 0); /* to free all */

                  if (!free_sd) {
                        free(sd);
                        sd = next;
                        continue;
                  }
                  sd->sid = 0; /* this slot may be recycled later */
            }
            prev = sd;
            sd = sd->next;
      }
}

SESSION *
session_find(peer, type)
      const struct sockaddr *peer;
      SessionType type;
{
      SESSION *sd;

      /* sanity check */
      if (!peer) return 0;

      for (sd = first_session; sd; sd = sd->next) {
            if (!sd->sid || sd->type != type ||
               sd->peer.ss_family != peer->sa_family)
                  continue;

            if (peer->sa_family == AF_INET) {
                  struct sockaddr_in *sin = (struct sockaddr_in *)&sd->peer;
                  if (sin->sin_port == ((struct sockaddr_in *)peer)->sin_port &&
                      !memcmp(&sin->sin_addr,
                            &((struct sockaddr_in *)peer)->sin_addr,
                            sizeof(sin->sin_addr)))
                        return sd;
            }
#ifdef      INET6
            else if (peer->sa_family == AF_INET6) {
                  struct sockaddr_in6 *sin = (struct sockaddr_in6 *)&sd->peer;
                  if (sin->sin6_port == ((struct sockaddr_in6 *)peer)->sin6_port &&
                      !memcmp(&sin->sin6_addr,
                            &((struct sockaddr_in6 *)peer)->sin6_addr,
                            sizeof(sin->sin6_addr)))
                        return sd;
            }
#endif
      }
      return 0;
}

int
session_send(sd, data, len)
      SESSION *sd;
      const unsigned char *data;
      int len;
{
      int wlen = 0;

      if (!sd || len < 0) {
            errno = EINVAL;
            return -1;
      }
      if (!sd->sid || sd->sock == -1) {
            errno = ENOTCONN;
            return -1;
      }
      if (data) {
            if (sd->type == PlainFile) {
                  if (len) wlen = write(sd->sock, data, len);

            } else if (sd->type == TextStream) {
                  char buf[BUF_SIZE];

                  if (!sd->peer.ss_family) {
                        errno = ENOTCONN;
                        return -1;
                  }
                  if (len > sizeof(buf)-2) len = sizeof(buf)-2;
                  if (len) memcpy(buf, data, len);
                  if (!len || buf[len-1] != '\n') {
                        buf[len++] = '\r';
                        buf[len++] = '\n';
                  }
                  wlen = write(sd->sock, buf, len);

            } else if (sd->type == DataSequence) {

                  if (!sd->peer.ss_family) {
                        errno = ENOTCONN;
                        return -1;
                  }
                  if (len) wlen = send(sd->sock, data, len, 0);

            } else { /* XXX other session types must be added here */
                  wlen = -1;
                  errno = ESOCKTNOSUPPORT;
            }
      }
      if (wlen == -1) {
            if (errno == EAGAIN || errno == EINPROGRESS) {
                  errno = 0;
                  wlen = 0;
            } else if (sd->read_error) {
                  (*sd->read_error)(sd, errno);
                  return wlen;
            }
      }
      if (sd->timeout > 0) {
            gettimeofday(&sd->expire, 0);
            sd->expire.tv_sec += sd->timeout;
      }
      return wlen;
}

static int
session_read(sd)
      SESSION *sd;
{
      int rlen = 0, rest = 0;
      char *cp, *line, buf[BUF_SIZE];

      if (!sd) {
            errno = EINVAL;
            return -1;
      }
      if (!sd->sid || sd->sock == -1) {
            errno = ENOTCONN;
            return -1;
      }
      buf[0] = '\0';

      if (sd->type == PlainFile) {
            rlen = read(sd->sock, buf, sizeof(buf));

      } else if (sd->type == TextStream) {
            if (sd->buf) { /* previous line was truncated */
                  rest = strlen(strcpy(buf, sd->buf));
                  free(sd->buf);
                  sd->buf = 0;
            }
            rlen = read(sd->sock, &buf[rest], (sizeof(buf)-1) - rest);

      } else if (sd->type == DataSequence) {
            struct sockaddr from;
            socklen_t slen = sizeof(from);

            rlen = recvfrom(sd->sock, buf, sizeof(buf), 0, &from, &slen);
            if (rlen != -1) {
                  /* just for sanity */
                  if (slen < sizeof(struct sockaddr_in) ||
                      slen > sizeof(struct sockaddr))
                        return 0; /* should not happen */

                  if (sd->peer.ss_family &&
                      sd->peer.ss_family != from.sa_family)
                        return 0; /* bad family */

                  /* save packet from */
                  memcpy(&sd->from, &from, slen);
            } else      memset(&sd->from, 0, sizeof(sd->from));

      } else { /* XXX other session types must be added here */
            errno = ESOCKTNOSUPPORT;
            return -1;
      }

      if (rlen < 1) {
            if (!rlen || !errno)
                  errno = ECONNRESET;
            return -1;
      }

      if (sd->type == PlainFile || sd->type == DataSequence) {
            if (!sd->sid || sd->sock == -1)
                  return 0;
            if (sd->read_data)
                  (*sd->read_data)(sd, (u_char *)buf, rlen);

      } else { /* TextStream */
            buf[rest + rlen] = '\0';
            for (cp = buf; (line = strchr(cp, '\n')) != 0; cp = line) {
                  if (line > cp && line[-1] == '\r') line[-1] = '\0';
                  *line++ = '\0';
                  if (!sd->sid || sd->sock == -1)
                        return 0;
                  if (sd->read_data) {
                        rest = strlen(cp);
                        if (rest > MAX_STR_LEN) {
                              errno = EMSGSIZE;
                              return -1;
                        }
                        (*sd->read_data)(sd, (u_char *)cp, rest);
                  }
            }
            if (cp && *cp) { /* truncated line, save it for next read */
                  if (strlen(cp) > MAX_STR_LEN) {
                        errno = EMSGSIZE;
                        return -1;
                  }
                  sd->buf = strdup(cp);
            }
      }
      return rlen;
}

int
session_select(nfds, readfds, writefds, timeout, block)
      int *nfds;
      fd_set *readfds, *writefds;
      struct timeval *timeout;
      int *block;
{
      SESSION *sd;
      struct timeval earliest, now;
      int active = 0, pending = 0;

      timerclear(&earliest);

      /*
       * For each request outstanding, add it's socket to the readfds,
       * and if it is the earliest timeout to expire, mark it as lowest.
       */
      for (sd = first_session; sd; sd = sd->next) {
            if (!sd->sid || sd->sock == -1) {
                  if (sd->sock != -1) /* lost session? free socket */
                        session_stop(sd);
                  continue;
            }

            active++;
            if (sd->sock + 1 > *nfds)
                  *nfds = sd->sock + 1;

            if (!sd->connected) {
                  FD_SET(sd->sock, readfds);

                  dprintf(("session_select: sock %d set for read", sd->sock));
            } else {
                  FD_SET(sd->sock, writefds);

                  dprintf(("session_select: sock %d set for write", sd->sock));
            }

            if (timerisset(&sd->expire)) {
                  pending++;
                  if (!timerisset(&earliest) ||
                      timercmp(&sd->expire, &earliest, <))
                        earliest = sd->expire;
            }
      }

      /*dprintf(("session_select: active=%d pending=%d", active, pending));*/

      if (!pending)
            return active;

      /*
       * Transforms earliest from an absolute time into a delta time, the
       * time left until the select should timeout.
       */
      gettimeofday(&now, 0);
      tv_sub(&earliest, &now);

      /* if it was blocking before or our delta time is less, reset timeout */
      if (*block || timercmp(&earliest, timeout, <)) {
            *timeout = earliest;
            *block = 0;
      }
      return active;
}

/*
 * Checks to see if any of the fd's set in the readfds belong to a session.
 */
void
session_operate(readfds, writefds)
      fd_set *readfds, *writefds;
{
      SESSION *sd;
      int try_conn, error;

      for (sd = first_session; sd; sd = sd->next) {
            if (!sd->sid || sd->sock == -1)
                  continue;

            try_conn = (sd->connected != 0);

            if (!try_conn && FD_ISSET(sd->sock, readfds)) {

                  dprintf(("session_operate: sock %d ready to read", sd->sock));

                  if (sd->type == PlainFile)
                        error = 0;
                  else  error = socket_error(sd->sock);

                  if (!error && session_read(sd) < 0)
                        error = errno;
                  if (error && sd->sid && sd->read_error)
                        (*sd->read_error)(sd, error);
            }

            if (try_conn && FD_ISSET(sd->sock, writefds)) {

                  dprintf(("session_operate: sock %d ready to write", sd->sock));

                  error = socket_error(sd->sock);
                  if (!error) {
                        socket_peer((struct sockaddr *)&sd->peer, sd->sock);
                        socket_nonblock(sd->sock, 0);
                        if (sd->type == TextStream)
                              socket_keepalive(sd->sock, 1);
                        if (sd->sid && sd->connected)
                              (*sd->connected)(sd);
                        sd->connected = 0; /* fire a shot only once! */
                  } else if (sd->sid && sd->read_error)
                        (*sd->read_error)(sd, error);
            }
      }
}

/*
 * Checks to see if any of the sessions have an outstanding request
 * that has timed out.
 */
void
session_timeout()
{
      SESSION *sd;
      struct timeval now;

      gettimeofday(&now, 0);

      for (sd = first_session; sd; sd = sd->next) {
            if (!sd->sid || sd->sock == -1)
                  continue;

            if (timerisset(&sd->expire) && timercmp(&sd->expire, &now, <)) {
                  if (sd->read_error) (*sd->read_error)(sd, ETIMEDOUT);
            }
      }
}

/*
 * Return session peer pointer.
 */
const struct sockaddr *
session_peer(sd)
      SESSION *sd;
{
      return ((sd && sd->peer.ss_family) ? (struct sockaddr *)&sd->peer : 0);
}

/*
 * Return session from pointer.
 */
const struct sockaddr *
session_from(sd)
      SESSION *sd;
{
      return ((sd && sd->from.ss_family) ? (struct sockaddr *)&sd->from : 0);
}

/*
 * Return connected socket peer ip address and port.
 */
int
socket_peer(peer, sock)
      struct sockaddr *peer;
      int sock;
{
      socklen_t arglen;

      if (!peer) {
            errno = EINVAL;
            return -1;
      }
      arglen = sizeof(struct sockaddr);
      return getpeername(sock, peer, &arglen);
}

/*
 * Return socket name ip address and port.
 */
int
socket_name(name, sock)
      struct sockaddr *name;
      int sock;
{
      socklen_t arglen;

      if (!name) {
            errno = EINVAL;
            return -1;
      }
      arglen = sizeof(*name);
      return getsockname(sock, name, &arglen);
}

/*
 * Return socket error (like errno) in the session or 0 if no errors.
 */
int
socket_error(sock)
      int sock;
{
      int argbuf;
      socklen_t arglen;
      struct sockaddr peer;

      arglen = sizeof(argbuf);
      if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &argbuf, &arglen) < 0)
            return errno;
      if (argbuf)
            return argbuf;

      arglen = sizeof(argbuf);
      if (getsockopt(sock, SOL_SOCKET, SO_TYPE, &argbuf, &arglen) < 0)
            return errno;
      if (argbuf == SOCK_STREAM) {
            arglen = sizeof(peer);
            if (getpeername(sock, &peer, &arglen) < 0)
                  return errno;
      }
      return 0;
}

/*
 * Make socket blocked or non-blocked for sync/async I/O.
 */
int
socket_nonblock(sock, on)
      int sock;
      int on; /* boolean */
{
      int mode;
      int prev; /* boolean */

      /* get current value of I/O mode */
      if ((mode = fcntl(sock, F_GETFL, 0)) < 0)
            return -1;

      prev = (mode & ASYNC_MODE) != 0;
      if (on != prev) {
            if (on)     mode |= ASYNC_MODE;
            else  mode &= ~ASYNC_MODE;
            if (fcntl(sock, F_SETFL, mode))
                  return -1;
      }
      return prev;
}

int
socket_keepalive(sock, on)
      int sock, on;
{
#ifdef  SO_KEEPALIVE
      int curr = 0;
      socklen_t slen = sizeof(curr);
      if (getsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &curr, &slen) < 0)
            return -1;

      curr = (curr != 0);
      if (on != curr) {
            if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) < 0)
                  return -1;
      }
      return 0;
#else
      errno = ESOCKTNOSUPPORT;
      return -1;
#endif
}


Generated by  Doxygen 1.6.0   Back to index