Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2019 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdbool.h>
#include <fcntl.h>

#include <logging/log.h>
LOG_MODULE_REGISTER(net_sock_mgmt, CONFIG_NET_SOCKETS_LOG_LEVEL);

#include <kernel.h>
#include <sys/util.h>
#include <net/socket.h>
#include <syscall_handler.h>
#include <sys/fdtable.h>
#include <net/socket_net_mgmt.h>
#include <net/ethernet_mgmt.h>

#include "sockets_internal.h"
#include "net_private.h"

#define MSG_ALLOC_TIMEOUT K_MSEC(100)

__net_socket struct net_mgmt_socket {
	/* Network interface related to this socket */
	struct net_if *iface;

	/* A way to separate different sockets that listen same events */
	uintptr_t pid;

	/* net_mgmt mask */
	uint32_t mask;

	/* Message allocation timeout */
	k_timeout_t alloc_timeout;

	/* net_mgmt event timeout */
	k_timeout_t wait_timeout;

	/* Socket protocol */
	int proto;

	/* Is this entry in use (true) or not (false) */
	uint8_t is_in_use : 1;
};

static struct net_mgmt_socket
		mgmt_sockets[CONFIG_NET_SOCKETS_NET_MGMT_MAX_LISTENERS];

static const struct socket_op_vtable net_mgmt_sock_fd_op_vtable;

int znet_mgmt_socket(int family, int type, int proto)
{
	struct net_mgmt_socket *mgmt = NULL;
	int fd, i;

	for (i = 0; i < ARRAY_SIZE(mgmt_sockets); i++) {
		if (mgmt_sockets[i].is_in_use) {
			continue;
		}

		mgmt = &mgmt_sockets[i];
	}

	if (mgmt == NULL) {
		errno = ENOENT;
		return -1;
	}

	fd = z_reserve_fd();
	if (fd < 0) {
		errno = ENOSPC;
		return -1;
	}

	mgmt->is_in_use = true;
	mgmt->proto = proto;
	mgmt->alloc_timeout = MSG_ALLOC_TIMEOUT;
	mgmt->wait_timeout = K_FOREVER;

	z_finalize_fd(fd, mgmt,
		     (const struct fd_op_vtable *)&net_mgmt_sock_fd_op_vtable);

	return fd;
}

static int znet_mgmt_bind(struct net_mgmt_socket *mgmt,
			  const struct sockaddr *addr,
			  socklen_t addrlen)
{
	struct sockaddr_nm *nm_addr = (struct sockaddr_nm *)addr;

	if (addrlen != sizeof(struct sockaddr_nm)) {
		return -EINVAL;
	}

	if (nm_addr->nm_ifindex) {
		mgmt->iface = net_if_get_by_index(nm_addr->nm_ifindex);
		if (!mgmt->iface) {
			errno = ENOENT;
			return -1;
		}
	} else {
		mgmt->iface = NULL;
	}

	mgmt->pid = nm_addr->nm_pid;

	if (mgmt->proto == NET_MGMT_EVENT_PROTO) {
		mgmt->mask = nm_addr->nm_mask;

		if (mgmt->iface) {
			mgmt->mask |= NET_MGMT_IFACE_BIT;
		}
	}

	return 0;
}

ssize_t znet_mgmt_sendto(struct net_mgmt_socket *mgmt,
			 const void *buf, size_t len,
			 int flags, const struct sockaddr *dest_addr,
			 socklen_t addrlen)
{
	if (mgmt->proto == NET_MGMT_EVENT_PROTO) {
		/* For net_mgmt events, we only listen and never send */
		errno = ENOTSUP;
		return -1;
	}

	/* Add handling of other network management operations here when
	 * needed.
	 */

	errno = EINVAL;
	return -1;
}

static ssize_t znet_mgmt_recvfrom(struct net_mgmt_socket *mgmt, void *buf,
				  size_t max_len, int flags,
				  struct sockaddr *src_addr,
				  socklen_t *addrlen)
{
	struct sockaddr_nm *nm_addr = (struct sockaddr_nm *)src_addr;
	k_timeout_t timeout = mgmt->wait_timeout;
	uint32_t raised_event = 0;
	uint8_t *copy_to = buf;
	struct net_mgmt_msghdr hdr;
	struct net_if *iface;
	const uint8_t *info;
	size_t info_len;
	int ret;

	if (flags & ZSOCK_MSG_DONTWAIT) {
		timeout = K_NO_WAIT;
	}

again:
	if (mgmt->iface == NULL) {
		ret = net_mgmt_event_wait(mgmt->mask, &raised_event,
					  &iface, (const void **)&info,
					  &info_len, timeout);
	} else {
		ret = net_mgmt_event_wait_on_iface(mgmt->iface,
						   mgmt->mask,
						   &raised_event,
						   (const void **)&info,
						   &info_len,
						   timeout);
		iface = mgmt->iface;
	}

	if (ret == -ETIMEDOUT) {
		errno = EAGAIN;
		return -1;
	}

	if (ret < 0) {
		errno = -ret;
		return -1;
	}

	if ((mgmt->mask & raised_event) != raised_event) {
		if (K_TIMEOUT_EQ(timeout, K_FOREVER)) {
			goto again;
		}

		errno = EAGAIN;
		return -1;
	}

	if (nm_addr) {
		if (iface) {
			nm_addr->nm_ifindex = net_if_get_by_iface(iface);
		} else {
			nm_addr->nm_ifindex = 0;
		}

		nm_addr->nm_pid = mgmt->pid;
		nm_addr->nm_family = AF_NET_MGMT;
		nm_addr->nm_mask = raised_event;
	}

	if (info) {
		ret = info_len + sizeof(hdr);
		ret = MIN(max_len, ret);
		memcpy(&copy_to[sizeof(hdr)], info, ret);
	} else {
		ret = 0;
	}

	hdr.nm_msg_version = NET_MGMT_SOCKET_VERSION_1;
	hdr.nm_msg_len = ret;

	memcpy(copy_to, &hdr, sizeof(hdr));

	return ret;
}

