refactor create_udp_socket

Function used very old style and was hard to read. Now also supports
strucutred handling of exceptions.

see also https://github.com/rsyslog/rsyslog/pull/2049
see also https://github.com/rsyslog/rsyslog/issues/2040
This commit is contained in:
Rainer Gerhards 2017-11-21 12:36:18 +01:00
parent 08f2d10caa
commit ee1499f462

View File

@ -1252,6 +1252,206 @@ closeUDPListenSockets(int *pSockArr)
}
/* create a single UDP socket and bail out if an error occurs.
* This is called from a loop inside create_udp_socket which
* iterates through potentially multiple sockets. NOT to be
* used elsewhere.
*/
static rsRetVal ATTR_NONNULL(1, 2)
create_single_udp_socket(int *const s, /* socket */
struct addrinfo *const r,
const uchar *const hostname,
const int bIsServer,
const int rcvbuf,
const int sndbuf,
const int ipfreebind,
const char *const device
)
{
const int on = 1;
int sockflags;
int actrcvbuf;
int actsndbuf;
socklen_t optlen;
char errStr[1024];
DEFiRet;
assert(r != NULL); // does NOT work with -O2 or higher due to ATTR_NONNULL!
assert(s != NULL);
# if defined (_AIX)
/* AIXPORT : socktype will be SOCK_DGRAM, as set in hints before */
*s = socket(r->ai_family, SOCK_DGRAM, r->ai_protocol);
# else
*s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
# endif
if (*s < 0) {
if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) {
errmsg.LogError(errno, NO_ERRCODE, "create_udp_socket(), socket");
/* it is debateble if PF_INET with EAFNOSUPPORT should
* also be ignored...
*/
}
ABORT_FINALIZE(RS_RET_ERR);
}
# ifdef IPV6_V6ONLY
if (r->ai_family == AF_INET6) {
int ion = 1;
if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
(char *)&ion, sizeof (ion)) < 0) {
errmsg.LogError(errno, RS_RET_ERR, "error creating UDP socket - setsockopt");
ABORT_FINALIZE(RS_RET_ERR);
}
}
# endif
if(device) {
# if defined(SO_BINDTODEVICE)
if(setsockopt(*s, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1) < 0)
# endif
{
errmsg.LogError(errno, RS_RET_ERR, "create UDP socket bound to device failed");
ABORT_FINALIZE(RS_RET_ERR);
}
}
if(setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0 ) {
errmsg.LogError(errno, RS_RET_ERR, "create UDP socket failed to set REUSEADDR");
ABORT_FINALIZE(RS_RET_ERR);
}
/* We need to enable BSD compatibility. Otherwise an attacker
* could flood our log files by sending us tons of ICMP errors.
*/
/* AIXPORT : SO_BSDCOMPAT socket option is depricated, and its usage
* has been discontinued on most unixes, AIX does not support this option,
* hence avoid the call.
*/
# if !defined(OS_BSD) && !defined(__hpux) && !defined(_AIX)
if (should_use_so_bsdcompat()) {
if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT, (char *) &on, sizeof(on)) < 0) {
errmsg.LogError(errno, RS_RET_ERR, "create UDP socket failed to set BSDCOMPAT");
ABORT_FINALIZE(RS_RET_ERR);
}
}
# endif
if(bIsServer) {
DBGPRINTF("net.c: trying to set server socket %d to non-blocking mode\n", *s);
if ((sockflags = fcntl(*s, F_GETFL)) != -1) {
sockflags |= O_NONBLOCK;
/* SETFL could fail too, so get it caught by the subsequent
* error check.
*/
sockflags = fcntl(*s, F_SETFL, sockflags);
}
if (sockflags == -1) {
LogError(errno, RS_RET_ERR, "net.c: socket %d fcntl(O_NONBLOCK)", *s);
ABORT_FINALIZE(RS_RET_ERR);
}
}
if(sndbuf != 0) {
# if defined(SO_SNDBUFFORCE)
if(setsockopt(*s, SOL_SOCKET, SO_SNDBUFFORCE, &sndbuf, sizeof(sndbuf)) < 0)
# endif
{
/* if we fail, try to do it the regular way. Experiments show that at
* least some platforms do not return an error here, but silently set
* it to the max permitted value. So we do our error check a bit
* differently by querying the size below.
*/
if(setsockopt(*s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) != 0) {
/* keep Coverity happy */
DBGPRINTF("setsockopt in %s:%d failed - this is expected and "
"handled at later stages\n", __FILE__, __LINE__);
}
}
/* report socket buffer sizes */
optlen = sizeof(actsndbuf);
if(getsockopt(*s, SOL_SOCKET, SO_SNDBUF, &actsndbuf, &optlen) == 0) {
LogMsg(0, NO_ERRCODE, LOG_INFO,
"socket %d, actual os socket sndbuf size is %d", *s, actsndbuf);
if(sndbuf != 0 && actsndbuf/2 != sndbuf) {
errmsg.LogError(errno, NO_ERRCODE,
"could not set os socket sndbuf size %d for socket %d, "
"value now is %d", sndbuf, *s, actsndbuf/2);
}
} else {
DBGPRINTF("could not obtain os socket rcvbuf size for socket %d: %s\n",
*s, rs_strerror_r(errno, errStr, sizeof(errStr)));
}
}
if(rcvbuf != 0) {
# if defined(SO_RCVBUFFORCE)
if(setsockopt(*s, SOL_SOCKET, SO_RCVBUFFORCE, &rcvbuf, sizeof(rcvbuf)) < 0)
# endif
{
/* if we fail, try to do it the regular way. Experiments show that at
* least some platforms do not return an error here, but silently set
* it to the max permitted value. So we do our error check a bit
* differently by querying the size below.
*/
if(setsockopt(*s, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) != 0) {
/* keep Coverity happy */
DBGPRINTF("setsockopt in %s:%d failed - this is expected and "
"handled at later stages\n", __FILE__, __LINE__);
}
}
optlen = sizeof(actrcvbuf);
if(getsockopt(*s, SOL_SOCKET, SO_RCVBUF, &actrcvbuf, &optlen) == 0) {
LogMsg(0, NO_ERRCODE, LOG_INFO,
"socket %d, actual os socket rcvbuf size %d\n", *s, actrcvbuf);
if(rcvbuf != 0 && actrcvbuf/2 != rcvbuf) {
errmsg.LogError(errno, NO_ERRCODE,
"cannot set os socket rcvbuf size %d for socket %d, value now is %d",
rcvbuf, *s, actrcvbuf/2);
}
} else {
DBGPRINTF("could not obtain os socket rcvbuf size for socket %d: %s\n",
*s, rs_strerror_r(errno, errStr, sizeof(errStr)));
}
}
if(bIsServer) {
/* rgerhards, 2007-06-22: if we run on a kernel that does not support
* the IPV6_V6ONLY socket option, we need to use a work-around. On such
* systems the IPv6 socket does also accept IPv4 sockets. So an IPv4
* socket can not listen on the same port as an IPv6 socket. The only
* workaround is to ignore the "socket in use" error. This is what we
* do if we have to.
*/
if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0)
# ifndef IPV6_V6ONLY
&& (errno != EADDRINUSE)
# endif
) {
if (errno == EADDRNOTAVAIL && ipfreebind != IPFREEBIND_DISABLED) {
if (setsockopt(*s, IPPROTO_IP, IP_FREEBIND, &on, sizeof(on)) < 0) {
errmsg.LogError(errno, RS_RET_ERR, "setsockopt(IP_FREEBIND)");
} else if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
errmsg.LogError(errno, RS_RET_ERR, "bind with IP_FREEBIND");
} else {
if (ipfreebind >= IPFREEBIND_ENABLED_WITH_LOG)
errmsg.LogMsg(0, RS_RET_OK_WARN, LOG_WARNING,
"bound address %s IP free", hostname);
//FINALIZE; TODO: activate this (issue 2040) BUT ensure first
// that the option is actually configurable
}
}
ABORT_FINALIZE(RS_RET_ERR);
}
}
finalize_it:
if(iRet != RS_RET_OK) {
close(*s);
*s = -1;
}
RETiRet;
}
/* creates the UDP listen sockets
* hostname and/or pszPort may be NULL, but not both!
* bIsServer indicates if a server socket should be created
@ -1264,21 +1464,17 @@ closeUDPListenSockets(int *pSockArr)
static int *
create_udp_socket(uchar *hostname,
uchar *pszPort,
int bIsServer,
int rcvbuf,
const int bIsServer,
const int rcvbuf,
const int sndbuf,
int ipfreebind,
const int ipfreebind,
char *device)
{
struct addrinfo hints, *res, *r;
int error, maxs, *s, *socks, on = 1;
int sockflags;
int actrcvbuf;
int actsndbuf;
socklen_t optlen;
char errStr[1024];
int error, maxs, *s, *socks;
rsRetVal localRet;
assert(!((pszPort == NULL) && (hostname == NULL)));
assert(!((pszPort == NULL) && (hostname == NULL))); /* one of them must be non-NULL */
memset(&hints, 0, sizeof(hints));
if(bIsServer)
hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
@ -1286,12 +1482,12 @@ create_udp_socket(uchar *hostname,
hints.ai_flags = AI_NUMERICSERV;
hints.ai_family = glbl.GetDefPFFamily();
hints.ai_socktype = SOCK_DGRAM;
#if defined (_AIX)
/* AIXPORT : SOCK_DGRAM has the protocol IPPROTO_UDP
* getaddrinfo needs this hint on AIX
*/
# if defined (_AIX)
/* AIXPORT : SOCK_DGRAM has the protocol IPPROTO_UDP
* getaddrinfo needs this hint on AIX
*/
hints.ai_protocol = IPPROTO_UDP;
#endif
# endif
error = getaddrinfo((char*) hostname, (char*) pszPort, &hints, &res);
if(error) {
errmsg.LogError(0, NO_ERRCODE, "%s", gai_strerror(error));
@ -1304,202 +1500,21 @@ create_udp_socket(uchar *hostname,
/* EMPTY */;
socks = MALLOC((maxs+1) * sizeof(int));
if (socks == NULL) {
errmsg.LogError(0, NO_ERRCODE, "couldn't allocate memory for UDP sockets, suspending UDP "
"message reception");
freeaddrinfo(res);
return NULL;
errmsg.LogError(0, RS_RET_OUT_OF_MEMORY, "couldn't allocate memory for UDP "
"sockets, suspending UDP message reception");
freeaddrinfo(res);
return NULL;
}
*socks = 0; /* num of sockets counter at start of array */
s = socks + 1;
for (r = res; r != NULL ; r = r->ai_next) {
#if defined (_AIX)
/* AIXPORT : socktype will be SOCK_DGRAM, as set in hints above */
*s = socket(r->ai_family, SOCK_DGRAM, r->ai_protocol);
#else
*s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
#endif
if (*s < 0) {
if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT))
errmsg.LogError(errno, NO_ERRCODE, "create_udp_socket(), socket");
/* it is debateble if PF_INET with EAFNOSUPPORT should
* also be ignored...
*/
continue;
}
# ifdef IPV6_V6ONLY
if (r->ai_family == AF_INET6) {
int ion = 1;
if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
(char *)&ion, sizeof (ion)) < 0) {
errmsg.LogError(errno, NO_ERRCODE, "setsockopt");
close(*s);
*s = -1;
continue;
}
}
# endif
if(device) {
# if defined(SO_BINDTODEVICE)
if(setsockopt(*s, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1) < 0)
# endif
{
errmsg.LogError(errno, NO_ERRCODE, "setsockopt(SO_BINDTODEVICE)");
close(*s);
*s = -1;
continue;
}
localRet = create_single_udp_socket(s, r, hostname, bIsServer, rcvbuf,
sndbuf, ipfreebind, device);
if(localRet == RS_RET_OK) {
(*socks)++;
s++;
}
/* if we have an error, we "just" suspend that socket. Eventually
* other sockets will work. At the end of this function, we check
* if we managed to open at least one socket. If not, we'll write
* a "inet suspended" message and declare failure. Else we use
* what we could obtain.
* rgerhards, 2007-06-22
*/
if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR,
(char *) &on, sizeof(on)) < 0 ) {
errmsg.LogError(errno, NO_ERRCODE, "setsockopt(REUSEADDR)");
close(*s);
*s = -1;
continue;
}
/* We need to enable BSD compatibility. Otherwise an attacker
* could flood our log files by sending us tons of ICMP errors.
*/
/* AIXPORT : SO_BSDCOMPAT socket option is depricated , and its usage has been discontinued
on most unixes, AIX does not support this option , hence avoid the call.
*/
#if !defined(OS_BSD) && !defined(__hpux) && !defined(_AIX)
if (should_use_so_bsdcompat()) {
if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT,
(char *) &on, sizeof(on)) < 0) {
errmsg.LogError(errno, NO_ERRCODE, "setsockopt(BSDCOMPAT)");
close(*s);
*s = -1;
continue;
}
}
#endif
if(bIsServer) {
DBGPRINTF("net.c: trying to set server socket %d to non-blocking mode\n", *s);
if ((sockflags = fcntl(*s, F_GETFL)) != -1) {
sockflags |= O_NONBLOCK;
/* SETFL could fail too, so get it caught by the subsequent
* error check.
*/
sockflags = fcntl(*s, F_SETFL, sockflags);
}
if (sockflags == -1) {
LogError(errno, NO_ERRCODE, "net.c: socket %d fcntl(O_NONBLOCK)", *s);
close(*s);
*s = -1;
continue;
}
}
if(sndbuf != 0) {
# if defined(SO_SNDBUFFORCE)
if(setsockopt(*s, SOL_SOCKET, SO_SNDBUFFORCE, &sndbuf, sizeof(sndbuf)) < 0)
# endif
{
/* if we fail, try to do it the regular way. Experiments show that at
* least some platforms do not return an error here, but silently set
* it to the max permitted value. So we do our error check a bit
* differently by querying the size below.
*/
if(setsockopt(*s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) != 0) {
/* keep Coverity happy */
DBGPRINTF("setsockopt in %s:%d failed - this is expected and "
"handled at later stages\n", __FILE__, __LINE__);
}
}
/* report socket buffer sizes */
optlen = sizeof(actsndbuf);
if(getsockopt(*s, SOL_SOCKET, SO_SNDBUF, &actsndbuf, &optlen) == 0) {
LogMsg(0, NO_ERRCODE, LOG_INFO,
"socket %d, actual os socket sndbuf size is %d", *s, actsndbuf);
if(sndbuf != 0 && actsndbuf/2 != sndbuf) {
errmsg.LogError(errno, NO_ERRCODE,
"could not set os socket sndbuf size %d for socket %d, "
"value now is %d", sndbuf, *s, actsndbuf/2);
}
} else {
DBGPRINTF("could not obtain os socket rcvbuf size for socket %d: %s\n",
*s, rs_strerror_r(errno, errStr, sizeof(errStr)));
}
}
if(rcvbuf != 0) {
# if defined(SO_RCVBUFFORCE)
if(setsockopt(*s, SOL_SOCKET, SO_RCVBUFFORCE, &rcvbuf, sizeof(rcvbuf)) < 0)
# endif
{
/* if we fail, try to do it the regular way. Experiments show that at
* least some platforms do not return an error here, but silently set
* it to the max permitted value. So we do our error check a bit
* differently by querying the size below.
*/
if(setsockopt(*s, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) != 0) {
/* keep Coverity happy */
DBGPRINTF("setsockopt in %s:%d failed - this is expected and "
"handled at later stages\n", __FILE__, __LINE__);
}
}
optlen = sizeof(actrcvbuf);
if(getsockopt(*s, SOL_SOCKET, SO_RCVBUF, &actrcvbuf, &optlen) == 0) {
LogMsg(0, NO_ERRCODE, LOG_INFO,
"socket %d, actual os socket rcvbuf size %d\n", *s, actrcvbuf);
if(rcvbuf != 0 && actrcvbuf/2 != rcvbuf) {
errmsg.LogError(errno, NO_ERRCODE,
"cannot set os socket rcvbuf size %d for socket %d, value now is %d",
rcvbuf, *s, actrcvbuf/2);
}
} else {
DBGPRINTF("could not obtain os socket rcvbuf size for socket %d: %s\n",
*s, rs_strerror_r(errno, errStr, sizeof(errStr)));
}
}
if(bIsServer) {
/* rgerhards, 2007-06-22: if we run on a kernel that does not support
* the IPV6_V6ONLY socket option, we need to use a work-around. On such
* systems the IPv6 socket does also accept IPv4 sockets. So an IPv4
* socket can not listen on the same port as an IPv6 socket. The only
* workaround is to ignore the "socket in use" error. This is what we
* do if we have to.
*/
if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0)
# ifndef IPV6_V6ONLY
&& (errno != EADDRINUSE)
# endif
) {
if (errno == EADDRNOTAVAIL && ipfreebind != IPFREEBIND_DISABLED) {
if (setsockopt(*s, IPPROTO_IP, IP_FREEBIND, &on, sizeof(on)) < 0) {
errmsg.LogError(errno, NO_ERRCODE, "setsockopt(IP_FREEBIND)");
}
else if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
errmsg.LogError(errno, NO_ERRCODE, "bind with IP_FREEBIND");
} else {
if (ipfreebind >= IPFREEBIND_ENABLED_WITH_LOG)
errmsg.LogMsg(0, RS_RET_OK_WARN, LOG_WARNING,
"bound address %s IP free", hostname);
continue;
}
}
close(*s);
*s = -1;
continue;
}
}
(*socks)++;
s++;
}
if(res != NULL)
@ -1512,7 +1527,7 @@ create_udp_socket(uchar *hostname,
if(*socks == 0) {
errmsg.LogError(0, NO_ERRCODE, "No UDP socket could successfully be initialized, "
"some functionality may be disabled.\n");
/* we do NOT need to free any sockets, because there were none... */
/* we do NOT need to close any sockets, because there were none... */
free(socks);
return(NULL);
}