blob: 3fb6632c5d3aa5b8fbe868195e67f22c1a61b4be [file] [log] [blame]
/**
* @file snmpSocketBaseDomain.c
*
* @brief Socket support functions.
*/
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/types.h>
#include <net-snmp/library/snmpSocketBaseDomain.h>
#include <stddef.h>
#include <stdio.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/types.h>
#include <ctype.h>
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include <errno.h>
#include <net-snmp/types.h>
#include <net-snmp/library/snmp_debug.h>
#include <net-snmp/library/tools.h>
#include <net-snmp/library/default_store.h>
#include <net-snmp/library/system.h>
#include <net-snmp/library/snmp_assert.h>
/* all sockets pretty much close the same way */
int netsnmp_socketbase_close(netsnmp_transport *t) {
int rc = -1;
if (t->sock >= 0) {
#ifndef HAVE_CLOSESOCKET
rc = close(t->sock);
#else
rc = closesocket(t->sock);
#endif
t->sock = -1;
}
return rc;
}
/*
* find largest possible buffer between current size and specified size.
*
* Try to maximize the current buffer of type "optname"
* to the maximum allowable size by the OS (as close to
* size as possible)
*/
static int
_sock_buffer_maximize(int s, int optname, const char *buftype, int size)
{
int curbuf = 0;
socklen_t curbuflen = sizeof(int);
int lo, mid, hi;
/*
* First we need to determine our current buffer
*/
if ((getsockopt(s, SOL_SOCKET, optname, (void *) &curbuf,
&curbuflen) == 0)
&& (curbuflen == sizeof(int))) {
DEBUGMSGTL(("verbose:socket:buffer:max", "Current %s is %d\n",
buftype, curbuf));
/*
* Let's not be stupid ... if we were asked for less than what we
* already have, then forget about it
*/
if (size <= curbuf) {
DEBUGMSGTL(("verbose:socket:buffer:max",
"Requested %s <= current buffer\n", buftype));
return curbuf;
}
/*
* Do a binary search the optimal buffer within 1k of the point of
* failure. This is rather bruteforce, but simple
*/
hi = size;
lo = curbuf;
while (hi - lo > 1024) {
mid = (lo + hi) / 2;
if (setsockopt(s, SOL_SOCKET, optname, (void *) &mid,
sizeof(int)) == 0) {
lo = mid; /* Success: search between mid and hi */
} else {
hi = mid; /* Failed: search between lo and mid */
}
}
/*
* Now print if this optimization helped or not
*/
if (getsockopt(s,SOL_SOCKET, optname, (void *) &curbuf,
&curbuflen) == 0) {
DEBUGMSGTL(("socket:buffer:max",
"Maximized %s: %d\n",buftype, curbuf));
}
} else {
/*
* There is really not a lot we can do anymore.
* If the OS doesn't give us the current buffer, then what's the
* point in trying to make it better
*/
DEBUGMSGTL(("socket:buffer:max", "Get %s failed ... giving up!\n",
buftype));
curbuf = -1;
}
return curbuf;
}
static const char *
_sock_buf_type_get(int optname, int local)
{
if (optname == SO_SNDBUF) {
if (local)
return "server send buffer";
else
return "client send buffer";
} else if (optname == SO_RCVBUF) {
if (local)
return "server receive buffer";
else
return "client receive buffer";
}
return "unknown buffer";
}
/*
*
* Get the requested buffersize, based on
* - sockettype : client (local = 0) or server (local = 1)
* - buffertype : send (optname = SO_SNDBUF) or recv (SO_RCVBUF)
*
* In case a compile time buffer was specified, then use that one
* if there was no runtime configuration override
*/
static int
_sock_buffer_size_get(int optname, int local, const char **buftype)
{
int size;
if (NULL != buftype)
*buftype = _sock_buf_type_get(optname, local);
if (optname == SO_SNDBUF) {
if (local) {
size = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_SERVERSENDBUF);
#ifdef NETSNMP_DEFAULT_SERVER_SEND_BUF
if (size <= 0)
size = NETSNMP_DEFAULT_SERVER_SEND_BUF;
#endif
} else {
size = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_CLIENTSENDBUF);
#ifdef NETSNMP_DEFAULT_CLIENT_SEND_BUF
if (size <= 0)
size = NETSNMP_DEFAULT_CLIENT_SEND_BUF;
#endif
}
} else if (optname == SO_RCVBUF) {
if (local) {
size = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_SERVERRECVBUF);
#ifdef NETSNMP_DEFAULT_SERVER_RECV_BUF
if (size <= 0)
size = NETSNMP_DEFAULT_SERVER_RECV_BUF;
#endif
} else {
size = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_CLIENTRECVBUF);
#ifdef NETSNMP_DEFAULT_CLIENT_RECV_BUF
if (size <= 0)
size = NETSNMP_DEFAULT_CLIENT_RECV_BUF;
#endif
}
} else {
size = 0;
}
DEBUGMSGTL(("socket:buffer", "Requested %s is %d\n",
(buftype) ? *buftype : "unknown buffer", size));
return(size);
}
/*
* set socket buffer size
*
* @param ss : socket
* @param optname: SO_SNDBUF or SO_RCVBUF
* @param local : 1 for server, 0 for client
* @param reqbuf : requested size, or 0 for default
*
* @retval -1 : error
* @retval >0 : new buffer size
*/
int
netsnmp_sock_buffer_set(int s, int optname, int local, int size)
{
#if ! defined(SO_SNDBUF) && ! defined(SO_RCVBUF)
DEBUGMSGTL(("socket:buffer", "Changing socket buffer is not supported\n"));
return -1;
#else
const char *buftype;
int curbuf = 0;
socklen_t curbuflen = sizeof(int);
# ifndef SO_SNDBUF
if (SO_SNDBUF == optname) {
DEBUGMSGTL(("socket:buffer",
"Changing socket send buffer is not supported\n"));
return -1;
}
# endif /*SO_SNDBUF */
# ifndef SO_RCVBUF
if (SO_RCVBUF == optname) {
DEBUGMSGTL(("socket:buffer",
"Changing socket receive buffer is not supported\n"));
return -1;
}
# endif /*SO_RCVBUF */
/*
* What is the requested buffer size ?
*/
if (0 == size)
size = _sock_buffer_size_get(optname, local, &buftype);
else {
buftype = _sock_buf_type_get(optname, local);
DEBUGMSGT(("verbose:socket:buffer", "Requested %s is %d\n",
buftype, size));
}
if ((getsockopt(s, SOL_SOCKET, optname, (void *) &curbuf,
&curbuflen) == 0)
&& (curbuflen == sizeof(int))) {
DEBUGMSGT(("verbose:socket:buffer", "Original %s is %d\n",
buftype, curbuf));
if (curbuf >= size) {
DEBUGMSGT(("verbose:socket:buffer",
"New %s size is smaller than original!\n", buftype));
}
}
/*
* If the buffersize was not specified or it was a negative value
* then don't change the OS buffers at all
*/
if (size <= 0) {
DEBUGMSGT(("socket:buffer",
"%s not valid or not specified; using OS default(%d)\n",
buftype,curbuf));
return curbuf;
}
/*
* Try to set the requested send buffer
*/
if (setsockopt(s, SOL_SOCKET, optname, (void *) &size, sizeof(int)) == 0) {
/*
* Because some platforms lie about the actual buffer that has been
* set (Linux will always say it worked ...), we print some
* diagnostic output for debugging
*/
DEBUGIF("socket:buffer") {
DEBUGMSGT(("socket:buffer", "Set %s to %d\n",
buftype, size));
if ((getsockopt(s, SOL_SOCKET, optname, (void *) &curbuf,
&curbuflen) == 0)
&& (curbuflen == sizeof(int))) {
DEBUGMSGT(("verbose:socket:buffer",
"Now %s is %d\n", buftype, curbuf));
}
}
/*
* If the new buffer is smaller than the size we requested, we will
* try to increment the new buffer with 1k increments
* (this will sometime allow us to reach a more optimal buffer.)
* For example : On Solaris, if the max OS buffer is 100k and you
* request 110k, you end up with the default 8k :-(
*/
if (curbuf < size) {
curbuf = _sock_buffer_maximize(s, optname, buftype, size);
if(-1 != curbuf)
size = curbuf;
}
} else {
/*
* Obviously changing the buffer failed, most like like because we
* requested a buffer greater than the OS limit.
* Therefore we need to search for an optimal buffer that is close
* enough to the point of failure.
* This will allow us to reach a more optimal buffer.
* For example : On Solaris, if the max OS buffer is 100k and you
* request 110k, you end up with the default 8k :-(
* After this quick seach we would get 1k close to 100k (the max)
*/
DEBUGMSGTL(("socket:buffer", "couldn't set %s to %d\n",
buftype, size));
curbuf = _sock_buffer_maximize(s, optname, buftype, size);
if(-1 != curbuf)
size = curbuf;
}
return size;
#endif
}
/**
* Sets the mode of a socket for all subsequent I/O operations.
*
* @param[in] sock Socket descriptor (Unix) or socket handle (Windows).
* @param[in] non_blocking_mode I/O mode: non-zero selects non-blocking mode;
* zero selects blocking mode.
*
* @return zero upon success and a negative value upon error.
*/
int
netsnmp_set_non_blocking_mode(int sock, int non_blocking_mode)
{
#ifdef WIN32
u_long arg;
arg = non_blocking_mode;
return ioctlsocket(sock, FIONBIO, &arg);
#else
int sockflags;
if ((sockflags = fcntl(sock, F_GETFL, 0)) >= 0) {
return fcntl(sock, F_SETFL,
non_blocking_mode ? sockflags | O_NONBLOCK
: sockflags & ~O_NONBLOCK);
} else
return -1;
#endif
}