static int znet_mgmt_getsockopt(struct net_mgmt_socket *mgmt, int level,
				int optname, void *optval, socklen_t *optlen)
{
	if (level != SOL_NET_MGMT_RAW || !optval || !optlen) {
		errno = EINVAL;
		return -1;
	}

	if (mgmt->iface == NULL) {
		errno = ENOENT;
		return -1;
	}

	if (IS_ENABLED(CONFIG_NET_L2_ETHERNET_MGMT)) {
		if (optname == NET_REQUEST_ETHERNET_GET_QAV_PARAM) {
			int ret;

			ret = net_mgmt(NET_REQUEST_ETHERNET_GET_QAV_PARAM,
				       mgmt->iface, (void *)optval, *optlen);
			if (ret < 0) {
				errno = -ret;
				return -1;
			}

			return 0;

		} else {
			errno = EINVAL;
			return -1;
		}
	}

	errno = ENOTSUP;
	return -1;
}

static int znet_mgmt_setsockopt(struct net_mgmt_socket *mgmt, int level,
				int optname, const void *optval,
				socklen_t optlen)
{
	if (level != SOL_NET_MGMT_RAW || !optval || !optlen) {
		errno = EINVAL;
		return -1;
	}

	if (mgmt->iface == NULL) {
		errno = ENOENT;
		return -1;
	}

	if (IS_ENABLED(CONFIG_NET_L2_ETHERNET_MGMT)) {
		if (optname == NET_REQUEST_ETHERNET_SET_QAV_PARAM) {
			int ret;

			ret = net_mgmt(NET_REQUEST_ETHERNET_SET_QAV_PARAM,
				       mgmt->iface, (void *)optval, optlen);
			if (ret < 0) {
				errno = -ret;
				return -1;
			}

			return 0;
		} else {
			errno = EINVAL;
			return -1;
		}
	}

	errno = ENOTSUP;
	return -1;
}

static ssize_t net_mgmt_sock_read(void *obj, void *buffer, size_t count)
{
	return znet_mgmt_recvfrom(obj, buffer, count, 0, NULL, 0);
}

static ssize_t net_mgmt_sock_write(void *obj, const void *buffer,
				   size_t count)
{
	return znet_mgmt_sendto(obj, buffer, count, 0, NULL, 0);
}

static int net_mgmt_sock_ioctl(void *obj, unsigned int request,
			       va_list args)
{
	return 0;
}

static int net_mgmt_sock_bind(void *obj, const struct sockaddr *addr,
			      socklen_t addrlen)
{
	return znet_mgmt_bind(obj, addr, addrlen);
}

/* The connect() function is not needed */
static int net_mgmt_sock_connect(void *obj, const struct sockaddr *addr,
				 socklen_t addrlen)
{
	return 0;
}

/*
 * The listen() and accept() functions are without any functionality.
 */
static int net_mgmt_sock_listen(void *obj, int backlog)
{
	return 0;
}

static int net_mgmt_sock_accept(void *obj, struct sockaddr *addr,
				socklen_t *addrlen)
{
	return 0;
}

static ssize_t net_mgmt_sock_sendto(void *obj, const void *buf,
				    size_t len, int flags,
				    const struct sockaddr *dest_addr,
				    socklen_t addrlen)
{
	return znet_mgmt_sendto(obj, buf, len, flags, dest_addr, addrlen);
}

static ssize_t net_mgmt_sock_recvfrom(void *obj, void *buf,
				      size_t max_len, int flags,
				      struct sockaddr *src_addr,
				      socklen_t *addrlen)
{
	return znet_mgmt_recvfrom(obj, buf, max_len, flags,
				  src_addr, addrlen);
}

static int net_mgmt_sock_getsockopt(void *obj, int level, int optname,
				    void *optval, socklen_t *optlen)
{
	return znet_mgmt_getsockopt(obj, level, optname, optval, optlen);
}

static int net_mgmt_sock_setsockopt(void *obj, int level, int optname,
				    const void *optval, socklen_t optlen)
{
	return znet_mgmt_setsockopt(obj, level, optname, optval, optlen);
}

static const struct socket_op_vtable net_mgmt_sock_fd_op_vtable = {
	.fd_vtable = {
		.read = net_mgmt_sock_read,
		.write = net_mgmt_sock_write,
		.ioctl = net_mgmt_sock_ioctl,
	},
	.bind = net_mgmt_sock_bind,
	.connect = net_mgmt_sock_connect,
	.listen = net_mgmt_sock_listen,
	.accept = net_mgmt_sock_accept,
	.sendto = net_mgmt_sock_sendto,
	.recvfrom = net_mgmt_sock_recvfrom,
	.getsockopt = net_mgmt_sock_getsockopt,
	.setsockopt = net_mgmt_sock_setsockopt,
};

static bool net_mgmt_is_supported(int family, int type, int proto)
{
	if ((type != SOCK_RAW && type != SOCK_DGRAM) ||
	    (proto != NET_MGMT_EVENT_PROTO)) {
		return false;
	}

	return true;
}

NET_SOCKET_REGISTER(af_net_mgmt, AF_NET_MGMT, net_mgmt_is_supported,
		    znet_mgmt_socket);