/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, 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 Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see .
**/
#include "../sysinfo.h"
#include "zbxjson.h"
#include "zbxcomms.h"
#include "zbxnum.h"
#include "zbxip.h"
typedef struct
{
zbx_uint64_t ibytes;
zbx_uint64_t ipackets;
zbx_uint64_t ierr;
zbx_uint64_t idrop;
zbx_uint64_t ififo;
zbx_uint64_t iframe;
zbx_uint64_t icompressed;
zbx_uint64_t imulticast;
zbx_uint64_t obytes;
zbx_uint64_t opackets;
zbx_uint64_t oerr;
zbx_uint64_t odrop;
zbx_uint64_t ocolls;
zbx_uint64_t ofifo;
zbx_uint64_t ocarrier;
zbx_uint64_t ocompressed;
}
net_stat_t;
typedef struct
{
struct addrinfo *ai;
unsigned short port;
unsigned int prefix_sz;
unsigned char mapped;
}
net_count_info_t;
#define NET_CONN_TYPE_TCP 0
#define NET_CONN_TYPE_UDP 1
#if HAVE_INET_DIAG
# include
# include
# include
enum
{
STATE_UNKNOWN = 0,
STATE_ESTABLISHED,
STATE_SYN_SENT,
STATE_SYN_RECV,
STATE_FIN_WAIT1,
STATE_FIN_WAIT2,
STATE_TIME_WAIT,
STATE_CLOSE,
STATE_CLOSE_WAIT,
STATE_LAST_ACK,
STATE_LISTEN,
STATE_CLOSING,
STATE_MAXSTATES
};
enum
{
NLERR_OK = 0,
NLERR_UNKNOWN,
NLERR_SOCKCREAT,
NLERR_BADSEND,
NLERR_BADRECV,
NLERR_RECVTIMEOUT,
NLERR_RESPTRUNCAT,
NLERR_OPNOTSUPPORTED,
NLERR_UNKNOWNMSGTYPE
};
static int nlerr;
static int find_tcp_port_by_state_nl(unsigned short port, int state, int *found)
{
struct
{
struct nlmsghdr nlhdr;
struct inet_diag_req r;
}
request;
int ret = FAIL, fd, status;
int families[] = {AF_INET, AF_INET6, AF_UNSPEC};
unsigned int sequence = 0x58425A;
struct timeval timeout = { 1, 500 * 1000 };
struct sockaddr_nl s_sa = { AF_NETLINK, 0, 0, 0 };
struct iovec s_io[1] = { { &request, sizeof(request) } };
struct msghdr s_msg;
char buffer[BUFSIZ] = { 0 };
struct sockaddr_nl r_sa = { AF_NETLINK, 0, 0, 0 };
struct iovec r_io[1] = { { buffer, BUFSIZ } };
struct msghdr r_msg;
struct nlmsghdr *r_hdr;
s_msg.msg_name = (void *)&s_sa;
s_msg.msg_namelen = sizeof(struct sockaddr_nl);
s_msg.msg_iov = s_io;
s_msg.msg_iovlen = 1;
s_msg.msg_control = NULL;
s_msg.msg_controllen = 0;
s_msg.msg_flags = 0;
r_msg.msg_name = (void *)&r_sa;
r_msg.msg_namelen = sizeof(struct sockaddr_nl);
r_msg.msg_iov = r_io;
r_msg.msg_iovlen = 1;
r_msg.msg_control = NULL;
r_msg.msg_controllen = 0;
r_msg.msg_flags = 0;
*found = 0;
request.nlhdr.nlmsg_len = sizeof(request);
request.nlhdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT | NLM_F_MATCH;
request.nlhdr.nlmsg_pid = 0;
request.nlhdr.nlmsg_seq = sequence;
request.nlhdr.nlmsg_type = TCPDIAG_GETSOCK;
memset(&request.r, 0, sizeof(request.r));
request.r.idiag_states = (1 << state);
if (-1 == (fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_INET_DIAG)) ||
0 != setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(struct timeval)))
{
nlerr = NLERR_SOCKCREAT;
goto out;
}
nlerr = NLERR_OK;
for (int i = 0; AF_UNSPEC != families[i]; i++)
{
request.r.idiag_family = families[i];
if (-1 == sendmsg(fd, &s_msg, 0))
{
nlerr = NLERR_BADSEND;
goto out;
}
while (NLERR_OK == nlerr)
{
status = recvmsg(fd, &r_msg, 0);
if (0 > status)
{
if (EAGAIN == errno || EWOULDBLOCK == errno)
nlerr = NLERR_RECVTIMEOUT;
else if (EINTR != errno)
nlerr = NLERR_BADRECV;
continue;
}
if (0 == status)
break;
for (r_hdr = (struct nlmsghdr *)buffer; NLMSG_OK(r_hdr, (unsigned)status);
r_hdr = NLMSG_NEXT(r_hdr, status))
{
struct inet_diag_msg *r = (struct inet_diag_msg *)NLMSG_DATA(r_hdr);
if (sequence != r_hdr->nlmsg_seq)
continue;
switch (r_hdr->nlmsg_type)
{
case NLMSG_DONE:
goto out;
case NLMSG_ERROR:
{
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(r_hdr);
if (NLMSG_LENGTH(sizeof(struct nlmsgerr)) > r_hdr->nlmsg_len)
{
nlerr = NLERR_RESPTRUNCAT;
}
else
{
nlerr = (EOPNOTSUPP == -err->error ? NLERR_OPNOTSUPPORTED :
NLERR_UNKNOWN);
}
goto out;
}
case 0x12:
if (state == r->idiag_state && port == ntohs(r->id.idiag_sport))
{
*found = 1;
goto out;
}
break;
default:
nlerr = NLERR_UNKNOWNMSGTYPE;
break;
}
}
}
}
out:
if (-1 != fd)
close(fd);
if (NLERR_OK == nlerr)
ret = SUCCEED;
return ret;
}
#endif
static int get_net_stat(const char *if_name, net_stat_t *result, char **error)
{
int ret = SYSINFO_RET_FAIL;
char line[MAX_STRING_LEN], name[MAX_STRING_LEN], *p;
FILE *f;
if (NULL == if_name || '\0' == *if_name)
{
*error = zbx_strdup(NULL, "Network interface name cannot be empty.");
return SYSINFO_RET_FAIL;
}
if (NULL == (f = fopen("/proc/net/dev", "r")))
{
*error = zbx_dsprintf(NULL, "Cannot open /proc/net/dev: %s", zbx_strerror(errno));
return SYSINFO_RET_FAIL;
}
while (NULL != fgets(line, sizeof(line), f))
{
if (NULL == (p = strstr(line, ":")))
continue;
*p = '\t';
if (17 == sscanf(line, "%s\t" ZBX_FS_UI64 "\t" ZBX_FS_UI64 "\t"
ZBX_FS_UI64 "\t" ZBX_FS_UI64 "\t"
ZBX_FS_UI64 "\t" ZBX_FS_UI64 "\t"
ZBX_FS_UI64 "\t" ZBX_FS_UI64 "\t"
ZBX_FS_UI64 "\t" ZBX_FS_UI64 "\t"
ZBX_FS_UI64 "\t" ZBX_FS_UI64 "\t"
ZBX_FS_UI64 "\t" ZBX_FS_UI64 "\t"
ZBX_FS_UI64 "\t" ZBX_FS_UI64 "\n",
name,
&result->ibytes, /* bytes */
&result->ipackets, /* packets */
&result->ierr, /* errs */
&result->idrop, /* drop */
&result->ififo, /* fifo (overruns) */
&result->iframe, /* frame */
&result->icompressed, /* compressed */
&result->imulticast, /* multicast */
&result->obytes, /* bytes */
&result->opackets, /* packets */
&result->oerr, /* errs */
&result->odrop, /* drop */
&result->ofifo, /* fifo (overruns)*/
&result->ocolls, /* colls (collisions) */
&result->ocarrier, /* carrier */
&result->ocompressed)) /* compressed */
{
if (0 == strcmp(name, if_name))
{
ret = SYSINFO_RET_OK;
break;
}
}
}
zbx_fclose(f);
if (SYSINFO_RET_FAIL == ret)
{
*error = zbx_strdup(NULL, "Cannot find information for this network interface in /proc/net/dev.");
return SYSINFO_RET_FAIL;
}
return SYSINFO_RET_OK;
}
/******************************************************************************
* *
* Purpose: Reads /proc/net/tcp(6) file by chunks until the last line in *
* in buffer has non-listening socket state. *
* *
* Parameters: filename - [IN] file to read *
* buffer - [IN/OUT] output buffer *
* buffer_alloc - [IN/OUT] output buffer size *
* *
* Return value: -1 error occurred during reading *
* 0 empty file (shouldn't happen) *
* >0 the number of bytes read *
* *
******************************************************************************/
static int proc_read_tcp_listen(const char *filename, char **buffer, int *buffer_alloc)
{
int n, fd, ret = -1, offset = 0;
char *start, *end;
if (-1 == (fd = open(filename, O_RDONLY)))
return -1;
while (0 != (n = read(fd, *buffer + offset, *buffer_alloc - offset)))
{
int count = 0;
if (-1 == n)
goto out;
offset += n;
if (offset == *buffer_alloc)
{
*buffer_alloc *= 2;
*buffer = (char *)zbx_realloc(*buffer, *buffer_alloc);
}
(*buffer)[offset] = '\0';
/* find the last full line */
for (start = *buffer + offset - 1; start > *buffer; start--)
{
if ('\n' == *start)
{
if (++count == 2)
break;
end = start;
}
}
/* check if the socket is in listening state */
if (2 == count)
{
start++;
count = 0;
while (' ' == *start++)
;
while (count < 3 && start < end)
{
while (' ' != *start)
start++;
while (' ' == *start)
start++;
count++;
}
if (3 == count && 0 != strncmp(start, "0A", 2) && 0 != strncmp(start, "03", 2))
break;
}
}
ret = offset;
out:
close(fd);
return ret;
}
/******************************************************************************
* *
* Purpose: reads whole file into buffer in single read operation *
* *
* Parameters: filename - [IN] file to read *
* buffer - [IN/OUT] output buffer *
* buffer_alloc - [IN/OUT] output buffer size *
* *
* Return value: -1 error occurred during reading *
* 0 empty file (shouldn't happen) *
* >0 the number of bytes read *
* *
******************************************************************************/
static int proc_read_file(const char *filename, char **buffer, int *buffer_alloc)
{
int n, fd, ret = -1, offset = 0;
if (-1 == (fd = open(filename, O_RDONLY)))
return -1;
while (0 != (n = read(fd, *buffer + offset, *buffer_alloc - offset)))
{
if (-1 == n)
goto out;
offset += n;
if (offset == *buffer_alloc)
{
*buffer_alloc *= 2;
*buffer = (char *)zbx_realloc(*buffer, *buffer_alloc);
}
}
ret = offset;
out:
close(fd);
return ret;
}
int net_if_in(AGENT_REQUEST *request, AGENT_RESULT *result)
{
net_stat_t ns;
char *if_name, *mode, *error;
if (2 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
return SYSINFO_RET_FAIL;
}
if_name = get_rparam(request, 0);
mode = get_rparam(request, 1);
if (SYSINFO_RET_OK != get_net_stat(if_name, &ns, &error))
{
SET_MSG_RESULT(result, error);
return SYSINFO_RET_FAIL;
}
if (NULL == mode || '\0' == *mode || 0 == strcmp(mode, "bytes")) /* default parameter */
SET_UI64_RESULT(result, ns.ibytes);
else if (0 == strcmp(mode, "packets"))
SET_UI64_RESULT(result, ns.ipackets);
else if (0 == strcmp(mode, "errors"))
SET_UI64_RESULT(result, ns.ierr);
else if (0 == strcmp(mode, "dropped"))
SET_UI64_RESULT(result, ns.idrop);
else if (0 == strcmp(mode, "overruns"))
SET_UI64_RESULT(result, ns.ififo);
else if (0 == strcmp(mode, "frame"))
SET_UI64_RESULT(result, ns.iframe);
else if (0 == strcmp(mode, "compressed"))
SET_UI64_RESULT(result, ns.icompressed);
else if (0 == strcmp(mode, "multicast"))
SET_UI64_RESULT(result, ns.imulticast);
else
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter."));
return SYSINFO_RET_FAIL;
}
return SYSINFO_RET_OK;
}
int net_if_out(AGENT_REQUEST *request, AGENT_RESULT *result)
{
net_stat_t ns;
char *if_name, *mode, *error;
if (2 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
return SYSINFO_RET_FAIL;
}
if_name = get_rparam(request, 0);
mode = get_rparam(request, 1);
if (SYSINFO_RET_OK != get_net_stat(if_name, &ns, &error))
{
SET_MSG_RESULT(result, error);
return SYSINFO_RET_FAIL;
}
if (NULL == mode || '\0' == *mode || 0 == strcmp(mode, "bytes")) /* default parameter */
SET_UI64_RESULT(result, ns.obytes);
else if (0 == strcmp(mode, "packets"))
SET_UI64_RESULT(result, ns.opackets);
else if (0 == strcmp(mode, "errors"))
SET_UI64_RESULT(result, ns.oerr);
else if (0 == strcmp(mode, "dropped"))
SET_UI64_RESULT(result, ns.odrop);
else if (0 == strcmp(mode, "overruns"))
SET_UI64_RESULT(result, ns.ofifo);
else if (0 == strcmp(mode, "collisions"))
SET_UI64_RESULT(result, ns.ocolls);
else if (0 == strcmp(mode, "carrier"))
SET_UI64_RESULT(result, ns.ocarrier);
else if (0 == strcmp(mode, "compressed"))
SET_UI64_RESULT(result, ns.ocompressed);
else
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter."));
return SYSINFO_RET_FAIL;
}
return SYSINFO_RET_OK;
}
int net_if_total(AGENT_REQUEST *request, AGENT_RESULT *result)
{
net_stat_t ns;
char *if_name, *mode, *error;
if (2 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
return SYSINFO_RET_FAIL;
}
if_name = get_rparam(request, 0);
mode = get_rparam(request, 1);
if (SYSINFO_RET_OK != get_net_stat(if_name, &ns, &error))
{
SET_MSG_RESULT(result, error);
return SYSINFO_RET_FAIL;
}
if (NULL == mode || '\0' == *mode || 0 == strcmp(mode, "bytes")) /* default parameter */
SET_UI64_RESULT(result, ns.ibytes + ns.obytes);
else if (0 == strcmp(mode, "packets"))
SET_UI64_RESULT(result, ns.ipackets + ns.opackets);
else if (0 == strcmp(mode, "errors"))
SET_UI64_RESULT(result, ns.ierr + ns.oerr);
else if (0 == strcmp(mode, "dropped"))
SET_UI64_RESULT(result, ns.idrop + ns.odrop);
else if (0 == strcmp(mode, "overruns"))
SET_UI64_RESULT(result, ns.ififo + ns.ofifo);
else if (0 == strcmp(mode, "compressed"))
SET_UI64_RESULT(result, ns.icompressed + ns.ocompressed);
else
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter."));
return SYSINFO_RET_FAIL;
}
return SYSINFO_RET_OK;
}
int net_if_collisions(AGENT_REQUEST *request, AGENT_RESULT *result)
{
net_stat_t ns;
char *if_name, *error;
if (1 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
return SYSINFO_RET_FAIL;
}
if_name = get_rparam(request, 0);
if (SYSINFO_RET_OK != get_net_stat(if_name, &ns, &error))
{
SET_MSG_RESULT(result, error);
return SYSINFO_RET_FAIL;
}
SET_UI64_RESULT(result, ns.ocolls);
return SYSINFO_RET_OK;
}
int net_if_discovery(AGENT_REQUEST *request, AGENT_RESULT *result)
{
char line[MAX_STRING_LEN], *p;
FILE *f;
struct zbx_json j;
ZBX_UNUSED(request);
if (NULL == (f = fopen("/proc/net/dev", "r")))
{
SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open /proc/net/dev: %s", zbx_strerror(errno)));
return SYSINFO_RET_FAIL;
}
zbx_json_initarray(&j, ZBX_JSON_STAT_BUF_LEN);
while (NULL != fgets(line, sizeof(line), f))
{
if (NULL == (p = strstr(line, ":")))
continue;
*p = '\0';
/* trim left spaces */
for (p = line; ' ' == *p && '\0' != *p; p++)
;
zbx_json_addobject(&j, NULL);
zbx_json_addstring(&j, "{#IFNAME}", p, ZBX_JSON_TYPE_STRING);
zbx_json_close(&j);
}
zbx_fclose(f);
zbx_json_close(&j);
SET_STR_RESULT(result, strdup(j.buffer));
zbx_json_free(&j);
return SYSINFO_RET_OK;
}
int net_tcp_listen(AGENT_REQUEST *request, AGENT_RESULT *result)
{
char pattern[64], *port_str, *buffer = NULL;
unsigned short port;
zbx_uint64_t listen = 0;
int ret = SYSINFO_RET_FAIL, buffer_alloc = 64 * ZBX_KIBIBYTE;
#ifdef HAVE_INET_DIAG
int found;
#endif
if (1 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
return SYSINFO_RET_FAIL;
}
port_str = get_rparam(request, 0);
if (NULL == port_str || SUCCEED != zbx_is_ushort(port_str, &port))
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
return SYSINFO_RET_FAIL;
}
#ifdef HAVE_INET_DIAG
if (SUCCEED == find_tcp_port_by_state_nl(port, STATE_LISTEN, &found))
{
ret = SYSINFO_RET_OK;
listen = found;
}
else
{
const char *error;
switch (nlerr)
{
case NLERR_UNKNOWN:
error = "unrecognized netlink error occurred";
break;
case NLERR_SOCKCREAT:
error = "cannot create netlink socket";
break;
case NLERR_BADSEND:
error = "cannot send netlink message to kernel";
break;
case NLERR_BADRECV:
error = "cannot receive netlink message from kernel";
break;
case NLERR_RECVTIMEOUT:
error = "receiving netlink response timed out";
break;
case NLERR_RESPTRUNCAT:
error = "received truncated netlink response from kernel";
break;
case NLERR_OPNOTSUPPORTED:
error = "netlink operation not supported";
break;
case NLERR_UNKNOWNMSGTYPE:
error = "received message of unrecognized type from kernel";
break;
default:
error = "unknown error";
}
zabbix_log(LOG_LEVEL_DEBUG, "netlink interface error: %s", error);
zabbix_log(LOG_LEVEL_DEBUG, "falling back on reading /proc/net/tcp...");
#endif
buffer = (char *)zbx_malloc(NULL, buffer_alloc);
if (0 < proc_read_tcp_listen("/proc/net/tcp", &buffer, &buffer_alloc))
{
ret = SYSINFO_RET_OK;
zbx_snprintf(pattern, sizeof(pattern), "%04X 00000000:0000 0A", (unsigned int)port);
if (NULL != strstr(buffer, pattern))
{
listen = 1;
goto out;
}
}
if (0 < proc_read_tcp_listen("/proc/net/tcp6", &buffer, &buffer_alloc))
{
ret = SYSINFO_RET_OK;
zbx_snprintf(pattern, sizeof(pattern), "%04X 00000000000000000000000000000000:0000 0A",
(unsigned int)port);
if (NULL != strstr(buffer, pattern))
listen = 1;
}
out:
zbx_free(buffer);
#ifdef HAVE_INET_DIAG
}
#endif
SET_UI64_RESULT(result, listen);
return ret;
}
int net_udp_listen(AGENT_REQUEST *request, AGENT_RESULT *result)
{
char pattern[64], *port_str, *buffer = NULL;
unsigned short port;
zbx_uint64_t listen = 0;
int ret = SYSINFO_RET_FAIL, n, buffer_alloc = 64 * ZBX_KIBIBYTE;
if (1 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
return SYSINFO_RET_FAIL;
}
port_str = get_rparam(request, 0);
if (NULL == port_str || SUCCEED != zbx_is_ushort(port_str, &port))
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
return SYSINFO_RET_FAIL;
}
buffer = (char *)zbx_malloc(NULL, buffer_alloc);
if (0 < (n = proc_read_file("/proc/net/udp", &buffer, &buffer_alloc)))
{
ret = SYSINFO_RET_OK;
zbx_snprintf(pattern, sizeof(pattern), "%04X 00000000:0000 07", (unsigned int)port);
buffer[n] = '\0';
if (NULL != strstr(buffer, pattern))
{
listen = 1;
goto out;
}
}
if (0 < (n = proc_read_file("/proc/net/udp6", &buffer, &buffer_alloc)))
{
ret = SYSINFO_RET_OK;
zbx_snprintf(pattern, sizeof(pattern), "%04X 00000000000000000000000000000000:0000 07",
(unsigned int)port);
buffer[n] = '\0';
if (NULL != strstr(buffer, pattern))
listen = 1;
}
out:
zbx_free(buffer);
SET_UI64_RESULT(result, listen);
return ret;
}
static unsigned char get_connection_state_tcp(const char *name)
{
unsigned char state;
if (0 == strcmp(name, "established"))
state = 1;
else if (0 == strcmp(name, "syn_sent"))
state = 2;
else if (0 == strcmp(name, "syn_recv"))
state = 3;
else if (0 == strcmp(name, "fin_wait1"))
state = 4;
else if (0 == strcmp(name, "fin_wait2"))
state = 5;
else if (0 == strcmp(name, "time_wait"))
state = 6;
else if (0 == strcmp(name, "close"))
state = 7;
else if (0 == strcmp(name, "close_wait"))
state = 8;
else if (0 == strcmp(name, "last_ack"))
state = 9;
else if (0 == strcmp(name, "listen"))
state = 10;
else if (0 == strcmp(name, "closing"))
state = 11;
else
state = 0;
return state;
}
static unsigned char get_connection_state_udp(const char *name)
{
unsigned char state;
if (0 == strcmp(name, "established"))
state = 1;
else if (0 == strcmp(name, "unconn"))
state = 7;
else
state = 0;
return state;
}
#ifdef HAVE_IPV6
static int scan_ipv6_addr(const char *addr, struct sockaddr_in6 *sa6)
{
for (int i = 0; i < 16; i += 4)
{
for (int k = 0; k < 4; k++)
{
if (1 != sscanf(addr + i * 2 + k * 2, "%2hhx", &sa6->sin6_addr.s6_addr[i + 3 - k]))
return FAIL;
}
}
return SUCCEED;
}
static void get_proc_net_count_ipv6(const char *filename, unsigned char state, net_count_info_t *exp_l,
net_count_info_t *exp_r, zbx_uint64_t *count)
{
char line[MAX_STRING_LEN], *p;
unsigned short lport, rport;
unsigned char state_f;
FILE *f;
ZBX_SOCKADDR sockaddr_l, sockaddr_r;
struct sockaddr_in6 *sa_l, *sa_r;
if (NULL == (f = fopen(filename, "r")))
return;
sa_l = (struct sockaddr_in6 *)&sockaddr_l;
sa_r = (struct sockaddr_in6 *)&sockaddr_r;
#ifdef HAVE_SOCKADDR_STORAGE_SS_FAMILY
sockaddr_l.ss_family = sockaddr_r.ss_family = AF_INET6;
#else
sockaddr_l.__ss_family = sockaddr_r.__ss_family = AF_INET6;
#endif
while (NULL != fgets(line, sizeof(line), f))
{
if (NULL == (p = strchr(line, ':')))
continue;
if (80 > strlen(p))
continue;
p += 2;
if (SUCCEED != scan_ipv6_addr(p, sa_l))
continue;
p += 32;
if (1 != sscanf(p, ":%hx", &lport))
continue;
p += 6;
if (SUCCEED != scan_ipv6_addr(p, sa_r))
continue;
p += 32;
if (2 != sscanf(p, ":%hx %hhx", &rport, &state_f))
continue;
if ((0 != exp_l->port && exp_l->port != lport) ||
(0 != exp_r->port && exp_r->port != rport) ||
(0 != state && state != state_f) ||
(NULL != exp_l->ai &&
FAIL == zbx_ip_cmp(exp_l->prefix_sz, exp_l->ai, &sockaddr_l,
1 == exp_l->mapped && 0 != exp_l->prefix_sz ? 0 : 1)) ||
(NULL != exp_r->ai &&
FAIL == zbx_ip_cmp(exp_r->prefix_sz, exp_r->ai, &sockaddr_r,
1 == exp_r->mapped && 0 != exp_r->prefix_sz ? 0 : 1)))
{
continue;
}
(*count)++;
}
zbx_fclose(f);
}
#endif
static void get_proc_net_count_ipv4(const char *filename, unsigned char state, net_count_info_t *exp_l,
net_count_info_t *exp_r, zbx_uint64_t *count)
{
char line[MAX_STRING_LEN], *p;
unsigned short lport, rport;
unsigned char state_f;
FILE *f;
ZBX_SOCKADDR sockaddr_l, sockaddr_r;
struct sockaddr_in *sa_l, *sa_r;
if (NULL == (f = fopen(filename, "r")))
{
return;
}
sa_l = (struct sockaddr_in *)&sockaddr_l;
sa_r = (struct sockaddr_in *)&sockaddr_r;
#ifdef HAVE_IPV6
#ifdef HAVE_SOCKADDR_STORAGE_SS_FAMILY
sockaddr_l.ss_family = sockaddr_r.ss_family = AF_INET;
#else
sockaddr_l.__ss_family = sockaddr_r.__ss_family = AF_INET;
#endif
#endif
while (NULL != fgets(line, sizeof(line), f))
{
if (NULL == (p = strchr(line, ':')))
continue;
if (5 != sscanf(p, ": %x:%hx %x:%hx %hhx", &sa_l->sin_addr.s_addr, &lport, &sa_r->sin_addr.s_addr,
&rport, &state_f))
{
continue;
}
if ((0 != exp_l->port && exp_l->port != lport) ||
(0 != exp_r->port && exp_r->port != rport) ||
(0 != state && state != state_f) ||
(NULL != exp_l->ai &&
FAIL == zbx_ip_cmp(exp_l->prefix_sz, exp_l->ai, &sockaddr_l,
1 == exp_l->mapped && 0 != exp_l->prefix_sz ? 0 : 1)) ||
(NULL != exp_r->ai &&
FAIL == zbx_ip_cmp(exp_r->prefix_sz, exp_r->ai, &sockaddr_r,
1 == exp_r->mapped && 0 != exp_r->prefix_sz ? 0 : 1)))
{
continue;
}
(*count)++;
}
zbx_fclose(f);
}
static int get_addr_info(const char *addr_in, const char *port_in, struct addrinfo *hints, net_count_info_t *info,
char **error)
{
char *cidr_sep, *addr;
const char *service = NULL;
int ret = FAIL, res, prefix_sz_local;
if (NULL != addr_in && '\0' != *addr_in)
{
prefix_sz_local = -1;
addr = zbx_strdup(NULL, addr_in);
if (NULL != (cidr_sep = strchr(addr, '/')))
{
*cidr_sep = '\0';
if (FAIL == validate_cidr(addr, cidr_sep + 1, &prefix_sz_local))
{
*error = zbx_dsprintf(*error, "Cannot validate CIDR \"%s/%s\"", addr, cidr_sep + 1);
goto err;
}
}
else if (FAIL == zbx_is_supported_ip(addr))
{
*error = zbx_dsprintf(*error, "IP is not supported: \"%s\"", addr_in);
goto err;
}
}
else
addr = NULL;
if (NULL != port_in && '\0' != *port_in)
{
if (SUCCEED != zbx_is_ushort(port_in, &info->port))
{
if (0 != atoi(port_in))
{
*error = zbx_dsprintf(*error, "Invalid port number: %s", port_in);
goto err;
}
service = port_in;
}
}
if (NULL == addr && NULL == service)
return SUCCEED;
if (EAI_SERVICE == (res = getaddrinfo(addr, service, hints, &info->ai)))
{
*error = zbx_dsprintf(*error, "The service \"%s\" is not available for the requested socket type.",
port_in);
goto err;
}
else if (0 != res)
{
*error = zbx_dsprintf(*error, "IP is not supported: \"%s\"", addr_in);
goto err;
}
#ifdef HAVE_IPV6
if (info->ai->ai_family == AF_INET6)
{
const unsigned char ipv6_mapped[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255};
if (NULL != addr)
{
if (-1 == prefix_sz_local)
prefix_sz_local = ZBX_IPV6_MAX_CIDR_PREFIX;
if (0 == memcmp(((struct sockaddr_in6*)info->ai->ai_addr)->sin6_addr.s6_addr, ipv6_mapped, 12))
info->mapped = 1;
}
if (NULL != service)
info->port = ntohs(((struct sockaddr_in6*)info->ai->ai_addr)->sin6_port);
}
else
#endif
{
if (NULL != addr && -1 == prefix_sz_local)
prefix_sz_local = ZBX_IPV4_MAX_CIDR_PREFIX;
if (NULL != service)
info->port = ntohs(((struct sockaddr_in*)info->ai->ai_addr)->sin_port);
}
if (NULL == addr)
{
freeaddrinfo(info->ai);
info->ai = NULL;
}
else
info->prefix_sz = (unsigned int)prefix_sz_local;
ret = SUCCEED;
err:
zbx_free(addr);
return ret;
}
static int net_socket_count(int conn_type, AGENT_REQUEST *request, AGENT_RESULT *result)
{
int ret = SYSINFO_RET_FAIL;
net_count_info_t info_l, info_r;
char *error = NULL, *laddr, *raddr, *lport, *rport, *state;
unsigned char state_num = 0;
zbx_uint64_t count = 0;
struct addrinfo hints;
if (5 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
return SYSINFO_RET_FAIL;
}
laddr = get_rparam(request, 0);
lport = get_rparam(request, 1);
raddr = get_rparam(request, 2);
rport = get_rparam(request, 3);
state = get_rparam(request, 4);
memset(&info_l, 0, sizeof(info_l));
memset(&info_r, 0, sizeof(info_r));
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_flags = AI_NUMERICHOST;
if (NET_CONN_TYPE_TCP == conn_type)
{
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
}
else
{
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
}
/* local address and port */
if (SUCCEED != get_addr_info(laddr, lport, &hints, &info_l, &error))
{
SET_MSG_RESULT(result, error);
goto err;
}
/* remote address and port */
if (SUCCEED != get_addr_info(raddr, rport, &hints, &info_r, &error))
{
SET_MSG_RESULT(result, error);
goto err;
}
/* connection state */
if (NULL != state && '\0' != *state && 0 == (state_num = (NET_CONN_TYPE_TCP ==
conn_type ? get_connection_state_tcp(state) : get_connection_state_udp(state))))
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fifth parameter."));
goto err;
}
get_proc_net_count_ipv4(NET_CONN_TYPE_TCP == conn_type ? "/proc/net/tcp" : "/proc/net/udp",
state_num, &info_l, &info_r, &count);
#ifdef HAVE_IPV6
get_proc_net_count_ipv6(NET_CONN_TYPE_TCP == conn_type ? "/proc/net/tcp6" : "/proc/net/udp6",
state_num, &info_l, &info_r, &count);
#endif
SET_UI64_RESULT(result, count);
ret = SYSINFO_RET_OK;
err:
if (NULL != info_l.ai)
freeaddrinfo(info_l.ai);
if (NULL != info_r.ai)
freeaddrinfo(info_r.ai);
return ret;
}
int net_tcp_socket_count(AGENT_REQUEST *request, AGENT_RESULT *result)
{
return net_socket_count(NET_CONN_TYPE_TCP, request, result);
}
int net_udp_socket_count(AGENT_REQUEST *request, AGENT_RESULT *result)
{
return net_socket_count(NET_CONN_TYPE_UDP, request, result);
}