#include <sys/types.h>
#include <sys/synch.h>
#include <sys/socket.h>
#include <sys/filio.h>
#include <sys/ioctl.h>
#include <sys/debug.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <errno.h>
#include <poll.h>
#include <pthread.h>
#include <time.h>
#include <signal.h>
#include <thread.h>
#include "vs_incl.h"
#define VS_CXN_MAX VS_VAL_SE_MAXCONN_MAX
typedef enum {
VS_ENG_DISCONNECTED = 0,
VS_ENG_RESERVED,
VS_ENG_INUSE,
VS_ENG_AVAILABLE,
VS_ENG_CLOSE_PENDING
} vs_eng_state_t;
typedef struct vs_connection {
vs_eng_state_t vsc_state;
int vsc_sockfd;
struct timeval vsc_avail_time;
} vs_connection_t;
typedef struct vs_engine {
vs_props_se_t vse_cfg;
int vse_inuse;
boolean_t vse_error;
vs_connection_t vse_cxns[VS_CXN_MAX];
} vs_engine_t;
static vs_engine_t vs_engines[VS_SE_MAX];
static int vs_eng_next;
static int vs_eng_count;
static int vs_eng_total_maxcon;
static int vs_eng_total_inuse;
static int vs_eng_wait_count;
static pthread_mutex_t vs_eng_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t vs_eng_cv;
int vs_inactivity_timeout = 60;
int vs_reuse_connection = 1;
time_t vs_eng_wait = VS_ENG_WAIT_DFLT;
static int vs_eng_connect(char *, int);
static boolean_t vs_eng_check_errors(void);
static int vs_eng_find_connection(int *, int *, boolean_t);
static int vs_eng_find_next(boolean_t);
static int vs_eng_compare(int, char *, int);
static void vs_eng_config_close(vs_engine_t *, int);
static void *vs_eng_housekeeper(void *);
#ifdef FIONBIO
static int nbio_connect(int, const struct sockaddr *, int);
int vs_connect_timeout = 5000;
#endif
void
vs_eng_init()
{
pthread_t tid;
(void) pthread_cond_init(&vs_eng_cv, NULL);
(void) pthread_mutex_lock(&vs_eng_mutex);
(void) memset(vs_engines, 0, sizeof (vs_engine_t) * VS_SE_MAX);
vs_eng_total_maxcon = 0;
vs_eng_total_inuse = 0;
vs_eng_count = 0;
vs_eng_next = 0;
(void) pthread_mutex_unlock(&vs_eng_mutex);
(void) pthread_create(&tid, NULL, vs_eng_housekeeper, NULL);
}
void
vs_eng_config(vs_props_all_t *config)
{
int i;
vs_props_se_t *cfg;
vs_engine_t *eng;
(void) pthread_mutex_lock(&vs_eng_mutex);
vs_eng_count = 0;
vs_eng_total_maxcon = 0;
for (i = 0; i < VS_SE_MAX; i++) {
cfg = &config->va_se[i];
eng = &vs_engines[i];
if (vs_eng_compare(i, cfg->vep_host, cfg->vep_port) != 0) {
vs_eng_config_close(eng, 0);
eng->vse_error = B_FALSE;
}
if (cfg->vep_enable) {
if (cfg->vep_maxconn < eng->vse_cfg.vep_maxconn)
vs_eng_config_close(eng, cfg->vep_maxconn);
eng->vse_cfg = *cfg;
vs_eng_total_maxcon += cfg->vep_maxconn;
vs_eng_count++;
} else {
vs_eng_config_close(eng, 0);
(void) memset(&eng->vse_cfg, 0, sizeof (vs_props_se_t));
}
vs_icap_config(i, eng->vse_cfg.vep_host, eng->vse_cfg.vep_port);
}
if ((vs_eng_total_maxcon <= 0) || (vs_eng_count == 0))
syslog(LOG_NOTICE, "Scan Engine - no engines configured");
(void) pthread_mutex_unlock(&vs_eng_mutex);
}
static void
vs_eng_config_close(vs_engine_t *eng, int start_idx)
{
int i;
vs_connection_t *cxn;
for (i = start_idx; i < eng->vse_cfg.vep_maxconn; i++) {
cxn = &(eng->vse_cxns[i]);
switch (cxn->vsc_state) {
case VS_ENG_RESERVED:
case VS_ENG_INUSE:
cxn->vsc_state = VS_ENG_CLOSE_PENDING;
break;
case VS_ENG_AVAILABLE:
(void) close(cxn->vsc_sockfd);
cxn->vsc_sockfd = -1;
cxn->vsc_state = VS_ENG_DISCONNECTED;
break;
case VS_ENG_CLOSE_PENDING:
case VS_ENG_DISCONNECTED:
break;
}
}
}
void
vs_eng_fini()
{
(void) pthread_cond_destroy(&vs_eng_cv);
}
static void *
vs_eng_housekeeper(void *arg)
{
struct timeval now;
long expire;
int i, j;
vs_engine_t *eng;
vs_connection_t *cxn;
for (;;) {
(void) sleep(vs_inactivity_timeout / 2);
if (vscand_get_state() == VS_STATE_SHUTDOWN)
break;
(void) gettimeofday(&now, NULL);
expire = now.tv_sec - vs_inactivity_timeout;
(void) pthread_mutex_lock(&vs_eng_mutex);
for (i = 0; i < VS_SE_MAX; i++) {
eng = &(vs_engines[i]);
for (j = 0; j < eng->vse_cfg.vep_maxconn; j++) {
cxn = &(eng->vse_cxns[j]);
if ((cxn->vsc_state == VS_ENG_AVAILABLE) &&
(cxn->vsc_avail_time.tv_sec < expire)) {
(void) close(cxn->vsc_sockfd);
cxn->vsc_sockfd = -1;
cxn->vsc_state = VS_ENG_DISCONNECTED;
}
}
}
(void) pthread_mutex_unlock(&vs_eng_mutex);
}
return (NULL);
}
void
vs_eng_set_error(vs_eng_ctx_t *eng_ctx, int error)
{
int eidx = eng_ctx->vse_eidx;
int cidx = eng_ctx->vse_cidx;
vs_engine_t *eng;
(void) pthread_mutex_lock(&vs_eng_mutex);
eng = &(vs_engines[eidx]);
if (vs_eng_compare(eidx, eng_ctx->vse_host, eng_ctx->vse_port) == 0)
eng->vse_error = (error == 0) ? B_FALSE : B_TRUE;
if (error != 0) {
eng->vse_cxns[cidx].vsc_state = VS_ENG_CLOSE_PENDING;
vs_stats_eng_err(eng_ctx->vse_engid);
}
(void) pthread_mutex_unlock(&vs_eng_mutex);
}
int
vs_eng_get(vs_eng_ctx_t *eng_ctx, boolean_t retry)
{
struct timespec tswait;
int eidx, cidx, sockfd;
vs_engine_t *eng;
vs_connection_t *cxn;
(void) pthread_mutex_lock(&vs_eng_mutex);
if ((vs_eng_total_maxcon <= 0) ||
((retry == B_TRUE) && (vs_eng_count <= 1))) {
(void) pthread_mutex_unlock(&vs_eng_mutex);
return (-1);
}
tswait.tv_sec = vs_eng_wait;
tswait.tv_nsec = 0;
while ((vscand_get_state() != VS_STATE_SHUTDOWN) &&
(vs_eng_find_connection(&eidx, &cidx, retry) == -1)) {
if (retry && vs_eng_check_errors() == B_TRUE) {
(void) pthread_mutex_unlock(&vs_eng_mutex);
return (-1);
}
vs_eng_wait_count++;
if (pthread_cond_reltimedwait_np(&vs_eng_cv, &vs_eng_mutex,
&tswait) < 0) {
syslog(LOG_NOTICE, "Scan Engine "
"- timeout waiting for available engine");
vs_eng_wait_count--;
(void) pthread_mutex_unlock(&vs_eng_mutex);
return (-1);
}
vs_eng_wait_count--;
}
if (vscand_get_state() == VS_STATE_SHUTDOWN) {
(void) pthread_mutex_unlock(&vs_eng_mutex);
return (-1);
}
eng = &(vs_engines[eidx]);
cxn = &(eng->vse_cxns[cidx]);
eng->vse_inuse++;
vs_eng_total_inuse++;
if (!retry)
vs_eng_next = (eidx == VS_SE_MAX) ? 0 : eidx + 1;
eng_ctx->vse_eidx = eidx;
eng_ctx->vse_cidx = cidx;
(void) strlcpy(eng_ctx->vse_engid, eng->vse_cfg.vep_engid,
sizeof (eng_ctx->vse_engid));
(void) strlcpy(eng_ctx->vse_host, eng->vse_cfg.vep_host,
sizeof (eng_ctx->vse_host));
eng_ctx->vse_port = eng->vse_cfg.vep_port;
eng_ctx->vse_sockfd = cxn->vsc_sockfd;
if (cxn->vsc_state == VS_ENG_INUSE) {
(void) pthread_mutex_unlock(&vs_eng_mutex);
return (0);
}
(void) pthread_mutex_unlock(&vs_eng_mutex);
sockfd = vs_eng_connect(eng_ctx->vse_host, eng_ctx->vse_port);
if (sockfd == -1) {
(void) sleep(1);
sockfd = vs_eng_connect(eng_ctx->vse_host, eng_ctx->vse_port);
}
if (sockfd == -1) {
syslog(LOG_NOTICE, "Scan Engine - connection error (%s:%d) %s",
eng_ctx->vse_host, eng_ctx->vse_port,
errno ? strerror(errno) : "");
vs_eng_set_error(eng_ctx, 1);
vs_eng_release(eng_ctx);
return (-1);
}
(void) pthread_mutex_lock(&vs_eng_mutex);
switch (cxn->vsc_state) {
case VS_ENG_DISCONNECTED:
(void) pthread_mutex_unlock(&vs_eng_mutex);
vs_eng_release(eng_ctx);
return (-1);
case VS_ENG_RESERVED:
cxn->vsc_state = VS_ENG_INUSE;
break;
case VS_ENG_CLOSE_PENDING:
break;
case VS_ENG_INUSE:
case VS_ENG_AVAILABLE:
default:
ASSERT(0);
break;
}
cxn->vsc_sockfd = sockfd;
eng_ctx->vse_sockfd = sockfd;
(void) pthread_mutex_unlock(&vs_eng_mutex);
return (0);
}
static boolean_t
vs_eng_check_errors()
{
int i;
for (i = 0; i < VS_SE_MAX; i++) {
if (vs_engines[i].vse_cfg.vep_maxconn > 0 &&
(vs_engines[i].vse_error == B_FALSE))
return (B_FALSE);
}
return (B_TRUE);
}
static int
vs_eng_find_connection(int *eng_idx, int *cxn_idx, boolean_t retry)
{
int i, idx;
vs_engine_t *eng;
vs_connection_t *cxn;
if ((idx = vs_eng_find_next(retry)) == -1)
return (-1);
eng = &(vs_engines[idx]);
*eng_idx = idx;
idx = -1;
for (i = 0; i < eng->vse_cfg.vep_maxconn; i++) {
cxn = &(eng->vse_cxns[i]);
if (cxn->vsc_state == VS_ENG_AVAILABLE) {
*cxn_idx = i;
cxn->vsc_state = VS_ENG_INUSE;
return (0);
}
if ((idx == -1) &&
(cxn->vsc_state == VS_ENG_DISCONNECTED)) {
idx = i;
}
}
if (idx == -1)
return (-1);
eng->vse_cxns[idx].vsc_state = VS_ENG_RESERVED;
*cxn_idx = idx;
return (0);
}
static int
vs_eng_find_next(boolean_t retry)
{
int i;
for (i = vs_eng_next; i < VS_SE_MAX; i++) {
if (vs_engines[i].vse_inuse <
vs_engines[i].vse_cfg.vep_maxconn) {
if (!retry || (vs_engines[i].vse_error == B_FALSE))
return (i);
}
}
for (i = 0; i < vs_eng_next; i++) {
if (vs_engines[i].vse_inuse <
vs_engines[i].vse_cfg.vep_maxconn) {
if (!retry || (vs_engines[i].vse_error == B_FALSE))
return (i);
}
}
return (-1);
}
void
vs_eng_release(const vs_eng_ctx_t *eng_ctx)
{
int eidx = eng_ctx->vse_eidx;
int cidx = eng_ctx->vse_cidx;
vs_connection_t *cxn;
(void) pthread_mutex_lock(&vs_eng_mutex);
cxn = &(vs_engines[eidx].vse_cxns[cidx]);
switch (cxn->vsc_state) {
case VS_ENG_DISCONNECTED:
break;
case VS_ENG_RESERVED:
cxn->vsc_state = VS_ENG_DISCONNECTED;
break;
case VS_ENG_INUSE:
if (vs_reuse_connection) {
cxn->vsc_state = VS_ENG_AVAILABLE;
(void) gettimeofday(&cxn->vsc_avail_time, NULL);
break;
}
case VS_ENG_CLOSE_PENDING:
(void) close(cxn->vsc_sockfd);
cxn->vsc_sockfd = -1;
cxn->vsc_state = VS_ENG_DISCONNECTED;
break;
case VS_ENG_AVAILABLE:
default:
ASSERT(0);
break;
}
vs_engines[eidx].vse_inuse--;
vs_eng_total_inuse--;
(void) pthread_cond_signal(&vs_eng_cv);
(void) pthread_mutex_unlock(&vs_eng_mutex);
}
void
vs_eng_close_connections(void)
{
int i, j;
vs_connection_t *cxn;
(void) pthread_mutex_lock(&vs_eng_mutex);
vs_eng_total_maxcon = 0;
for (i = 0; i < VS_SE_MAX; i++) {
for (j = 0; j < VS_CXN_MAX; j++) {
cxn = &(vs_engines[i].vse_cxns[j]);
switch (cxn->vsc_state) {
case VS_ENG_INUSE:
case VS_ENG_AVAILABLE:
case VS_ENG_CLOSE_PENDING:
(void) close(cxn->vsc_sockfd);
cxn->vsc_sockfd = -1;
break;
case VS_ENG_DISCONNECTED:
case VS_ENG_RESERVED:
default:
break;
}
cxn->vsc_state = VS_ENG_DISCONNECTED;
}
}
(void) pthread_mutex_unlock(&vs_eng_mutex);
}
static int
vs_eng_connect(char *host, int port)
{
int rc, sockfd, opt_nodelay, opt_keepalive, opt_reuseaddr, err_num;
struct sockaddr_in addr;
struct hostent *hp;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
return (-1);
hp = getipnodebyname(host, AF_INET, 0, &err_num);
if (hp == NULL) {
(void) close(sockfd);
return (-1);
}
(void) memset(&addr, 0, sizeof (addr));
(void) memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
addr.sin_port = htons(port);
addr.sin_family = hp->h_addrtype;
freehostent(hp);
#ifdef FIONBIO
rc = nbio_connect(sockfd, (struct sockaddr *)&addr,
sizeof (struct sockaddr));
#else
rc = connect(sockfd, (struct sockaddr *)&addr,
sizeof (struct sockaddr));
#endif
opt_nodelay = 1;
opt_keepalive = 1;
opt_reuseaddr = 1;
if ((rc < 0) ||
(setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,
&opt_nodelay, sizeof (opt_nodelay)) < 0) ||
(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE,
&opt_keepalive, sizeof (opt_keepalive)) < 0) ||
(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
&opt_reuseaddr, sizeof (opt_reuseaddr)) < 0)) {
(void) close(sockfd);
return (-1);
}
return (sockfd);
}
#ifdef FIONBIO
static int
nbio_connect(int sockfd, const struct sockaddr *sa, int sa_len)
{
struct pollfd pfd;
int nbio, rc;
int error, len = sizeof (error);
nbio = 1;
if ((ioctl(sockfd, FIONBIO, &nbio)) < 0)
return (connect(sockfd, sa, sa_len));
if ((rc = connect(sockfd, sa, sa_len)) != 0) {
if (errno == EINPROGRESS || errno == EINTR) {
errno = 0;
pfd.fd = sockfd;
pfd.events = POLLOUT;
pfd.revents = 0;
if ((rc = poll(&pfd, 1, vs_connect_timeout)) <= 0) {
if (rc == 0)
errno = ETIMEDOUT;
rc = -1;
} else {
if ((pfd.revents &
(POLLHUP | POLLERR | POLLNVAL)) ||
(!(pfd.revents & POLLOUT))) {
rc = -1;
} else {
rc = getsockopt(sockfd, SOL_SOCKET,
SO_ERROR, &error, &len);
if (rc != 0 || error != 0)
rc = -1;
if (error != 0)
errno = error;
}
}
}
}
nbio = 0;
(void) ioctl(sockfd, FIONBIO, &nbio);
return (rc);
}
#endif
int
vs_eng_scanstamp_current(vs_scanstamp_t scanstamp)
{
int i;
if (scanstamp[0] == '\0')
return (0);
(void) pthread_mutex_lock(&vs_eng_mutex);
for (i = 0; i < VS_SE_MAX; i++) {
if ((vs_engines[i].vse_cfg.vep_enable) &&
(vs_engines[i].vse_error == B_FALSE) &&
(vs_icap_compare_scanstamp(i, scanstamp) == 0))
break;
}
(void) pthread_mutex_unlock(&vs_eng_mutex);
return ((i < VS_SE_MAX) ? 1 : 0);
}
static int
vs_eng_compare(int idx, char *host, int port)
{
if (vs_engines[idx].vse_cfg.vep_port != port)
return (-1);
if (strcmp(vs_engines[idx].vse_cfg.vep_host, host) != 0)
return (-1);
return (0);
}