/*
 * Copyright (c) 2001-2020 Mellanox Technologies, Ltd. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */


#include <errno.h>
#include <inttypes.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#include "hash.h"
#include "tc.h"
#include "daemon.h"
#include "nl.h"


/**
 * @struct nl_object
 * @brief netlink container
 */
struct nl_object {
	int fd;            /**< the netlink socket file descriptor used for communication */
	int seq;           /**< sequence number of send operation */
	char buf[81920];   /**< buffer for receive data */
};

nl_t nl_create(void)
{
	nl_t nt = NULL;
	int fd = -1;

	nt = (struct nl_object *)malloc(sizeof(*nt));
	if (nt) {
		int sndbuf_size = 32768;
		int rcvbuf_size = 32768;
		struct sockaddr_nl local;

		fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
		if (fd < 0) {
			log_error("Unable to create a netlink socket\n");
			goto err;
		}
		if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf_size, sizeof(int))) {
			log_error("Unable to set SO_SNDBUF\n");
			goto err;
		}
		if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf_size, sizeof(int))) {
			log_error("Unable to set SO_RCVBUF\n");
			goto err;
		}
		memset(&local, 0, sizeof(local));
		local.nl_family = AF_NETLINK;
		local.nl_groups = 0;
		if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0) {
			log_error("Unable to bind to the netlink socket\n");
			goto err;
		}

		memset(nt, 0, sizeof(*nt));
		nt->fd = fd;
		nt->seq = 0;
	}

	return nt;
err:
	if (fd >= 0) {
		close(fd);
	}
	if (nt) {
		free(nt);
	}
	nt = NULL;

	return NULL;
}

void nl_destroy(nl_t nt)
{
	if (nt) {
		close(nt->fd);
		free(nt);
		nt = NULL;
	}
}

int nl_send(nl_t nt, struct nlmsghdr *nl_msg)
{
	struct sockaddr_nl nladdr;
	struct iovec iov;
	struct msghdr msg;
	int ret = -1;

	nl_msg->nlmsg_seq = nt->seq++;

	memset(&nladdr, 0, sizeof(nladdr));
	nladdr.nl_family = AF_NETLINK;
	nladdr.nl_pid = 0;
	nladdr.nl_groups = 0;

	iov.iov_base = nl_msg;
	iov.iov_len = nl_msg->nlmsg_len;

	memset(&msg, 0, sizeof(msg));
	msg.msg_name = &nladdr;
	msg.msg_namelen = sizeof(nladdr);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;

	log_hexdump((void *)nl_msg, nl_msg->nlmsg_len);
	ret = sendmsg(nt->fd, &msg, 0);
	if (ret < 0) {
		log_error("Failed to send netlink message: %s (%d)\n",
			strerror(errno), errno);
		return ret;
	}

	return ret;
}

int nl_recv(nl_t nt, int (*cb)(struct nlmsghdr *, void *arg), void *arg)
{
	struct sockaddr_nl nladdr;
	struct iovec iov;
	struct msghdr msg;
	int ret = 0;
	int multipart = 0;

	memset(&nladdr, 0, sizeof(nladdr));

	iov.iov_base = nt->buf;
	iov.iov_len = sizeof(nt->buf);

	memset(&msg, 0, sizeof(msg));
	msg.msg_name = &nladdr;
	msg.msg_namelen = sizeof(nladdr);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;

	do {
		struct nlmsghdr *nl_msg;
		int recv_bytes = 0;

		recv_bytes = recvmsg(nt->fd, &msg, 0);
		if (recv_bytes <= 0) {
			if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
				continue;
			}
			return -1;
		}

		for (nl_msg = (struct nlmsghdr *)nt->buf;
		     NLMSG_OK(nl_msg, (unsigned int)recv_bytes);
		     nl_msg = NLMSG_NEXT(nl_msg, recv_bytes)) {
			if (nl_msg->nlmsg_type == NLMSG_ERROR) {
				struct nlmsgerr *err_data = NLMSG_DATA(nl_msg);

				if (err_data->error < 0) {
					errno = -err_data->error;
					return -1;
				}
				/* Ack message. */
				return 0;
			}
			/* Multi-part msgs and their trailing DONE message. */
			if (nl_msg->nlmsg_flags & NLM_F_MULTI) {
				if (nl_msg->nlmsg_type == NLMSG_DONE) {
					return 0;
				}
				multipart = 1;
			}
			if (cb) {
				ret = cb(nl_msg, arg);
			}
		}
	} while (multipart || (msg.msg_flags & MSG_TRUNC));

	return ret;
}

void nl_attr_add(struct nlmsghdr *nl_msg, unsigned short type,
		const void *data, unsigned int data_len)
{
	struct rtattr *rta;

	if ((NLMSG_ALIGN(nl_msg->nlmsg_len) + RTA_ALIGN(RTA_LENGTH(data_len))) > NLMSG_BUF) {
		log_error("Message size is: %d that exceeds limit: %d\n",
				(NLMSG_ALIGN(nl_msg->nlmsg_len) + RTA_ALIGN(RTA_LENGTH(data_len))), NLMSG_BUF);
		return ;
	}
	rta = (struct rtattr *)NLMSG_TAIL(nl_msg);
	rta->rta_len = RTA_LENGTH(data_len);
	rta->rta_type = type;
	if (data && data_len) {
		memcpy(RTA_DATA(rta), data, data_len);
	}
	nl_msg->nlmsg_len = NLMSG_ALIGN(nl_msg->nlmsg_len) + RTA_ALIGN(rta->rta_len);
}

struct rtattr *nl_attr_nest_start(struct nlmsghdr *nl_msg, int type)
{
	struct rtattr *nest = NLMSG_TAIL(nl_msg);

	nl_attr_add(nl_msg, type, NULL, 0);

	return nest;
}

int nl_attr_nest_end(struct nlmsghdr *nl_msg, struct rtattr *nest)
{
	nest->rta_len = (uintptr_t)NLMSG_TAIL(nl_msg) - (uintptr_t)nest;

	return nest->rta_len;
}
