/*
** Copyright (C) 2001-2024 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 <https://www.gnu.org/licenses/>.
**/

#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 <sys/socket.h>
#	include <linux/netlink.h>
#	include <linux/inet_diag.h>

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);
}