Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: aa491f6ca2
Fetching contributors…

Cannot retrieve contributors at this time

860 lines (674 sloc) 21.91 kB
/*
* Copyright (C) 2011 Tildeslash Ltd. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
*
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*/
#include <config.h>
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_POLL_H
#include <poll.h>
#endif
#ifdef HAVE_STDARG_H
#include <stdarg.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef NEED_SOCKLEN_T_DEFINED
#define _BSD_SOCKLEN_T_
#endif
#ifdef HAVE_NETINET_IN_SYSTM_H
#include <netinet/in_systm.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif
#ifdef HAVE_NETINET_IP_H
#include <netinet/ip.h>
#endif
#ifdef HAVE_NETINET_IP_ICMP_H
#include <netinet/ip_icmp.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_STDDEF_H
#include <stddef.h>
#else
#define offsetof(st, m) ((size_t) ( (char *)&((st *)(0))->m - (char *)0 ))
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifndef __dietlibc__
#ifdef HAVE_STROPTS_H
#include <stropts.h>
#endif
#endif
#include "monitor.h"
#include "net.h"
#include "ssl.h"
/**
* General purpose Network and Socket methods.
*
* @author Jan-Henrik Haukeland, <hauk@tildeslash.com>
* @author Christian Hopp, <chopp@iei.tu-clausthal.de>
* @author Martin Pala, <martinp@tildeslash.com>
*
* @file
*/
/* ------------------------------------------------------------- Definitions */
#define DATALEN 64
/* -------------------------------------------------------------- Prototypes */
static int do_connect(int, const struct sockaddr *, socklen_t, int);
static unsigned short checksum_ip(unsigned char *, int);
/* ------------------------------------------------------------------ Public */
/**
* Check if the hostname resolves
* @param hostname The host to check
* @return TRUE if hostname resolves, otherwise FALSE
*/
int check_host(const char *hostname) {
struct addrinfo hints;
struct addrinfo *res;
ASSERT(hostname);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = PF_INET; /* we support just IPv4 currently */
if(getaddrinfo(hostname, NULL, &hints, &res) != 0)
return FALSE;
freeaddrinfo(res);
return TRUE;
}
/**
* Verify that the socket is ready for i|o
* @param socket A socket
* @return TRUE if the socket is ready, otherwise FALSE.
*/
int check_socket(int socket) {
return (can_read(socket, 0) || can_write(socket, 0));
}
/**
* Verify that the udp server is up. The given socket must be a
* connected udp socket if we should be able to test the udp server.
* The test is conducted by sending a datagram to the server and
* check for a returned ICMP error when reading from the socket.
* @param socket A socket
* @return TRUE if the socket is ready, otherwise FALSE.
*/
int check_udp_socket(int socket) {
char buf[STRLEN]= {0};
/* We have to send something and if the UDP server is down/unreachable
* the remote host should send an ICMP error. We then need to call read
* to get the ICMP error as a ECONNREFUSED errno. This test is asynchronous
* so we must wait, but we do not want to block to long either and it is
* probably better to report a server falsely up than to block to long.
*/
sock_write(socket, buf, 1, 0);
if(sock_read(socket, buf, STRLEN, 2) < 0) {
switch(errno) {
case ECONNREFUSED: return FALSE;
default: break;
}
}
return TRUE;
}
/**
* Create a non-blocking socket against hostname:port with the given
* type. The type should be either SOCK_STREAM or SOCK_DGRAM.
* @param hostname The host to open a socket at
* @param port The port number to connect to
* @param type Socket type to use (SOCK_STREAM|SOCK_DGRAM)
* @param timeout If not connected within timeout seconds abort and return -1
* @return The socket or -1 if an error occured.
*/
int create_socket(const char *hostname, int port, int type, int timeout) {
int s;
struct sockaddr_in sin;
struct sockaddr_in *sa;
struct addrinfo hints;
struct addrinfo *result;
ASSERT(hostname);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
if(getaddrinfo(hostname, NULL, &hints, &result) != 0) {
return -1;
}
if((s= socket(AF_INET, type, 0)) < 0) {
freeaddrinfo(result);
return -1;
}
sa = (struct sockaddr_in *)result->ai_addr;
memcpy(&sin, sa, result->ai_addrlen);
sin.sin_family= AF_INET;
sin.sin_port= htons(port);
freeaddrinfo(result);
if(! set_noblock(s)) {
goto error;
}
if(fcntl(s, F_SETFD, FD_CLOEXEC) == -1)
goto error;
if(do_connect(s, (struct sockaddr *)&sin, sizeof(sin), timeout) < 0) {
goto error;
}
return s;
error:
close_socket(s);
return -1;
}
/**
* Open a socket using the given Port_T structure. The protocol,
* destination and type are selected appropriately.
* @param p connection description
* @return The socket or -1 if an error occured.
*/
int create_generic_socket(Port_T p) {
int socket_fd= -1;
ASSERT(p);
switch(p->family) {
case AF_UNIX:
socket_fd= create_unix_socket(p->pathname, p->timeout);
break;
case AF_INET:
socket_fd= create_socket(p->hostname, p->port, p->type, p->timeout);
break;
default:
socket_fd= -1;
}
return socket_fd;
}
/**
* Create a non-blocking UNIX socket.
* @param pathname The pathname to use for the unix socket
* @param timeout If not connected within timeout seconds abort and return -1
* @return The socket or -1 if an error occured.
*/
int create_unix_socket(const char *pathname, int timeout) {
int s;
struct sockaddr_un unixsocket;
ASSERT(pathname);
if((s= socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
LogError("%s: Cannot create socket -- %s\n", prog, STRERROR);
return -1;
}
unixsocket.sun_family= AF_UNIX;
snprintf(unixsocket.sun_path, sizeof(unixsocket.sun_path), "%s", pathname);
if(! set_noblock(s)) {
goto error;
}
if(do_connect(s, (struct sockaddr *)&unixsocket, sizeof(unixsocket), timeout) < 0) {
goto error;
}
return s;
error:
close_socket(s);
return -1;
}
/**
* Create a non-blocking server socket and bind it to the specified local
* port number, with the specified backlog. Set a socket option to
* make the port reusable again. If a bind address is given the socket
* will only accept connect requests to this addresses. If the bind
* address is NULL it will accept connections on any/all local
* addresses
* @param port The localhost port number to open
* @param backlog The maximum queue length for incomming connections
* @param bindAddr the local address the server will bind to
* @return The socket ready for accept, or -1 if an error occured.
*/
int create_server_socket(int port, int backlog, const char *bindAddr) {
int s;
int status;
int flag = 1;
struct sockaddr_in myaddr;
if((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
LogError("%s: Cannot create socket -- %s\n", prog, STRERROR);
return -1;
}
memset(&myaddr, 0, sizeof(struct sockaddr_in));
if(bindAddr) {
struct sockaddr_in *sa;
struct addrinfo hints;
struct addrinfo *result;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
if((status = getaddrinfo(bindAddr, NULL, &hints, &result)) != 0) {
LogError("%s: Cannot translate '%s' to IP address -- %s\n", prog, bindAddr, gai_strerror(status));
goto error;
}
sa = (struct sockaddr_in *)result->ai_addr;
memcpy(&myaddr, sa, result->ai_addrlen);
freeaddrinfo(result);
} else {
myaddr.sin_addr.s_addr= htonl(INADDR_ANY);
}
myaddr.sin_family= AF_INET;
myaddr.sin_port= htons(port);
if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&flag, sizeof(flag)) < 0) {
LogError("%s: Cannot set reuseaddr option -- %s\n", prog, STRERROR);
goto error;
}
if(! set_noblock(s))
goto error;
if(fcntl(s, F_SETFD, FD_CLOEXEC) == -1) {
LogError("%s: Cannot set close on exec option -- %s\n", prog, STRERROR);
goto error;
}
if(bind(s, (struct sockaddr *)&myaddr, sizeof(struct sockaddr_in)) < 0) {
LogError("%s: Cannot bind -- %s\n", prog, STRERROR);
goto error;
}
if(listen(s, backlog) < 0) {
LogError("%s: Cannot listen -- %s\n", prog, STRERROR);
goto error;
}
return s;
error:
if (close(s) < 0)
LogError("%s: Socket %d close failed -- %s\n", prog, s, STRERROR);
return -1;
}
/**
* Shutdown a socket and close the descriptor.
* @param socket The socket to shutdown and close
* @return TRUE if the close succeed otherwise FALSE
*/
int close_socket(int socket) {
int r;
shutdown(socket, 2);
/* Try to close even if shutdown failed so we won't leak file descriptors */
do {
r = close(socket);
} while(r == -1 && errno == EINTR);
if (r == -1)
LogError("%s: Socket %d close failed -- %s\n", prog, socket, STRERROR);
return r;
}
/**
* Enable nonblocking i|o on the given socket.
* @param socket A socket
* @return TRUE if success, otherwise FALSE
*/
int set_noblock(int socket) {
int flags = fcntl(socket, F_GETFL, 0);
if (fcntl(socket, F_SETFL, flags|O_NONBLOCK) == -1) {
LogError("%s: Cannot set nonblocking -- %s\n", prog, STRERROR);
return FALSE;
}
return TRUE;
}
/**
* Disable nonblocking i|o on the given socket
* @param socket A socket
* @return TRUE if success, otherwise FALSE
*/
int set_block(int socket) {
int flags;
flags= fcntl(socket, F_GETFL, 0);
flags &= ~O_NONBLOCK;
return (fcntl(socket, F_SETFL, flags) == 0);
}
/**
* Check if data is available, if not, wait timeout seconds for data
* to be present.
* @param socket A socket
* @param timeout How long to wait before timeout (value in seconds)
* @return Return TRUE if the event occured, otherwise FALSE.
*/
int can_read(int socket, int timeout) {
int r = 0;
struct pollfd fds[1];
fds[0].fd = socket;
fds[0].events = POLLIN;
do {
r = poll(fds, 1, timeout * 1000);
} while (r == -1 && errno == EINTR);
return (r > 0);
}
/**
* Check if data can be sent to the socket, if not, wait timeout
* seconds for the socket to be ready.
* @param socket A socket
* @param timeout How long to wait before timeout (value in seconds)
* @return Return TRUE if the event occured, otherwise FALSE.
*/
int can_write(int socket, int timeout) {
int r = 0;
struct pollfd fds[1];
fds[0].fd = socket;
fds[0].events = POLLOUT;
do {
r = poll(fds, 1, timeout * 1000);
} while (r == -1 && errno == EINTR);
return (r > 0);
}
/**
* Write <code>size</code> bytes from the <code>buffer</code> to the
* <code>socket</code>
* @param socket the socket to write to
* @param buffer The buffer to write
* @param size Number of bytes to send
* @param timeout Seconds to wait for data to be written
* @return The number of bytes sent or -1 if an error occured.
*/
int sock_write(int socket, const void *buffer, int size, int timeout) {
ssize_t n= 0;
if(size<=0)
return 0;
errno= 0;
do {
n= write(socket, buffer, size);
} while(n == -1 && errno == EINTR);
if(n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
if(! can_write(socket, timeout)) {
return -1;
}
do {
n= write(socket, buffer, size);
} while(n == -1 && errno == EINTR);
}
return n;
}
/**
* Read up to size bytes from the <code>socket</code> into the
* <code>buffer</code>. If data is not available wait for
* <code>timeout</code> seconds.
* @param socket the Socket to read data from
* @param buffer The buffer to write the data to
* @param size Number of bytes to read from the socket
* @param timeout Seconds to wait for data to be available
* @return The number of bytes read or -1 if an error occured.
*/
int sock_read(int socket, void *buffer, int size, int timeout) {
ssize_t n;
if(size<=0)
return 0;
errno= 0;
do {
n= read(socket, buffer, size);
} while(n == -1 && errno == EINTR);
if(n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
if(! can_read(socket, timeout)) {
return -1;
}
do {
n= read(socket, buffer, size);
} while(n == -1 && errno == EINTR);
}
return n;
}
/**
* Write <code>size</code> bytes from the <code>buffer</code> to the
* <code>socket</code>. The given socket <b>must</b> be a connected
* UDP socket
* @param socket the socket to write to
* @param buffer The buffer to write
* @param size Number of bytes to send
* @param timeout Seconds to wait for data to be written
* @return The number of bytes sent or -1 if an error occured.
*/
int udp_write(int socket, void *b, int len, int timeout) {
int i, n;
ASSERT(timeout>=0);
for(i= 4; i>=1; i--) {
do {
n= sock_write(socket, b, len, 0);
} while(n == -1 && errno == EINTR);
if(n == -1 && (errno != EAGAIN || errno != EWOULDBLOCK))
return -1;
/* Simple retransmit scheme, wait for the server to reply
back to our socket. This assume a request-response pattern,
which really is the only pattern we can support */
if(can_read(socket, timeout/i)) return n;
DEBUG("udp_write: Resending request\n");
}
errno= EHOSTUNREACH;
return -1;
}
/**
* Create a ICMP socket against hostname, send echo and wait for response.
* The 'count' echo requests is send and we expect at least one reply.
* @param hostname The host to open a socket at
* @param timeout If response will not come within timeout seconds abort
* @param count How many pings to send
* @return response time on succes, -1 on error, -2 when monit has no
* permissions for raw socket (normally requires root or net_icmpaccess
* privilege on Solaris)
*/
double icmp_echo(const char *hostname, int timeout, int count) {
struct sockaddr_in sout;
struct sockaddr_in *sa;
struct addrinfo hints;
struct addrinfo *result;
struct ip *iphdrin;
int len_out = offsetof(struct icmp, icmp_data) + DATALEN;
int len_in = sizeof(struct ip) + sizeof(struct icmp);
struct icmp *icmpin = NULL;
struct icmp *icmpout = NULL;
uint16_t id_in, id_out, seq_in;
int r, i, s, n = 0;
struct timeval t_in, t_out;
char buf[STRLEN];
double response = -1.;
#if ! defined NETBSD && ! defined AIX
int sol_ip;
unsigned ttl = 255;
#endif
ASSERT(hostname);
ASSERT(len_out < sizeof(buf));
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
if (getaddrinfo(hostname, NULL, &hints, &result) != 0) {
LogError("ICMP echo for %s -- getaddrinfo failed: %s\n", hostname, STRERROR);
return response;
}
if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
if (errno == EACCES || errno == EPERM) {
DEBUG("ICMP echo for %s -- cannot create socket: %s\n", hostname, STRERROR);
response = -2.;
} else {
LogError("ICMP echo for %s -- canot create socket: %s\n", hostname, STRERROR);
}
goto error2;
}
#if ! defined NETBSD && ! defined AIX
#ifdef HAVE_SOL_IP
sol_ip = SOL_IP;
#else
{
struct protoent *pent;
pent = getprotobyname("ip");
sol_ip = pent ? pent->p_proto : 0;
}
#endif
if (setsockopt(s, sol_ip, IP_TTL, (char *)&ttl, sizeof(ttl)) < 0) {
LogError("ICMP echo for %s -- setsockopt failed: %s\n", hostname, STRERROR);
goto error1;
}
#endif
id_out = getpid() & 0xFFFF;
icmpout = (struct icmp *)buf;
for (i = 0; i < count; i++) {
int j;
unsigned char *data = (unsigned char *)icmpout->icmp_data;
icmpout->icmp_code = 0;
icmpout->icmp_type = ICMP_ECHO;
icmpout->icmp_id = htons(id_out);
icmpout->icmp_seq = htons(i);
icmpout->icmp_cksum = 0;
/* Add originate timestamp to data section */
gettimeofday(&t_out, NULL);
memcpy(data, &t_out, sizeof(struct timeval));
data += sizeof(struct timeval);
/* Initialize rest of data section to numeric sequence */
for (j = 0; j < DATALEN - sizeof(struct timeval); j++)
data[j] = j;
icmpout->icmp_cksum = checksum_ip((unsigned char *)icmpout, len_out);
sa = (struct sockaddr_in *)result->ai_addr;
memcpy(&sout, sa, result->ai_addrlen);
sout.sin_family = AF_INET;
sout.sin_port = 0;
do {
n = sendto(s, (char *)icmpout, len_out, 0, (struct sockaddr *)&sout, sizeof(struct sockaddr));
} while(n == -1 && errno == EINTR);
if (n < 0) {
LogError("ICMP echo request for %s %d/%d failed -- %s\n", hostname, i + 1, count, STRERROR);
continue;
}
if (can_read(s, timeout)) {
socklen_t size = sizeof(struct sockaddr_in);
do {
n = recvfrom(s, buf, STRLEN, 0, (struct sockaddr *)&sout, &size);
} while(n == -1 && errno == EINTR);
if (n < 0) {
LogError("ICMP echo response for %s %d/%d failed -- %s\n", hostname, i + 1, count, STRERROR);
continue;
} else if (n < len_in) {
LogError("ICMP echo response for %s %d/%d failed -- received %d bytes, expected at least %d bytes\n", hostname, i + 1, count, n, len_in);
continue;
}
iphdrin = (struct ip *)buf;
icmpin = (struct icmp *)(buf + iphdrin->ip_hl * 4);
id_in = ntohs(icmpin->icmp_id);
seq_in = ntohs(icmpin->icmp_seq);
if (icmpin->icmp_type == ICMP_ECHOREPLY) {
if (id_in == id_out && seq_in < (uint16_t)count) {
/* Get the response time */
gettimeofday(&t_in, NULL);
memcpy(&t_out, icmpin->icmp_data, sizeof(struct timeval));
response = (double)(t_in.tv_sec - t_out.tv_sec) + (double)(t_in.tv_usec - t_out.tv_usec) / 1000000;
DEBUG("ICMP echo response for %s %d/%d succeeded -- received id=%d sequence=%d response_time=%fs\n", hostname, i + 1, count, id_in, seq_in, response);
break; // Wait for one response only
} else
LogError("ICMP echo response for %s %d/%d error -- received id=%d (expected id=%d), received sequence=%d (expected sequence=%d)\n", hostname, i + 1, count, id_in, id_out, seq_in, i);
} else
DEBUG("ICMP echo response for %s %d/%d -- expected ECHOREPLY, received response type: %x (%s), source id=%d (mine id=%d) sequence=%d (mine sequence=%d)\n", hostname, i + 1, count, icmpin->icmp_type, icmpin->icmp_type < 19 ? icmpnames[icmpin->icmp_type] : "unknown", id_in, id_out, seq_in, i);
} else
LogError("ICMP echo response for %s %d/%d timed out -- no response within %d seconds\n", hostname, i + 1, count, timeout);
}
error1:
do {
r = close(s);
} while(r == -1 && errno == EINTR);
if (r == -1)
LogError("%s: Socket %d close failed -- %s\n", prog, s, STRERROR);
error2:
freeaddrinfo(result);
return response;
}
/* ----------------------------------------------------------------- Private */
/*
* Do a non blocking connect, timeout if not connected within timeout seconds
*/
static int do_connect(int s, const struct sockaddr *addr, socklen_t addrlen, int timeout) {
int error;
struct pollfd fds[1];
switch (connect(s, addr, addrlen)) {
case 0:
return 0;
default:
if (errno != EINPROGRESS)
return -1;
break;
}
fds[0].fd = s;
fds[0].events = POLLIN|POLLOUT;
if (poll(fds, 1, timeout * 1000) == 0) {
errno = ETIMEDOUT;
return -1;
}
if (fds[0].events & POLLIN || fds[0].events & POLLOUT) {
socklen_t len = sizeof(error);
if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
return -1; // Solaris pending error
} else {
return -1;
}
if (error) {
errno = error;
return -1;
}
return 0;
}
/*
* Compute Internet Checksum for "count" bytes beginning at location "addr".
* Based on RFC1071.
*/
static unsigned short checksum_ip(unsigned char *_addr, int count) {
register long sum= 0;
unsigned short *addr= (unsigned short *)_addr;
while(count > 1) {
sum += *addr++;
count -= 2;
}
/* Add left-over byte, if any */
if(count > 0)
sum += *(unsigned char *)addr;
/* Fold 32-bit sum to 16 bits */
while(sum >> 16)
sum= (sum & 0xffff) + (sum >> 16);
return ~sum;
}
Jump to Line
Something went wrong with that request. Please try again.